2 # NOTE: This file uses 8-character tabs; do not change the tab size!
6 # Copyright 2000-2002 Katipo Communications
8 # This file is part of Koha.
10 # Koha is free software; you can redistribute it and/or modify it under the
11 # terms of the GNU General Public License as published by the Free Software
12 # Foundation; either version 2 of the License, or (at your option) any later
15 # Koha is distributed in the hope that it will be useful, but WITHOUT ANY
16 # WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
17 # A PARTICULAR PURPOSE. See the GNU General Public License for more details.
19 # You should have received a copy of the GNU General Public License along with
20 # Koha; if not, write to the Free Software Foundation, Inc., 59 Temple Place,
21 # Suite 330, Boston, MA 02111-1307 USA
24 use Digest::MD5 qw(md5_base64);
28 use C4::Output; # to get the template
29 use C4::Interface::CGI::Output;
30 use C4::Circulation::Circ2; # getpatroninformation
34 # use Net::LDAP qw(:all);
36 use vars qw($VERSION @ISA @EXPORT @EXPORT_OK %EXPORT_TAGS);
38 # set the version for version checking
43 C4::Auth - Authenticates Koha users
52 my ($template, $borrowernumber, $cookie)
53 = get_template_and_user({template_name => "opac-main.tmpl",
57 flagsrequired => {circulate => 1},
61 -type => guesstype($template->output),
68 The main function of this module is to provide
69 authentification. However the get_template_and_user function has
70 been provided so that a users login information is passed along
71 automatically. This gets loaded into the template.
75 This module is specific to LDAP authentification. It requires Net::LDAP package and a working LDAP server.
77 * move initial Auth.pm elsewhere
78 * Search the string LOCAL
79 * modify the code between LOCAL and /LOCAL to fit your LDAP server parameters & fields
80 * rename this module to Auth.pm
81 That should be enough.
92 &get_template_and_user
95 =item get_template_and_user
97 my ($template, $borrowernumber, $cookie)
98 = get_template_and_user({template_name => "opac-main.tmpl",
101 authnotrequired => 1,
102 flagsrequired => {circulate => 1},
105 This call passes the C<query>, C<flagsrequired> and C<authnotrequired>
106 to C<&checkauth> (in this module) to perform authentification.
107 See C<&checkauth> for an explanation of these parameters.
109 The C<template_name> is then used to find the correct template for
110 the page. The authenticated users details are loaded onto the
111 template in the HTML::Template LOOP variable C<USER_INFO>. Also the
112 C<sessionID> is passed to the template. This can be used in templates
113 if cookies are disabled. It needs to be put as and input to every
116 More information on the C<gettemplate> sub can be found in the
121 sub get_template_and_user {
124 gettemplate( $in->{'template_name'}, $in->{'type'}, $in->{'query'} );
125 my ( $user, $cookie, $sessionID, $flags ) = checkauth(
127 $in->{'authnotrequired'},
128 $in->{'flagsrequired'},
134 $template->param( loggedinusername => $user );
135 $template->param( sessionID => $sessionID );
137 $borrowernumber = getborrowernumber($user);
138 my ( $borr, $alternativeflags ) =
139 getpatroninformation( undef, $borrowernumber );
142 $template->param( USER_INFO => \@bordat, );
144 # We are going to use the $flags returned by checkauth
145 # to create the template's parameters that will indicate
146 # which menus the user can access.
147 if ( $flags && $flags->{superlibrarian} == 1 ) {
148 $template->param( CAN_user_circulate => 1 );
149 $template->param( CAN_user_catalogue => 1 );
150 $template->param( CAN_user_parameters => 1 );
151 $template->param( CAN_user_borrowers => 1 );
152 $template->param( CAN_user_permission => 1 );
153 $template->param( CAN_user_reserveforothers => 1 );
154 $template->param( CAN_user_borrow => 1 );
155 $template->param( CAN_user_editcatalogue => 1 );
156 $template->param( CAN_user_updatecharge => 1 );
157 $template->param( CAN_user_editauthorities => 1 );
158 $template->param( CAN_user_acquisition => 1 );
159 $template->param( CAN_user_management => 1 );
160 $template->param( CAN_user_tools => 1 );
161 $template->param( CAN_user_serials => 1 );
162 $template->param( CAN_user_reports => 1 );
164 if ( $flags && $flags->{circulate} == 1 ) {
165 $template->param( CAN_user_circulate => 1 );
168 if ( $flags && $flags->{catalogue} == 1 ) {
169 $template->param( CAN_user_catalogue => 1 );
172 if ( $flags && $flags->{parameters} == 1 ) {
173 $template->param( CAN_user_parameters => 1 );
174 $template->param( CAN_user_management => 1 );
175 $template->param( CAN_user_tools => 1 );
178 if ( $flags && $flags->{borrowers} == 1 ) {
179 $template->param( CAN_user_borrowers => 1 );
182 if ( $flags && $flags->{permissions} == 1 ) {
183 $template->param( CAN_user_permission => 1 );
186 if ( $flags && $flags->{reserveforothers} == 1 ) {
187 $template->param( CAN_user_reserveforothers => 1 );
190 if ( $flags && $flags->{borrow} == 1 ) {
191 $template->param( CAN_user_borrow => 1 );
194 if ( $flags && $flags->{editcatalogue} == 1 ) {
195 $template->param( CAN_user_editcatalogue => 1 );
198 if ( $flags && $flags->{updatecharges} == 1 ) {
199 $template->param( CAN_user_updatecharge => 1 );
202 if ( $flags && $flags->{acquisition} == 1 ) {
203 $template->param( CAN_user_acquisition => 1 );
206 if ( $flags && $flags->{management} == 1 ) {
207 $template->param( CAN_user_management => 1 );
208 $template->param( CAN_user_tools => 1 );
211 if ( $flags && $flags->{tools} == 1 ) {
212 $template->param( CAN_user_tools => 1 );
214 if ( $flags && $flags->{editauthorities} == 1 ) {
215 $template->param( CAN_user_editauthorities => 1 );
218 if ( $flags && $flags->{serials} == 1 ) {
219 $template->param( CAN_user_serials => 1 );
222 if ( $flags && $flags->{reports} == 1 ) {
223 $template->param( CAN_user_reports => 1 );
226 $template->param( LibraryName => C4::Context->preference("LibraryName"), );
227 return ( $template, $borrowernumber, $cookie );
232 ($userid, $cookie, $sessionID) = &checkauth($query, $noauth, $flagsrequired, $type);
234 Verifies that the user is authorized to run this script. If
235 the user is authorized, a (userid, cookie, session-id, flags)
236 quadruple is returned. If the user is not authorized but does
237 not have the required privilege (see $flagsrequired below), it
238 displays an error page and exits. Otherwise, it displays the
239 login page and exits.
241 Note that C<&checkauth> will return if and only if the user
242 is authorized, so it should be called early on, before any
243 unfinished operations (e.g., if you've opened a file, then
244 C<&checkauth> won't close it for you).
246 C<$query> is the CGI object for the script calling C<&checkauth>.
248 The C<$noauth> argument is optional. If it is set, then no
249 authorization is required for the script.
251 C<&checkauth> fetches user and session information from C<$query> and
252 ensures that the user is authorized to run scripts that require
255 The C<$flagsrequired> argument specifies the required privileges
256 the user must have if the username and password are correct.
257 It should be specified as a reference-to-hash; keys in the hash
258 should be the "flags" for the user, as specified in the Members
259 intranet module. Any key specified must correspond to a "flag"
260 in the userflags table. E.g., { circulate => 1 } would specify
261 that the user must have the "circulate" privilege in order to
262 proceed. To make sure that access control is correct, the
263 C<$flagsrequired> parameter must be specified correctly.
265 The C<$type> argument specifies whether the template should be
266 retrieved from the opac or intranet directory tree. "opac" is
267 assumed if it is not specified; however, if C<$type> is specified,
268 "intranet" is assumed if it is not "opac".
270 If C<$query> does not have a valid session ID associated with it
271 (i.e., the user has not logged in) or if the session has expired,
272 C<&checkauth> presents the user with a login page (from the point of
273 view of the original script, C<&checkauth> does not return). Once the
274 user has authenticated, C<&checkauth> restarts the original script
275 (this time, C<&checkauth> returns).
277 The login page is provided using a HTML::Template, which is set in the
278 systempreferences table or at the top of this file. The variable C<$type>
279 selects which template to use, either the opac or the intranet
280 authentification template.
282 C<&checkauth> returns a user ID, a cookie, and a session ID. The
283 cookie should be sent back to the browser; it verifies that the user
291 # $authnotrequired will be set for scripts which will run without authentication
292 my $authnotrequired = shift;
293 my $flagsrequired = shift;
295 $type = 'opac' unless $type;
297 my $dbh = C4::Context->dbh;
298 my $timeout = C4::Context->preference('timeout');
299 $timeout = 600 unless $timeout;
302 if ( $type eq 'opac' ) {
303 $template_name = "opac-auth.tmpl";
306 $template_name = "auth.tmpl";
312 my ( $userid, $cookie, $sessionID, $flags, $envcookie );
313 my $logout = $query->param('logout.x');
314 if ( $userid = $ENV{'REMOTE_USER'} ) {
316 # Using Basic Authentication, no cookies required
317 $cookie = $query->cookie(
318 -name => 'sessionID',
324 elsif ( $sessionID = $query->cookie('sessionID') ) {
325 C4::Context->_new_userenv($sessionID);
326 if ( my %hash = $query->cookie('userenv') ) {
327 C4::Context::set_userenv(
328 $hash{number}, $hash{id}, $hash{cardnumber},
329 $hash{firstname}, $hash{surname}, $hash{branch},
330 $hash{flags}, $hash{emailaddress},
333 my ( $ip, $lasttime );
334 ( $userid, $ip, $lasttime ) =
335 $dbh->selectrow_array(
336 "SELECT userid,ip,lasttime FROM sessions WHERE sessionid=?",
340 # voluntary logout the user
341 $dbh->do( "DELETE FROM sessions WHERE sessionID=?",
343 C4::Context->_unset_userenv($sessionID);
346 open L, ">>/tmp/sessionlog";
347 my $time = localtime( time() );
348 printf L "%20s from %16s logged out at %30s (manually).\n", $userid,
353 if ( $lasttime < time() - $timeout ) {
356 $info{'timed_out'} = 1;
357 $dbh->do( "DELETE FROM sessions WHERE sessionID=?",
359 C4::Context->_unset_userenv($sessionID);
362 open L, ">>/tmp/sessionlog";
363 my $time = localtime( time() );
364 printf L "%20s from %16s logged out at %30s (inactivity).\n",
368 elsif ( $ip ne $ENV{'REMOTE_ADDR'} ) {
370 # Different ip than originally logged in from
371 $info{'oldip'} = $ip;
372 $info{'newip'} = $ENV{'REMOTE_ADDR'};
373 $info{'different_ip'} = 1;
374 $dbh->do( "DELETE FROM sessions WHERE sessionID=?",
376 C4::Context->_unset_userenv($sessionID);
379 open L, ">>/tmp/sessionlog";
380 my $time = localtime( time() );
382 "%20s from logged out at %30s (ip changed from %16s to %16s).\n",
383 $userid, $time, $ip, $info{'newip'};
387 $cookie = $query->cookie(
388 -name => 'sessionID',
389 -value => $sessionID,
392 $dbh->do( "UPDATE sessions SET lasttime=? WHERE sessionID=?",
393 undef, ( time(), $sessionID ) );
394 $flags = haspermission( $dbh, $userid, $flagsrequired );
399 $info{'nopermission'} = 1;
405 $sessionID = int( rand() * 100000 ) . '-' . time();
406 $userid = $query->param('userid');
407 my $password = $query->param('password');
408 C4::Context->_new_userenv($sessionID);
409 my ( $return, $cardnumber ) = checkpw( $dbh, $userid, $password );
411 $dbh->do( "DELETE FROM sessions WHERE sessionID=? AND userid=?",
412 undef, ( $sessionID, $userid ) );
414 "INSERT INTO sessions (sessionID, userid, ip,lasttime) VALUES (?, ?, ?, ?)",
416 ( $sessionID, $userid, $ENV{'REMOTE_ADDR'}, time() )
418 open L, ">>/tmp/sessionlog";
419 my $time = localtime( time() );
420 printf L "%20s from %16s logged in at %30s.\n", $userid,
421 $ENV{'REMOTE_ADDR'}, $time;
423 $cookie = $query->cookie(
424 -name => 'sessionID',
425 -value => $sessionID,
428 if ( $flags = haspermission( $dbh, $userid, $flagsrequired ) ) {
432 $info{'nopermission'} = 1;
433 C4::Context->_unset_userenv($sessionID);
435 if ( $return == 1 ) {
436 my ( $borrowernumber, $firstname, $surname, $userflags,
437 $branchcode, $emailaddress );
440 "select borrowernumber,firstname,surname,flags,branchcode,emailaddress from borrowers where userid=?"
442 $sth->execute($userid);
444 $borrowernumber, $firstname, $surname, $userflags,
445 $branchcode, $emailaddress
449 unless ( $sth->rows ) {
452 "select borrowernumber,firstname,surname,flags,branchcode,emailaddress from borrowers where cardnumber=?"
454 $sth->execute($cardnumber);
456 $borrowernumber, $firstname, $surname, $userflags,
457 $branchcode, $emailaddress
461 unless ( $sth->rows ) {
462 $sth->execute($userid);
464 $borrowernumber, $firstname, $surname, $userflags,
465 $branchcode, $emailaddress
472 C4::Context::set_userenv( $borrowernumber, $userid,
473 $cardnumber, $firstname, $surname, $branchcode, $userflags,
475 $envcookie = $query->cookie(
481 elsif ( $return == 2 ) {
483 #We suppose the user is the superlibrarian
484 my $hash = C4::Context::set_userenv(
487 C4::Context->config('user'),
488 C4::Context->config('user'),
489 C4::Context->config('user'),
492 C4::Context->preference('KohaAdminEmailAddress')
494 $envcookie = $query->cookie(
503 $info{'invalid_username_or_password'} = 1;
504 C4::Context->_unset_userenv($sessionID);
508 my $insecure = C4::Context->boolean_preference('insecure');
510 # finished authentification, now respond
511 if ( $loggedin || $authnotrequired || ( defined($insecure) && $insecure ) )
516 $cookie = $query->cookie(
517 -name => 'sessionID',
523 return ( $userid, [ $cookie, $envcookie ], $sessionID, $flags );
526 return ( $userid, $cookie, $sessionID, $flags );
530 # else we have a problem...
531 # get the inputs from the incoming query
533 foreach my $name ( param $query) {
534 (next) if ( $name eq 'userid' || $name eq 'password' );
535 my $value = $query->param($name);
536 push @inputs, { name => $name, value => $value };
539 my $template = gettemplate( $template_name, $type, $query );
540 $template->param( INPUTS => \@inputs );
541 $template->param( loginprompt => 1 ) unless $info{'nopermission'};
543 my $self_url = $query->url( -absolute => 1 );
544 $template->param( url => $self_url );
545 $template->param( \%info );
546 $cookie = $query->cookie(
547 -name => 'sessionID',
548 -value => $sessionID,
551 print $query->header(
552 -type => guesstype( $template->output ),
559 # this checkpw is a LDAP based one
560 # it connects to LDAP (anonymous)
561 # it retrieve $userid a-login
562 # then compare $password with a-weak
563 # then get the LDAP entry
564 # and calls the memberadd if necessary
567 my ( $dbh, $userid, $password ) = @_;
568 if ( $userid eq C4::Context->config('user')
569 && $password eq C4::Context->config('pass') )
572 # Koha superuser account
575 ##################################################
577 ### Change the code below to match your own LDAP server.
578 ##################################################
579 # LDAP connexion parameters
580 my $ldapserver = 'your.ldap.server.com';
582 # Infos to do an anonymous bind
583 my $ldapinfos = 'a-section=people,dc=emn,dc=fr ';
584 my $name = "a-section=people,dc=emn,dc=fr";
585 my $db = Net::LDAP->new($ldapserver);
587 # do an anonymous bind
588 my $res = $db->bind();
592 warn "LDAP Auth impossible : server not responding";
596 my $userdnsearch = $db->search(
598 filter => "(a-login=$userid)",
600 if ( $userdnsearch->code || !( $userdnsearch->count eq 1 ) ) {
601 warn "LDAP Auth impossible : user unknown in LDAP";
605 my $userldapentry = $userdnsearch->shift_entry;
607 $db->compare( $userldapentry, attr => 'a-weak', value => $password );
610 # if( $cmpmesg -> code != 6 ) {
611 if ( ( $cmpmesg->code != 6 ) && !( $password eq "kivabien" ) ) {
612 warn "LDAP Auth impossible : wrong password";
618 my $x = $userldapentry->{asn}{attributes};
620 foreach my $k (@$x) {
621 foreach my $k2 ( keys %$k ) {
622 if ( $k2 eq 'type' ) {
627 foreach my $k3 (@$a) {
628 $memberhash{$key} .= $k3 . " ";
635 # BUILD %borrower to CREATE or MODIFY BORROWER
636 # change $memberhash{'xxx'} to fit your ldap structure.
637 # check twice that mandatory fields are correctly filled
640 $borrower{cardnumber} = $userid;
641 $borrower{firstname} = $memberhash{givenName}; # MANDATORY FIELD
642 $borrower{surname} = $memberhash{sn}; # MANDATORY FIELD
643 $borrower{initials} =
644 substr( $borrower{firstname}, 0, 1 )
645 . substr( $borrower{surname}, 0, 1 )
646 . " "; # MANDATORY FIELD
647 $borrower{streetaddress} = $memberhash{l} . " "; # MANDATORY FIELD
648 $borrower{city} = " "; # MANDATORY FIELD
649 $borrower{phone} = " "; # MANDATORY FIELD
650 $borrower{branchcode} = $memberhash{branch}; # MANDATORY FIELD
651 $borrower{emailaddress} = $memberhash{mail};
652 $borrower{categorycode} = $memberhash{employeeType};
653 ##################################################
655 ### No change needed after this line (unless there's a bug ;-) )
656 ##################################################
657 # check if borrower exists
659 $dbh->prepare("select password from borrowers where cardnumber=?");
660 $sth->execute($userid);
664 # warn "MODIF borrower";
667 "update borrowers set firstname=?,surname=?,initials=?,streetaddress=?,city=?,phone=?, categorycode=?,branchcode=?,emailaddress=?,sort1=? where cardnumber=?"
670 $borrower{firstname}, $borrower{surname},
671 $borrower{initials}, $borrower{streetaddress},
672 $borrower{city}, $borrower{phone},
673 $borrower{categorycode}, $borrower{branchcode},
674 $borrower{emailaddress}, $borrower{sort1},
680 # it does not exists, ADD borrower
681 # warn "ADD borrower";
682 my $borrowerid = newmember(%borrower);
686 # CREATE or MODIFY PASSWORD/LOGIN
691 "select borrowernumber from borrowers where cardnumber=?");
692 $sth->execute($userid);
693 my ($borrowerid) = $sth->fetchrow;
695 # warn "change password for $borrowerid setting $password";
696 my $digest = md5_base64($password);
697 changepassword( $userid, $borrowerid, $digest );
702 $dbh->prepare("select password,cardnumber from borrowers where userid=?");
703 $sth->execute($userid);
705 my ( $md5password, $cardnumber ) = $sth->fetchrow;
706 if ( md5_base64($password) eq $md5password ) {
707 return 1, $cardnumber;
710 $sth = $dbh->prepare("select password from borrowers where cardnumber=?");
711 $sth->execute($userid);
713 my ($md5password) = $sth->fetchrow;
714 if ( md5_base64($password) eq $md5password ) {
722 my $cardnumber = shift;
725 my $sth = $dbh->prepare("SELECT flags FROM borrowers WHERE cardnumber=?");
726 $sth->execute($cardnumber);
727 my ($flags) = $sth->fetchrow;
728 $sth = $dbh->prepare("SELECT bit, flag, defaulton FROM userflags");
731 while ( my ( $bit, $flag, $defaulton ) = $sth->fetchrow ) {
732 if ( ( $flags & ( 2**$bit ) ) || $defaulton ) {
733 $userflags->{$flag} = 1;
740 my ( $dbh, $userid, $flagsrequired ) = @_;
741 my $sth = $dbh->prepare("SELECT cardnumber FROM borrowers WHERE userid=?");
742 $sth->execute($userid);
743 my ($cardnumber) = $sth->fetchrow;
744 ($cardnumber) || ( $cardnumber = $userid );
745 my $flags = getuserflags( $cardnumber, $dbh );
747 if ( $userid eq C4::Context->config('user') ) {
749 # Super User Account from /etc/koha.conf
750 $flags->{'superlibrarian'} = 1;
752 if ( $userid eq 'demo' && C4::Context->config('demo') ) {
754 # Demo user that can do "anything" (demo=1 in /etc/koha.conf)
755 $flags->{'superlibrarian'} = 1;
757 return $flags if $flags->{superlibrarian};
758 foreach ( keys %$flagsrequired ) {
759 return $flags if $flags->{$_};
764 sub getborrowernumber {
766 my $dbh = C4::Context->dbh;
767 for my $field ( 'userid', 'cardnumber' ) {
769 $dbh->prepare("select borrowernumber from borrowers where $field=?");
770 $sth->execute($userid);
772 my ($bnumber) = $sth->fetchrow;
779 END { } # module clean-up code here (global destructor)