Bug 22461: Filter ldap mapping before inserting patron's info
[koha-equinox.git] / C4 / Auth_with_ldap.pm
index 58484a2..6def54d 100644 (file)
@@ -23,21 +23,20 @@ use Carp;
 
 use C4::Debug;
 use C4::Context;
-use C4::Members qw(AddMember changepassword);
 use C4::Members::Attributes;
 use C4::Members::AttributeTypes;
 use C4::Members::Messaging;
 use C4::Auth qw(checkpw_internal);
+use Koha::Patrons;
 use Koha::AuthUtils qw(hash_password);
 use List::MoreUtils qw( any );
 use Net::LDAP;
 use Net::LDAP::Filter;
 
-use vars qw($VERSION @ISA @EXPORT @EXPORT_OK %EXPORT_TAGS $debug);
+use vars qw(@ISA @EXPORT @EXPORT_OK %EXPORT_TAGS $debug);
 
 BEGIN {
        require Exporter;
-    $VERSION = 3.07.00.049;    # set the version for version checking
        @ISA    = qw(Exporter);
        @EXPORT = qw( checkpw_ldap );
 }
@@ -66,8 +65,17 @@ $debug and print STDERR "Got ", scalar(@mapkeys), " ldap mapkeys (  total  ): ",
 @mapkeys = grep {defined $mapping{$_}->{is}} @mapkeys;
 $debug and print STDERR "Got ", scalar(@mapkeys), " ldap mapkeys (populated): ", join ' ', @mapkeys, "\n";
 
+my %categorycode_conversions;
+my $default_categorycode;
+if(defined $ldap->{categorycode_mapping}) {
+    $default_categorycode = $ldap->{categorycode_mapping}->{default};
+    foreach my $cat (@{$ldap->{categorycode_mapping}->{categorycode}}) {
+        $categorycode_conversions{$cat->{value}} = $cat->{content};
+    }
+}
+
 my %config = (
-       anonymous => ($ldapname and $ldappassword) ? 0 : 1,
+    anonymous => defined ($ldap->{anonymous_bind}) ? $ldap->{anonymous_bind} : 1,
     replicate => defined($ldap->{replicate}) ? $ldap->{replicate} : 1,  #    add from LDAP to Koha database for new user
        update => defined($ldap->{update}   ) ? $ldap->{update}    : 1,  # update from LDAP to Koha database for existing user
 );
@@ -96,10 +104,10 @@ sub search_method {
                warn sprintf("LDAP Auth rejected : %s gets %d hits\n", $filter->as_string, $count) . description($search);
                return 0;
        }
-       if ($count != 1) {
-               warn sprintf("LDAP Auth rejected : %s gets %d hits\n", $filter->as_string, $count);
-               return 0;
-       }
+    if ($count == 0) {
+        warn sprintf("LDAP Auth rejected : search with filter '%s' returns no hit\n", $filter->as_string);
+        return 0;
+    }
     return $search;
 }
 
