From 24aa2db9fae35460f0e7182bd07d7503d04a09e5 Mon Sep 17 00:00:00 2001 From: Shi Yao Wang Date: Fri, 9 Dec 2022 16:08:24 -0500 Subject: [PATCH] Bug 11808: Add the SearchCardnumberWithLDAP system preference Add option to search patrons on ldap server when not found in local database and import it Test plan: 0. You need to have an ldap server containing patron informations configured in Koha. 1. Apply patch and update database. 2. Check that the new system preference SearchCardnumberWithLDAP has been added and set as 'never'. 3. Choose a patron who exists in your ldap server but not in Koha. (you can delete an existing patron from Koha if needed) 4. Search for the patron by it's cardnumber in the following locations. Do not choose from the dropdown list, use the submit button. - circ/circulation.pl - circ/article-request.pl - reserve/request.pl - members/member.pl both at the top and in the filter box - any page using common/patron_search.tt, for example adding a guarantor with the members/guarantor_search.pl popup => Should return cardnumber/patron not found or no results 5. Change SearchCardnumberWithLDAP to 'if not found locally'. 6. Repeat step 4. => The patron should be added to Koha. 7. Edit the patron and change any of the existing information. 8. Repeat step 4. => The patron has not been updated (the change made in step 7 is still there) 9. Change SearchCardnumberWithLDAP to 'always'. 10. Repeat step 4. => The patron has been updated. Change made in step 7 has been rewritten and patron informations is the same as in step 6. 11. Try searching for a patron who doesn't exist in Koha and has a nonexistant branch on the LDAP server. An error message should appear accompanying the absence of results. --- C4/Auth_with_ldap.pm | 94 +++++++++++++++++++ circ/circulation.pl | 14 +++ circ/request-article.pl | 23 ++++- .../data/mysql/atomicupdate/bug_11808.pl | 14 +++ installer/data/mysql/mandatory/sysprefs.sql | 1 + .../en/modules/admin/preferences/patrons.pref | 7 ++ .../prog/en/modules/circ/circulation.tt | 5 + .../prog/en/modules/circ/request-article.tt | 6 ++ .../prog/en/modules/members/member.tt | 6 ++ .../prog/en/modules/reserve/request.tt | 6 ++ members/member.pl | 14 +++ reserve/request.pl | 14 +++ 12 files changed, 200 insertions(+), 4 deletions(-) create mode 100755 installer/data/mysql/atomicupdate/bug_11808.pl diff --git a/C4/Auth_with_ldap.pm b/C4/Auth_with_ldap.pm index f13dbdcf8e..6a4e83668e 100644 --- a/C4/Auth_with_ldap.pm +++ b/C4/Auth_with_ldap.pm @@ -28,6 +28,7 @@ use Koha::Patrons; use Koha::AuthUtils qw( hash_password ); use Net::LDAP; use Net::LDAP::Filter; +use Try::Tiny; our (@ISA, @EXPORT_OK); BEGIN { @@ -433,6 +434,99 @@ sub update_local { _do_changepassword($userid, $borrowerid, $password) if exists( $borrower->{'password'} ); } +# Subroutine to update a user with LDAP using its cardnumber. +sub updateborrower_ldap { + my $patron = shift; + my $cardnumber = $patron->cardnumber; + + return unless ($config{update}); + + my @hosts = split(',', $prefhost); + my $db = Net::LDAP->new(\@hosts, timeout => ($ldap->{timeout} || 30)) or return; + + my $cardnumber_field = $mapping{cardnumber}->{is} or die ldapserver_error("mapping for 'cardnumber'"); + my $filter = Net::LDAP::Filter->new("$cardnumber_field=$cardnumber") or die "Failed to create new Net::LDAP::Filter"; + 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; + } + my $search = $db->search( + base => $base, + filter => $filter, + ); + + # Search error, return undef + return (undef, $search->error) if $search->code; + + my $count = $search->count; + if ($count == 1) { + # User was found, update! + my %borrower = ldap_entry_2_hash($search->shift_entry,$cardnumber); + + &update_local($patron->userid, $patron->password, $patron->borrowernumber, \%borrower); + + my $updated_patron = Koha::Patrons->find( $patron->borrowernumber ); + return $updated_patron; + } + else { + return; + } + +} + +# Subroutine to search for a user by its cardnumber through LDAP. +# The user is added locally if found. (We suppose this user doesn't already exist if this subroutine is called) +sub findcardnumber_ldap { + my $cardnumber = shift; + + return unless ($config{replicate}); + + my @hosts = split(',', $prefhost); + my $db = Net::LDAP->new(\@hosts, timeout => ($ldap->{timeout} || 30)) or return; + + my $cardnumber_field = $mapping{cardnumber}->{is} or die ldapserver_error("mapping for 'cardnumber'"); + my $filter = Net::LDAP::Filter->new("$cardnumber_field=$cardnumber") or die "Failed to create new Net::LDAP::Filter"; + 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; + } + my $search = $db->search( + base => $base, + filter => $filter, + ); + + # Search error, return undef + return (undef, $search->error) if $search->code; + + my $count = $search->count; + if ($count == 1) { + # User was found, add it! + my %borrower = ldap_entry_2_hash($search->shift_entry,$cardnumber); + + # Generate a random password to keep the user secure if no password was mapped + my @chars = ( "A" .. "Z", "a" .. "z", 0 .. 9, qw(! @ $ % ^ & *) ); + $borrower{'password'} = join("", @chars[ map { rand @chars } ( 1 .. 30 ) ]) unless $borrower{'password'}; + + try { + my @columns = Koha::Patrons->columns; + my $patron = Koha::Patron->new( + { + map { exists( $borrower{$_} ) ? ( $_ => $borrower{$_} ) : () } @columns + } + )->store; + return $patron; + } catch { + return (undef, $_->{msg}); + }; + } + else { + return; + } + +} + 1; __END__ diff --git a/circ/circulation.pl b/circ/circulation.pl index b7270bfe56..ed61874d89 100755 --- a/circ/circulation.pl +++ b/circ/circulation.pl @@ -220,6 +220,20 @@ my $message; if ($findborrower) { Koha::Plugins->call( 'patron_barcode_transform', \$findborrower ); my $patron = Koha::Patrons->find( { cardnumber => $findborrower } ); + + # search cardnumber on LDAP server + if ( C4::Context->config('useldapserver') + and ( my $search_cardnumber = C4::Context->preference("SearchCardnumberWithLDAP") ) ne "never") + { + my $ldap_error; + if ( $patron ) { + ($patron, $ldap_error) = C4::Auth_with_ldap::updateborrower_ldap($patron) if $search_cardnumber eq "always"; + } else { + ($patron, $ldap_error) = C4::Auth_with_ldap::findcardnumber_ldap($findborrower); + } + $template->param(ldap_error => $ldap_error) if $ldap_error; + } + if ( $patron ) { $borrowernumber = $patron->borrowernumber; } else { diff --git a/circ/request-article.pl b/circ/request-article.pl index a9724749dc..d1e1a83dce 100755 --- a/circ/request-article.pl +++ b/circ/request-article.pl @@ -51,10 +51,25 @@ my $biblio = Koha::Biblios->find($biblionumber); output_and_exit( $cgi, $cookie, $template, 'unknown_biblio') unless $biblio; -my $patron = - $patron_id ? Koha::Patrons->find($patron_id) - : $patron_cardnumber ? Koha::Patrons->find( { cardnumber => $patron_cardnumber } ) - : undef; +my $patron = undef; +if ( $patron_id ) { + $patron = Koha::Patrons->find($patron_id); +} elsif ( $patron_cardnumber ) { + $patron = Koha::Patrons->find( { cardnumber => $patron_cardnumber } ); + + # search cardnumber on LDAP server + if ( C4::Context->config('useldapserver') + and ( my $search_cardnumber = C4::Context->preference("SearchCardnumberWithLDAP") ) ne "never") + { + my $ldap_error; + if ( $patron ) { + ($patron, $ldap_error) = C4::Auth_with_ldap::updateborrower_ldap($patron) if $search_cardnumber eq "always"; + } else { + ($patron, $ldap_error) = C4::Auth_with_ldap::findcardnumber_ldap($patron_cardnumber); + } + $template->param(ldap_error => $ldap_error) if $ldap_error; + } +} if ( $action eq 'create' ) { my $borrowernumber = $cgi->param('borrowernumber'); diff --git a/installer/data/mysql/atomicupdate/bug_11808.pl b/installer/data/mysql/atomicupdate/bug_11808.pl new file mode 100755 index 0000000000..b74649d264 --- /dev/null +++ b/installer/data/mysql/atomicupdate/bug_11808.pl @@ -0,0 +1,14 @@ +use Modern::Perl; + +return { + bug_number => "11808", + description => "Search cardnumber on the LDAP server if one is configured and add/update user.", + up => sub { + my ($args) = @_; + my ($dbh, $out) = @$args{qw(dbh out)}; + $dbh->do(q{ + INSERT INTO systempreferences (variable,value,explanation,options,type) + VALUES('SearchCardnumberWithLDAP', 'never', 'Search for a cardnumber with LDAP when searching for a patron that does not exist locally. If the cardnumber is found via LDAP, the user is added or updated.', 'always|not_found|never', 'Choice'); + }); + }, +}; diff --git a/installer/data/mysql/mandatory/sysprefs.sql b/installer/data/mysql/mandatory/sysprefs.sql index beebd27a6f..16d8f6e30e 100644 --- a/installer/data/mysql/mandatory/sysprefs.sql +++ b/installer/data/mysql/mandatory/sysprefs.sql @@ -636,6 +636,7 @@ INSERT INTO systempreferences ( `variable`, `value`, `options`, `explanation`, ` ('SCOMainUserBlock','','70|10','Add a block of HTML that will display on the self checkout screen','Textarea'), ('SCOUserCSS','',NULL,'Add CSS to be included in the SCO module in an embedded