Bug 14066: Remove the Readonly[::XS] dependency
[srvgit] / C4 / Auth_with_ldap.pm
index 47c3056..58484a2 100644 (file)
@@ -4,18 +4,18 @@ package C4::Auth_with_ldap;
 #
 # This file is part of Koha.
 #
-# Koha is free software; you can redistribute it and/or modify it under the
-# terms of the GNU General Public License as published by the Free Software
-# Foundation; either version 2 of the License, or (at your option) any later
-# version.
+# Koha is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3 of the License, or
+# (at your option) any later version.
 #
-# Koha is distributed in the hope that it will be useful, but WITHOUT ANY
-# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
-# A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
+# Koha is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
 #
-# You should have received a copy of the GNU General Public License along
-# with Koha; if not, write to the Free Software Foundation, Inc.,
-# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+# You should have received a copy of the GNU General Public License
+# along with Koha; if not, see <http://www.gnu.org/licenses>.
 
 use strict;
 #use warnings; FIXME - Bug 2505
@@ -26,6 +26,7 @@ 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::AuthUtils qw(hash_password);
 use List::MoreUtils qw( any );
@@ -87,7 +88,9 @@ sub search_method {
                  base => $base,
                filter => $filter,
                # attrs => ['*'],
-       ) or die "LDAP search failed to return object.";
+    );
+    die "LDAP search failed to return object : " . $search->error if $search->code;
+
        my $count = $search->count;
        if ($search->code > 0) {
                warn sprintf("LDAP Auth rejected : %s gets %d hits\n", $filter->as_string, $count) . description($search);
@@ -142,8 +145,19 @@ 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 ) {
-            warn "LDAP bind failed as kohauser $userid: " . description($res);
-            return 0;
+            if ( $ldap->{anonymous_bind} ) {
+                # 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.
+                warn "LDAP bind failed as kohauser $userid: " . description($res);
+                return -1;
+            } else {
+                # Without a anonymous_bind, we cannot be sure we are looking at a valid ldap user
+                # at all, and thus we should fall back to local logins to restore previous behaviour
+                # see bug 12831
+                warn "LDAP bind failed as kohauser $userid: " . description($res);
+                return 0;
+            }
         }
         if ( !defined($userldapentry)
             && ( $config{update} or $config{replicate} ) )
@@ -162,7 +176,7 @@ sub checkpw_ldap {
                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 0;
+                       return -1;
                }
        }
 
@@ -188,6 +202,7 @@ sub checkpw_ldap {
         }
     } elsif ($config{replicate}) { # A2, C2
         $borrowernumber = AddMember(%borrower) or die "AddMember failed";
+        C4::Members::Messaging::SetMessagingPreferencesFromDefaults( { borrowernumber => $borrowernumber, categorycode => $borrower{'categorycode'} } );
    } else {
         return 0;   # B2, D2
     }
@@ -278,9 +293,34 @@ sub exists_local {
        return 0;
 }
 
+# This function performs a password update, given the userid, borrowerid,
+# and digested password. It will verify that things are correct and return the
+# borrowers cardnumber. The idea is that it is used to keep the local
+# passwords in sync with the LDAP passwords.
+#
+#   $cardnum = _do_changepassword($userid, $borrowerid, $digest)
+#
+# Note: if the LDAP config has the update_password tag set to a false value,
+# then this will not update the password, it will simply return the cardnumber.
 sub _do_changepassword {
     my ($userid, $borrowerid, $password) = @_;
 
+    if ( exists( $ldap->{update_password} ) && !$ldap->{update_password} ) {
+
+        # We don't store the password in the database
+        my $sth = C4::Context->dbh->prepare(
+            'SELECT cardnumber FROM borrowers WHERE borrowernumber=?');
+        $sth->execute($borrowerid);
+        die "Unable to access borrowernumber "
+            . "with userid=$userid, "
+            . "borrowernumber=$borrowerid"
+          if !$sth->rows;
+        my ($cardnum) = $sth->fetchrow;
+        $sth = C4::Context->dbh->prepare(
+            'UPDATE borrowers SET password = null WHERE borrowernumber=?');
+        $sth->execute($borrowerid);
+        return $cardnum;
+    }
     my $digest = hash_password($password);
 
     $debug and print STDERR "changing local password for borrowernumber=$borrowerid to '$digest'\n";
@@ -314,8 +354,8 @@ sub update_local {
         ((map {$borrower->{$_}} @keys), $borrowerid)
     );
 
-    # MODIFY PASSWORD/LOGIN
-    _do_changepassword($userid, $borrowerid, $password);
+    # MODIFY PASSWORD/LOGIN if password was mapped
+    _do_changepassword($userid, $borrowerid, $password) if $borrower->{'password'};
 }
 
 1;
@@ -437,6 +477,11 @@ Example XML stanza for LDAP configuration in KOHA_CONF.
                                         password comparison, e.g., to use Active Directory -->
     <anonymous_bind>0</anonymous_bind> <!-- set to 1 if users should be searched using
                                             an anonymous bind, even when auth_by_bind is on -->
+    <principal_name>%s@my_domain.com</principal_name>
+                                   <!-- optional, for auth_by_bind: a printf format to make userPrincipalName from koha userid.
+                                        Not used with anonymous_bind. -->
+    <update_password>1</update_password> <!-- set to 0 if you don't want LDAP passwords
+                                              synced to the local database -->
     <mapping>                  <!-- match koha SQL field names to your LDAP record field names -->
       <firstname    is="givenname"      ></firstname>
       <surname      is="sn"             ></surname>
@@ -494,6 +539,14 @@ attribute that the server allows to be used for binding could be used.
 
 Currently, principal_name only operates when auth_by_bind is enabled.
 
+=head2 update_password
+
+If this tag is left out or set to a true value, then the user's LDAP password
+will be stored (hashed) in the local Koha database. If you don't want this
+to happen, then set the value of this to '0'. Note that if passwords are not
+stored locally, and the connection to the LDAP system fails, then the users
+will not be able to log in at all.
+
 =head2 Active Directory 
 
 The auth_by_bind and principal_name settings are recommended for Active Directory.