X-Git-Url: http://git.rot13.org/?a=blobdiff_plain;f=C4%2FAuth_with_ldap.pm;h=d9841a88a03f86162505e581bfac06f196edc5fd;hb=refs%2Fheads%2Fkoha_ffzg;hp=6000e76851b800cb97b706a8453e8e4461b2f802;hpb=9da4c80b0149a88c14316099515ede2ebdaf7646;p=koha.git diff --git a/C4/Auth_with_ldap.pm b/C4/Auth_with_ldap.pm index 6000e76851..d9841a88a0 100644 --- a/C4/Auth_with_ldap.pm +++ b/C4/Auth_with_ldap.pm @@ -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 . use strict; #use warnings; FIXME - Bug 2505 @@ -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 ); @@ -88,16 +96,18 @@ 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); 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; } @@ -113,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; @@ -143,8 +154,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 ( $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. + 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} ) ) @@ -153,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 0; - } - } + # 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. @@ -188,31 +221,25 @@ sub checkpw_ldap { return(1, $cardnumber, $local_userid); } } elsif ($config{replicate}) { # A2, C2 - $borrowernumber = AddMember(%borrower) or die "AddMember failed"; + Koha::Patron->new( \%borrower )->store; 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). @@ -250,6 +277,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 = ?"); @@ -280,13 +315,38 @@ 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) = @_; - my $digest = hash_password($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"; - 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; @@ -301,7 +361,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) . @@ -316,8 +385,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; @@ -442,7 +511,8 @@ Example XML stanza for LDAP configuration in KOHA_CONF. %s@my_domain.com - + 1 @@ -500,6 +570,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.