Bug 23697: (follow-up) Add autocompletion search in patron module
[koha-ffzg.git] / C4 / Auth.pm
index 8932066..7a7d9f5 100644 (file)
@@ -44,6 +44,7 @@ use POSIX qw/strftime/;
 use List::MoreUtils qw/ any /;
 use Encode qw( encode is_utf8);
 use C4::Auth_with_shibboleth;
+use Net::CIDR;
 
 # use utf8;
 use vars qw(@ISA @EXPORT @EXPORT_OK %EXPORT_TAGS $debug $ldap $cas $caslogout);
@@ -60,7 +61,7 @@ BEGIN {
     @ISA       = qw(Exporter);
     @EXPORT    = qw(&checkauth &get_template_and_user &haspermission &get_user_subpermissions);
     @EXPORT_OK = qw(&check_api_auth &get_session &check_cookie_auth &checkpw &checkpw_internal &checkpw_hash
-      &get_all_subpermissions &get_user_subpermissions track_login_daily
+      &get_all_subpermissions &get_user_subpermissions track_login_daily &in_ipset
     );
     %EXPORT_TAGS = ( EditPermissions => [qw(get_all_subpermissions get_user_subpermissions)] );
     $ldap      = C4::Context->config('useldapserver') || 0;
@@ -171,8 +172,9 @@ sub get_template_and_user {
     }
 
     # If we enforce GDPR and the user did not consent, redirect
+    # Exceptions for consent page itself and SCI/SCO system
     if( $in->{type} eq 'opac' && $user &&
-        $in->{'template_name'} !~ /opac-patron-consent/ &&
+        $in->{'template_name'} !~ /^(opac-patron-consent|sc[io]\/)/ &&
         C4::Context->preference('GDPR_Policy') eq 'Enforced' )
     {
         my $consent = Koha::Patron::Consents->search({
@@ -192,7 +194,7 @@ sub get_template_and_user {
         if (
 # If the user logged in is the SCO user and they try to go out of the SCO module,
 # log the user out removing the CGISESSID cookie
-               $in->{template_name} !~ m|sco/|
+            $in->{template_name} !~ m|sco/| && $in->{template_name} !~ m|errors/errorpage.tt|
             && C4::Context->preference('AutoSelfCheckID')
             && $user eq C4::Context->preference('AutoSelfCheckID')
           )
@@ -478,7 +480,7 @@ sub get_template_and_user {
             AmazonCoverImages                                                          => C4::Context->preference("AmazonCoverImages"),
             AutoLocation                                                               => C4::Context->preference("AutoLocation"),
             "BiblioDefaultView" . C4::Context->preference("IntranetBiblioDefaultView") => 1,
-            CircAutocompl                                                              => C4::Context->preference("CircAutocompl"),
+            PatronAutocompletion                                                       => C4::Context->preference("PatronAutocompletion"),
             FRBRizeEditions                                                            => C4::Context->preference("FRBRizeEditions"),
             IndependentBranches                                                        => C4::Context->preference("IndependentBranches"),
             IntranetNav                                                                => C4::Context->preference("IntranetNav"),
@@ -548,7 +550,6 @@ sub get_template_and_user {
 
         my @search_groups = Koha::Library::Groups->get_search_groups({ interface => 'opac' });
         $template->param(
-            OpacAdditionalStylesheet                   => C4::Context->preference("OpacAdditionalStylesheet"),
             AnonSuggestions                       => "" . C4::Context->preference("AnonSuggestions"),
             LibrarySearchGroups                   => \@search_groups,
             opac_name                             => $opac_name,
@@ -570,7 +571,6 @@ sub get_template_and_user {
             OpacKohaUrl                           => C4::Context->preference("OpacKohaUrl"),
             OpacMainUserBlock                     => "" . C4::Context->preference("OpacMainUserBlock"),
             OpacNav                               => "" . C4::Context->preference("OpacNav"),
-            OpacNavRight                          => "" . C4::Context->preference("OpacNavRight"),
             OpacNavBottom                         => "" . C4::Context->preference("OpacNavBottom"),
             OpacPasswordChange                    => C4::Context->preference("OpacPasswordChange"),
             OPACPatronDetails                     => C4::Context->preference("OPACPatronDetails"),
@@ -581,7 +581,6 @@ sub get_template_and_user {
             'Version'                             => C4::Context->preference('Version'),
             hidelostitems                         => C4::Context->preference("hidelostitems"),
             mylibraryfirst                        => ( C4::Context->preference("SearchMyLibraryFirst") && C4::Context->userenv ) ? C4::Context->userenv->{'branch'} : '',
-            opaclayoutstylesheet                  => "" . C4::Context->preference("opaclayoutstylesheet"),
             opacbookbag                           => "" . C4::Context->preference("opacbookbag"),
             opaccredits                           => "" . C4::Context->preference("opaccredits"),
             OpacFavicon                           => C4::Context->preference("OpacFavicon"),
@@ -1155,6 +1154,7 @@ sub checkauth {
                     $session->param( 'emailaddress', $emailaddress );
                     $session->param( 'ip',           $session->remote_addr() );
                     $session->param( 'lasttime',     time() );
+                    $session->param( 'interface',    $type);
                     $session->param( 'shibboleth',   $shibSuccess );
                     $debug and printf STDERR "AUTH_4: (%s)\t%s %s - %s\n", map { $session->param($_) } qw(cardnumber firstname surname branch);
                 }
@@ -1180,6 +1180,7 @@ sub checkauth {
                 $session->param( 'lasttime', time() );
                 $session->param( 'ip',       $session->remote_addr() );
                 $session->param( 'sessiontype', 'anon' );
+                $session->param( 'interface', $type);
             }
         }    # END if ( $q_userid
         elsif ( $type eq "opac" ) {
@@ -1192,6 +1193,7 @@ sub checkauth {
             $session->param( 'ip',          $session->remote_addr() );
             $session->param( 'lasttime',    time() );
             $session->param( 'sessiontype', 'anon' );
+            $session->param( 'interface', $type);
         }
     }    # END unless ($userid)
 
@@ -1235,8 +1237,6 @@ sub checkauth {
     my $template_name = ( $type eq 'opac' ) ? 'opac-auth.tt' : 'auth.tt';
     my $template = C4::Templates::gettemplate( $template_name, $type, $query );
     $template->param(
-        OpacAdditionalStylesheet                   => C4::Context->preference("OpacAdditionalStylesheet"),
-        opaclayoutstylesheet                  => C4::Context->preference("opaclayoutstylesheet"),
         login                                 => 1,
         INPUTS                                => \@inputs,
         script_name                           => get_script_name(),
@@ -1249,7 +1249,6 @@ sub checkauth {
         LibraryNameTitle                      => "" . $LibraryNameTitle,
         opacuserlogin                         => C4::Context->preference("opacuserlogin"),
         OpacNav                               => C4::Context->preference("OpacNav"),
-        OpacNavRight                          => C4::Context->preference("OpacNavRight"),
         OpacNavBottom                         => C4::Context->preference("OpacNavBottom"),
         opaccredits                           => C4::Context->preference("opaccredits"),
         OpacFavicon                           => C4::Context->preference("OpacFavicon"),
@@ -1427,6 +1426,7 @@ sub check_api_auth {
         my $session = get_session($sessionID);
         C4::Context->_new_userenv($sessionID);
         if ($session) {
+            C4::Context->interface($session->param('interface'));
             C4::Context->set_userenv(
                 $session->param('number'),       $session->param('id'),
                 $session->param('cardnumber'),   $session->param('firstname'),
@@ -1585,6 +1585,7 @@ sub check_api_auth {
                 $session->param( 'emailaddress', $emailaddress );
                 $session->param( 'ip',           $session->remote_addr() );
                 $session->param( 'lasttime',     time() );
+                $session->param( 'interface',    'api'  );
             }
             $session->param( 'cas_ticket', $cas_ticket);
             C4::Context->set_userenv(
@@ -1606,7 +1607,11 @@ sub check_api_auth {
   ($status, $sessionId) = check_api_auth($cookie, $userflags);
 
 Given a CGISESSID cookie set during a previous login to Koha, determine
-if the user has the privileges specified by C<$userflags>.
+if the user has the privileges specified by C<$userflags>. C<$userflags>
+is passed unaltered into C<haspermission> and as such accepts all options
+avaiable to that routine with the one caveat that C<check_api_auth> will
+also allow 'undef' to be passed and in such a case the permissions check
+will be skipped altogether.
 
 C<check_cookie_auth> is meant for authenticating special services
 such as tools/upload-file.pl that are invoked by other pages that
@@ -1670,6 +1675,7 @@ sub check_cookie_auth {
     my $session   = get_session($sessionID);
     C4::Context->_new_userenv($sessionID);
     if ($session) {
+        C4::Context->interface($session->param('interface'));
         C4::Context->set_userenv(
             $session->param('number'),       $session->param('id'),
             $session->param('cardnumber'),   $session->param('firstname'),
@@ -1701,7 +1707,7 @@ sub check_cookie_auth {
             return ( "expired", undef );
         } else {
             $session->param( 'lasttime', time() );
-            my $flags = haspermission( $userid, $flagsrequired );
+            my $flags = defined($flagsrequired) ? haspermission( $userid, $flagsrequired ) : 1;
             if ($flags) {
                 return ( "ok", $sessionID );
             } else {
@@ -1776,6 +1782,7 @@ sub checkpw {
 
     my @return;
     my $patron = Koha::Patrons->find({ userid => $userid });
+    $patron = Koha::Patrons->find({ cardnumber => $userid }) unless $patron;
     my $check_internal_as_fallback = 0;
     my $passwd_ok = 0;
     # Note: checkpw_* routines returns:
@@ -1841,7 +1848,7 @@ sub checkpw {
     if( $patron ) {
         if ( $passwd_ok ) {
             $patron->update({ login_attempts => 0 });
-        } else {
+        } elsif( !$patron->account_locked ) {
             $patron->update({ login_attempts => $patron->login_attempts + 1 });
         }
     }
@@ -2024,14 +2031,21 @@ sub get_all_subpermissions {
 
 =head2 haspermission
 
+  $flagsrequired = '*';                                 # Any permission at all
+  $flagsrequired = 'a_flag';                            # a_flag must be satisfied (all subpermissions)
+  $flagsrequired = [ 'a_flag', 'b_flag' ];              # a_flag OR b_flag must be satisfied
+  $flagsrequired = { 'a_flag => 1, 'b_flag' => 1 };     # a_flag AND b_flag must be satisfied
+  $flagsrequired = { 'a_flag' => 'sub_a' };             # sub_a of a_flag must be satisfied
+  $flagsrequired = { 'a_flag' => [ 'sub_a, 'sub_b' ] }; # sub_a OR sub_b of a_flag must be satisfied
+
   $flags = ($userid, $flagsrequired);
 
 C<$userid> the userid of the member
 C<$flags> is a query structure similar to that used by SQL::Abstract that
-denotes the combination of flags required.
+denotes the combination of flags required. It is a required parameter.
 
 The main logic of this method is that things in arrays are OR'ed, and things
-in hashes are AND'ed.
+in hashes are AND'ed. The `*` character can be used, at any depth, to denote `ANY`
 
 Returns member's flags or 0 if a permission is not met.
 
@@ -2049,6 +2063,7 @@ sub _dispatch {
         }
     } elsif ($ref eq 'HASH') {
         foreach my $key (keys %{$required}) {
+            next if $flags == 1;
             my $require = $required->{$key};
             my $rflags  = $flags->{$key};
             return 0 unless _dispatch($require, $rflags);
@@ -2072,17 +2087,44 @@ sub _dispatch {
 
 sub haspermission {
     my ( $userid, $flagsrequired ) = @_;
+
+    #Koha::Exceptions::WrongParameter->throw('$flagsrequired should not be undef')
+    #  unless defined($flagsrequired);
+
     my $sth = C4::Context->dbh->prepare("SELECT flags FROM borrowers WHERE userid=?");
     $sth->execute($userid);
     my $row = $sth->fetchrow();
     my $flags = getuserflags( $row, $userid );
 
+    return $flags unless defined($flagsrequired);
     return $flags if $flags->{superlibrarian};
     return _dispatch($flagsrequired, $flags);
 
     #FIXME - This fcn should return the failed permission so a suitable error msg can be delivered.
 }
 
+=head2 in_ipset
+
+  $flags = ($ipset);
+
+C<$ipset> A space separated string describing an IP set. Can include single IPs or ranges
+
+Returns 1 if the remote address is in the provided ipset, or 0 otherwise.
+
+=cut
+
+sub in_ipset {
+    my ($ipset) = @_;
+    my $result = 1;
+    my @allowedipranges = $ipset ? split(' ', $ipset) : ();
+    if (scalar @allowedipranges > 0) {
+        my @rangelist;
+        eval { @rangelist = Net::CIDR::range2cidr(@allowedipranges); }; return 0 if $@;
+        eval { $result = Net::CIDR::cidrlookup($ENV{'REMOTE_ADDR'}, @rangelist) } || ( $ENV{DEBUG} && warn 'cidrlookup failed for ' . join(' ',@rangelist) );
+     }
+     return $result ? 1 : 0;
+}
+
 sub getborrowernumber {
     my ($userid) = @_;
     my $userenv = C4::Context->userenv;