@@ -115,9 +123,10 @@ sub checkpw_ldap {
        #$debug and $db->debug(5);
     my $userldapentry;
 
+    # first, LDAP authentication
     if ( $ldap->{auth_by_bind} ) {
         my $principal_name;
-        if ( $ldap->{anonymous_bind} ) {
+        if ( $config{anonymous} ) {
 
             # Perform an anonymous bind
             my $res = $db->bind;
@@ -145,7 +154,7 @@ sub checkpw_ldap {
         # Perform a LDAP bind for the given username using the matched DN
         my $res = $db->bind( $principal_name, password => $password );
         if ( $res->code ) {
-            if ( $ldap->{anonymous_bind} ) {
+            if ( $config{anonymous} ) {
                 # With anonymous_bind approach we can be sure we have found the correct user
                 # and that any 'code' response indicates a 'bad' user (be that blocked, banned
                 # or password changed). We should not fall back to local accounts in this case.
@@ -166,19 +175,30 @@ sub checkpw_ldap {
             $userldapentry = $search->shift_entry;
         }
     } else {
-               my $res = ($config{anonymous}) ? $db->bind : $db->bind($ldapname, password=>$ldappassword);
+        my $res = ($config{anonymous}) ? $db->bind : $db->bind($ldapname, password=>$ldappassword);
                if ($res->code) {               # connection refused
                        warn "LDAP bind failed as ldapuser " . ($ldapname || '[ANONYMOUS]') . ": " . description($res);
                        return 0;
                }
         my $search = search_method($db, $userid) or return 0;   # warnings are in the sub
-        $userldapentry = $search->shift_entry;
-               my $cmpmesg = $db->compare( $userldapentry, attr=>'userpassword', value => $password );
-               if ($cmpmesg->code != 6) {
-                       warn "LDAP Auth rejected : invalid password for user '$userid'. " . description($cmpmesg);
-                       return -1;
-               }
-       }
+        # Handle multiple branches. Same login exists several times in different branches.
+        my $bind_ok = 0;
+        while (my $entry = $search->shift_entry) {
+            my $user_ldap_bind_ret = $db->bind($entry->dn, password => $password);
+            unless ($user_ldap_bind_ret->code) {
+                $userldapentry = $entry;
+                $bind_ok = 1;
+                last;
+            }
+        }
+
+        unless ($bind_ok) {
+            warn "LDAP Auth rejected : invalid password for user '$userid'.";
+            return -1;
+        }
+
+
+    }
 
     # To get here, LDAP has accepted our user's login attempt.
     # But we still have work to do.  See perldoc below for detailed breakdown.
@@ -201,31 +221,34 @@ sub checkpw_ldap {
                return(1, $cardnumber, $local_userid);
         }
     } elsif ($config{replicate}) { # A2, C2
-        $borrowernumber = AddMember(%borrower) or die "AddMember failed";
+        my @columns = Koha::Patrons->columns;
+        my $patron = Koha::Patron->new(
+            map {
+                grep join( ' ', @columns ) =~ /$_/
+                  ? ( $_ => $data{$_} )
+                  : ()
+            } keys %borrower
+        )->store;
+        die "Insert of new patron failed" unless $patron;
+        $borrowernumber = $patron->borrowernumber;
         C4::Members::Messaging::SetMessagingPreferencesFromDefaults( { borrowernumber => $borrowernumber, categorycode => $borrower{'categorycode'} } );
    } else {
         return 0;   # B2, D2
     }
     if (C4::Context->preference('ExtendedPatronAttributes') && $borrowernumber && ($config{update} ||$config{replicate})) {
-        my @extended_patron_attributes;
         foreach my $attribute_type ( C4::Members::AttributeTypes::GetAttributeTypes() ) {
             my $code = $attribute_type->{code};
-            if ( exists($borrower{$code}) && $borrower{$code} !~ m/^\s*$/ ) { # skip empty values
-                push @extended_patron_attributes, { code => $code, value => $borrower{$code} };
+            unless (exists($borrower{$code}) && $borrower{$code} !~ m/^\s*$/ ) {
+                next;
             }
-        }
-        #Check before add
-        my @unique_attr;
-        foreach my $attr ( @extended_patron_attributes ) {
-            if (C4::Members::Attributes::CheckUniqueness($attr->{code}, $attr->{value}, $borrowernumber)) {
-                push @unique_attr, $attr;
+            if (C4::Members::Attributes::CheckUniqueness($code, $borrower{$code}, $borrowernumber)) {
+                C4::Members::Attributes::UpdateBorrowerAttribute($borrowernumber, {code => $code, attribute => $borrower{$code}});
             } else {
-                warn "ERROR_extended_unique_id_failed $attr->{code} $attr->{value}";
+                warn "ERROR_extended_unique_id_failed $code $borrower{$code}";
             }
         }
-        C4::Members::Attributes::SetBorrowerAttributes($borrowernumber, \@unique_attr);
     }
-return(1, $cardnumber, $userid);
+    return(1, $cardnumber, $userid);
 }
 
 # Pass LDAP entry object and local cardnumber (userid).
@@ -263,6 +286,14 @@ sub ldap_entry_2_hash {
                . substr($borrower{ 'surname' },0,1)
                . " ");
 
+    # categorycode conversions
+    if(defined $categorycode_conversions{$borrower{categorycode}}) {
+        $borrower{categorycode} = $categorycode_conversions{$borrower{categorycode}};
+    }
+    elsif($default_categorycode) {
+        $borrower{categorycode} = $default_categorycode;
+    }
+
        # check if categorycode exists, if not, fallback to default from koha-conf.xml
        my $dbh = C4::Context->dbh;
        my $sth = $dbh->prepare("SELECT categorycode FROM categories WHERE categorycode = ?");
@@ -321,10 +352,10 @@ sub _do_changepassword {
         $sth->execute($borrowerid);
         return $cardnum;
     }
-    my $digest = hash_password($password);
 
+    my $digest = hash_password($password);
     $debug and print STDERR "changing local password for borrowernumber=$borrowerid to '$digest'\n";
-    changepassword($userid, $borrowerid, $digest);
+    Koha::Patrons->find($borrowerid)->set_password({ password => $password, skip_validation => 1 });
 
     my ($ok, $cardnum) = checkpw_internal(C4::Context->dbh, $userid, $password);
     return $cardnum if $ok;
@@ -339,7 +370,16 @@ sub update_local {
     my $borrowerid = shift or croak "No borrowerid";
     my $borrower   = shift or croak "No borrower record";
 
+    # skip extended patron attributes in 'borrowers' attribute update
     my @keys = keys %$borrower;
+    if (C4::Context->preference('ExtendedPatronAttributes')) {
+        foreach my $attribute_type ( C4::Members::AttributeTypes::GetAttributeTypes() ) {
+           my $code = $attribute_type->{code};
+           @keys = grep { $_ ne $code } @keys;
+           $debug and printf STDERR "ignoring extended patron attribute '%s' in update_local()\n", $code;
+        }
+    }
+
     my $dbh = C4::Context->dbh;
     my $query = "UPDATE  borrowers\nSET     " .
         join(',', map {"$_=?"} @keys) .