3 # NOTE: This file uses 8-character tabs; do not change the tab size!
7 # Copyright 2000-2002 Katipo Communications
9 # This file is part of Koha.
11 # Koha is free software; you can redistribute it and/or modify it under the
12 # terms of the GNU General Public License as published by the Free Software
13 # Foundation; either version 2 of the License, or (at your option) any later
16 # Koha is distributed in the hope that it will be useful, but WITHOUT ANY
17 # WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
18 # A PARTICULAR PURPOSE. See the GNU General Public License for more details.
20 # You should have received a copy of the GNU General Public License along with
21 # Koha; if not, write to the Free Software Foundation, Inc., 59 Temple Place,
22 # Suite 330, Boston, MA 02111-1307 USA
25 use Digest::MD5 qw(md5_base64);
30 use C4::Output; # to get the template
33 use C4::Branch; # GetBranches
34 use C4::VirtualShelves 3.02 qw(GetShelvesSummary);
37 use vars qw($VERSION @ISA @EXPORT @EXPORT_OK %EXPORT_TAGS $debug $ldap);
40 $VERSION = 3.02; # set version for version checking
41 $debug = $ENV{DEBUG} || 0 ;
43 @EXPORT = qw(&checkauth &get_template_and_user);
44 @EXPORT_OK = qw(&check_api_auth &get_session &check_cookie_auth &checkpw &get_all_subpermissions &get_user_subpermissions);
45 %EXPORT_TAGS = (EditPermissions => [qw(get_all_subpermissions get_user_subpermissions)]);
46 $ldap = C4::Context->config('useldapserver') || 0;
48 require C4::Auth_with_ldap; # no import
49 import C4::Auth_with_ldap qw(checkpw_ldap);
55 C4::Auth - Authenticates Koha users
64 my ($template, $borrowernumber, $cookie)
65 = get_template_and_user(
67 template_name => "opac-main.tmpl",
71 flagsrequired => {borrow => 1, catalogue => '*', tools => 'import_patrons' },
83 The main function of this module is to provide
84 authentification. However the get_template_and_user function has
85 been provided so that a users login information is passed along
86 automatically. This gets loaded into the template.
92 =item get_template_and_user
94 my ($template, $borrowernumber, $cookie)
95 = get_template_and_user(
97 template_name => "opac-main.tmpl",
100 authnotrequired => 1,
101 flagsrequired => {borrow => 1, catalogue => '*', tools => 'import_patrons' },
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'},
130 ) unless ($in->{'template_name'}=~/maintenance/);
133 my $insecure = C4::Context->preference('insecure');
134 if ($user or $insecure) {
136 # load the template variables for stylesheets and JavaScript
137 $template->param( css_libs => $in->{'css_libs'} );
138 $template->param( css_module => $in->{'css_module'} );
139 $template->param( css_page => $in->{'css_page'} );
140 $template->param( css_widgets => $in->{'css_widgets'} );
142 $template->param( js_libs => $in->{'js_libs'} );
143 $template->param( js_module => $in->{'js_module'} );
144 $template->param( js_page => $in->{'js_page'} );
145 $template->param( js_widgets => $in->{'js_widgets'} );
148 $template->param( loggedinusername => $user );
149 $template->param( sessionID => $sessionID );
151 if ($shelves = C4::Context->get_shelves_userenv()) {
152 $template->param( barshelves => scalar (@$shelves));
153 $template->param( barshelvesloop => $shelves);
156 $borrowernumber = getborrowernumber($user);
157 my ( $borr, $alternativeflags ) =
158 GetMemberDetails( $borrowernumber );
161 $template->param( "USER_INFO" => \@bordat );
163 my $all_perms = get_all_subpermissions();
165 my @flagroots = qw(circulate catalogue parameters borrowers permissions reserveforothers borrow
166 editcatalogue updatecharges management tools editauthorities serials reports);
167 # We are going to use the $flags returned by checkauth
168 # to create the template's parameters that will indicate
169 # which menus the user can access.
170 if (( $flags && $flags->{superlibrarian}==1) or $insecure==1) {
171 $template->param( CAN_user_circulate => 1 );
172 $template->param( CAN_user_catalogue => 1 );
173 $template->param( CAN_user_parameters => 1 );
174 $template->param( CAN_user_borrowers => 1 );
175 $template->param( CAN_user_permissions => 1 );
176 $template->param( CAN_user_reserveforothers => 1 );
177 $template->param( CAN_user_borrow => 1 );
178 $template->param( CAN_user_editcatalogue => 1 );
179 $template->param( CAN_user_updatecharges => 1 );
180 $template->param( CAN_user_acquisition => 1 );
181 $template->param( CAN_user_management => 1 );
182 $template->param( CAN_user_tools => 1 );
183 $template->param( CAN_user_editauthorities => 1 );
184 $template->param( CAN_user_serials => 1 );
185 $template->param( CAN_user_reports => 1 );
186 $template->param( CAN_user_staffaccess => 1 );
187 foreach my $module (keys %$all_perms) {
188 foreach my $subperm (keys %{ $all_perms->{$module} }) {
189 $template->param( "CAN_user_${module}_${subperm}" => 1 );
194 if (C4::Context->preference('GranularPermissions')) {
196 foreach my $module (keys %$all_perms) {
197 if ( $flags->{$module} == 1) {
198 foreach my $subperm (keys %{ $all_perms->{$module} }) {
199 $template->param( "CAN_user_${module}_${subperm}" => 1 );
201 } elsif ( ref($flags->{$module}) ) {
202 foreach my $subperm (keys %{ $flags->{$module} } ) {
203 $template->param( "CAN_user_${module}_${subperm}" => 1 );
209 foreach my $module (keys %$all_perms) {
210 foreach my $subperm (keys %{ $all_perms->{$module} }) {
211 $template->param( "CAN_user_${module}_${subperm}" => 1 );
217 foreach my $module (keys %$flags) {
218 if ( $flags->{$module} == 1 or ref($flags->{$module}) ) {
219 $template->param( "CAN_user_$module" => 1 );
220 if ($module eq "parameters") {
221 $template->param( CAN_user_management => 1 );
228 if ( $in->{'type'} eq "intranet" ) {
230 intranetcolorstylesheet => C4::Context->preference("intranetcolorstylesheet"),
231 intranetstylesheet => C4::Context->preference("intranetstylesheet"),
232 IntranetNav => C4::Context->preference("IntranetNav"),
233 intranetuserjs => C4::Context->preference("intranetuserjs"),
234 TemplateEncoding => C4::Context->preference("TemplateEncoding"),
235 AmazonContent => C4::Context->preference("AmazonContent"),
236 LibraryName => C4::Context->preference("LibraryName"),
237 LoginBranchcode => (C4::Context->userenv?C4::Context->userenv->{"branch"}:"insecure"),
238 LoginBranchname => (C4::Context->userenv?C4::Context->userenv->{"branchname"}:"insecure"),
239 LoginFirstname => (C4::Context->userenv?C4::Context->userenv->{"firstname"}:"Bel"),
240 LoginSurname => C4::Context->userenv?C4::Context->userenv->{"surname"}:"Inconnu",
241 AutoLocation => C4::Context->preference("AutoLocation"),
242 hide_marc => C4::Context->preference("hide_marc"),
243 patronimages => C4::Context->preference("patronimages"),
244 "BiblioDefaultView".C4::Context->preference("IntranetBiblioDefaultView") => 1,
245 advancedMARCEditor => C4::Context->preference("advancedMARCEditor"),
246 suggestion => C4::Context->preference("suggestion"),
247 virtualshelves => C4::Context->preference("virtualshelves"),
248 LibraryName => C4::Context->preference("LibraryName"),
249 KohaAdminEmailAddress => "" . C4::Context->preference("KohaAdminEmailAddress"),
250 IntranetmainUserblock => C4::Context->preference("IntranetmainUserblock"),
251 IndependantBranches => C4::Context->preference("IndependantBranches"),
252 CircAutocompl => C4::Context->preference("CircAutocompl"),
253 yuipath => C4::Context->preference("yuipath"),
254 FRBRizeEditions => C4::Context->preference("FRBRizeEditions"),
255 AmazonSimilarItems => C4::Context->preference("AmazonSimilarItems"),
256 'item-level_itypes' => C4::Context->preference('item-level_itypes'),
257 canreservefromotherbranches => C4::Context->preference('canreservefromotherbranches'),
258 intranetreadinghistory => C4::Context->preference("intranetreadinghistory"),
259 noItemTypeImages => C4::Context->preference("noItemTypeImages"),
260 singleBranchMode => C4::Context->preference("singleBranchMode"),
261 TagsEnabled => C4::Context->preference("TagsEnabled"),
265 warn "template type should be OPAC, here it is=[" . $in->{'type'} . "]" unless ( $in->{'type'} eq 'opac' );
266 my $LibraryNameTitle = C4::Context->preference("LibraryName");
267 $LibraryNameTitle =~ s/<(?:\/?)(?:br|p)\s*(?:\/?)>/ /sgi;
268 $LibraryNameTitle =~ s/<(?:[^<>'"]|'(?:[^']*)'|"(?:[^"]*)")*>//sg;
270 KohaAdminEmailAddress => "" . C4::Context->preference("KohaAdminEmailAddress"),
271 AnonSuggestions => "" . C4::Context->preference("AnonSuggestions"),
272 suggestion => "" . C4::Context->preference("suggestion"),
273 OPACViewOthersSuggestions => "" . C4::Context->preference("OPACViewOthersSuggestions"),
274 virtualshelves => "" . C4::Context->preference("virtualshelves"),
275 OpacNav => "" . C4::Context->preference("OpacNav"),
276 opacheader => "" . C4::Context->preference("opacheader"),
277 opaccredits => "" . C4::Context->preference("opaccredits"),
278 opacsmallimage => "" . C4::Context->preference("opacsmallimage"),
279 opaclargeimage => "" . C4::Context->preference("opaclargeimage"),
280 opaclayoutstylesheet => "". C4::Context->preference("opaclayoutstylesheet"),
281 opaccolorstylesheet => "". C4::Context->preference("opaccolorstylesheet"),
282 OPACUserCSS => "". C4::Context->preference("OPACUserCSS"),
283 opaclanguagesdisplay => "". C4::Context->preference("opaclanguagesdisplay"),
284 opacuserlogin => "" . C4::Context->preference("opacuserlogin"),
285 OpacMainUserBlock => "" . C4::Context->preference("OpacMainUserBlock"),
286 OPACURLOpenInNewWindow => "" . C4::Context->preference("OPACURLOpenInNewWindow"),
287 opacbookbag => "" . C4::Context->preference("opacbookbag"),
288 TemplateEncoding => "". C4::Context->preference("TemplateEncoding"),
289 AmazonContent => "" . C4::Context->preference("AmazonContent"),
290 OPACShelfBrowser => "". C4::Context->preference("OPACShelfBrowser"),
291 OPACAmazonSimilarItems => "" . C4::Context->preference("OPACAmazonSimilarItems"),
292 LibraryName => "" . C4::Context->preference("LibraryName"),
293 LibraryNameTitle => "" . $LibraryNameTitle,
294 LoginBranchcode => (C4::Context->userenv?C4::Context->userenv->{"branch"}:"insecure"),
295 LoginBranchname => C4::Context->userenv?C4::Context->userenv->{"branchname"}:"",
296 LoginFirstname => (C4::Context->userenv?C4::Context->userenv->{"firstname"}:"Bel"),
297 LoginSurname => C4::Context->userenv?C4::Context->userenv->{"surname"}:"Inconnu",
298 OpacPasswordChange => C4::Context->preference("OpacPasswordChange"),
299 opacreadinghistory => C4::Context->preference("opacreadinghistory"),
300 opacuserjs => C4::Context->preference("opacuserjs"),
301 OpacCloud => C4::Context->preference("OpacCloud"),
302 OpacTopissue => C4::Context->preference("OpacTopissue"),
303 OpacAuthorities => C4::Context->preference("OpacAuthorities"),
304 OpacBrowser => C4::Context->preference("OpacBrowser"),
305 RequestOnOpac => C4::Context->preference("RequestOnOpac"),
306 OPACItemHolds => C4::Context->preference("OPACItemHolds"),
307 reviewson => C4::Context->preference("reviewson"),
308 hide_marc => C4::Context->preference("hide_marc"),
309 patronimages => C4::Context->preference("patronimages"),
310 hidelostitems => C4::Context->preference("hidelostitems"),
311 mylibraryfirst => C4::Context->preference("SearchMyLibraryFirst"),
312 "BiblioDefaultView".C4::Context->preference("BiblioDefaultView") => 1,
313 OPACFRBRizeEditions => C4::Context->preference("OPACFRBRizeEditions"),
314 'item-level_itypes' => C4::Context->preference('item-level_itypes'),
315 'Version' => C4::Context->preference('Version'),
316 yuipath => C4::Context->preference("yuipath"),
317 singleBranchMode => C4::Context->preference("singleBranchMode"),
318 XSLTResultsDisplay => C4::Context->preference("XSLTResultsDisplay"),
319 XSLTDetailsDisplay => C4::Context->preference("XSLTDetailsDisplay"),
320 TagsEnabled => C4::Context->preference("TagsEnabled"),
323 $template->param(listloop=>[{shelfname=>"Freelist", shelfnumber=>110}]);
324 return ( $template, $borrowernumber, $cookie, $flags);
329 ($userid, $cookie, $sessionID) = &checkauth($query, $noauth, $flagsrequired, $type);
331 Verifies that the user is authorized to run this script. If
332 the user is authorized, a (userid, cookie, session-id, flags)
333 quadruple is returned. If the user is not authorized but does
334 not have the required privilege (see $flagsrequired below), it
335 displays an error page and exits. Otherwise, it displays the
336 login page and exits.
338 Note that C<&checkauth> will return if and only if the user
339 is authorized, so it should be called early on, before any
340 unfinished operations (e.g., if you've opened a file, then
341 C<&checkauth> won't close it for you).
343 C<$query> is the CGI object for the script calling C<&checkauth>.
345 The C<$noauth> argument is optional. If it is set, then no
346 authorization is required for the script.
348 C<&checkauth> fetches user and session information from C<$query> and
349 ensures that the user is authorized to run scripts that require
352 The C<$flagsrequired> argument specifies the required privileges
353 the user must have if the username and password are correct.
354 It should be specified as a reference-to-hash; keys in the hash
355 should be the "flags" for the user, as specified in the Members
356 intranet module. Any key specified must correspond to a "flag"
357 in the userflags table. E.g., { circulate => 1 } would specify
358 that the user must have the "circulate" privilege in order to
359 proceed. To make sure that access control is correct, the
360 C<$flagsrequired> parameter must be specified correctly.
362 If the GranularPermissions system preference is ON, the
363 value of each key in the C<flagsrequired> hash takes on an additional
368 The user must have access to all subfunctions of the module
369 specified by the hash key.
373 The user must have access to at least one subfunction of the module
374 specified by the hash key.
376 =item specific permission, e.g., 'export_catalog'
378 The user must have access to the specific subfunction list, which
379 must correspond to a row in the permissions table.
381 The C<$type> argument specifies whether the template should be
382 retrieved from the opac or intranet directory tree. "opac" is
383 assumed if it is not specified; however, if C<$type> is specified,
384 "intranet" is assumed if it is not "opac".
386 If C<$query> does not have a valid session ID associated with it
387 (i.e., the user has not logged in) or if the session has expired,
388 C<&checkauth> presents the user with a login page (from the point of
389 view of the original script, C<&checkauth> does not return). Once the
390 user has authenticated, C<&checkauth> restarts the original script
391 (this time, C<&checkauth> returns).
393 The login page is provided using a HTML::Template, which is set in the
394 systempreferences table or at the top of this file. The variable C<$type>
395 selects which template to use, either the opac or the intranet
396 authentification template.
398 C<&checkauth> returns a user ID, a cookie, and a session ID. The
399 cookie should be sent back to the browser; it verifies that the user
404 sub _version_check ($$) {
408 # If Version syspref is unavailable, it means Koha is beeing installed,
409 # and so we must redirect to OPAC maintenance page or to the WebInstaller
410 #warn "about to check version";
411 unless ($version = C4::Context->preference('Version')) { # assignment, not comparison
412 if ($type ne 'opac') {
413 warn "Install required, redirecting to Installer";
414 print $query->redirect("/cgi-bin/koha/installer/install.pl");
417 warn "OPAC Install required, redirecting to maintenance";
418 print $query->redirect("/cgi-bin/koha/maintenance.pl");
423 # check that database and koha version are the same
424 # there is no DB version, it's a fresh install,
425 # go to web installer
426 # there is a DB version, compare it to the code version
427 my $kohaversion=C4::Context::KOHAVERSION;
428 # remove the 3 last . to have a Perl number
429 $kohaversion =~ s/(.*\..*)\.(.*)\.(.*)/$1$2$3/;
430 $debug and print STDERR "kohaversion : $kohaversion\n";
431 if ($version < $kohaversion){
432 my $warning = "Database update needed, redirecting to %s. Database is $version and Koha is $kohaversion";
433 if ($type ne 'opac'){
434 warn sprintf($warning, 'Installer');
435 print $query->redirect("/cgi-bin/koha/installer/install.pl?step=3");
437 warn sprintf("OPAC: " . $warning, 'maintenance');
438 print $query->redirect("/cgi-bin/koha/maintenance.pl");
446 open L, ">>/tmp/sessionlog" or warn "ERROR: Cannot append to /tmp/sessionlog";
447 printf L join("\n",@_);
453 $debug and warn "Checking Auth";
454 # $authnotrequired will be set for scripts which will run without authentication
455 my $authnotrequired = shift;
456 my $flagsrequired = shift;
458 $type = 'opac' unless $type;
460 my $dbh = C4::Context->dbh;
461 my $timeout = C4::Context->preference('timeout');
463 if ($timeout =~ /(\d+)[dD]/) {
464 $timeout = $1 * 86400;
466 $timeout = 600 unless $timeout;
468 _version_check($type,$query);
472 my ( $userid, $cookie, $sessionID, $flags, $shelves );
473 my $logout = $query->param('logout.x');
474 if ( $userid = $ENV{'REMOTE_USER'} ) {
475 # Using Basic Authentication, no cookies required
476 $cookie = $query->cookie(
477 -name => 'CGISESSID',
483 elsif ( $sessionID = $query->cookie("CGISESSID")) { # assignment, not comparison
484 my $session = get_session($sessionID);
485 C4::Context->_new_userenv($sessionID);
488 C4::Context::set_userenv(
489 $session->param('number'), $session->param('id'),
490 $session->param('cardnumber'), $session->param('firstname'),
491 $session->param('surname'), $session->param('branch'),
492 $session->param('branchname'), $session->param('flags'),
493 $session->param('emailaddress'), $session->param('branchprinter')
495 C4::Context::set_shelves_userenv($session->param('shelves'));
496 $debug and printf STDERR "AUTH_SESSION: (%s)\t%s %s - %s\n", map {$session->param($_)} qw(cardnumber firstname surname branch) ;
497 $ip = $session->param('ip');
498 $lasttime = $session->param('lasttime');
499 $userid = $session->param('id');
503 # voluntary logout the user
506 C4::Context->_unset_userenv($sessionID);
507 _session_log(sprintf "%20s from %16s logged out at %30s (manually).\n", $userid,$ip,localtime);
511 elsif ( $lasttime < time() - $timeout ) {
513 $info{'timed_out'} = 1;
515 C4::Context->_unset_userenv($sessionID);
516 _session_log(sprintf "%20s from %16s logged out at %30s (inactivity).\n", $userid,$ip,localtime);
520 elsif ( $ip ne $ENV{'REMOTE_ADDR'} ) {
521 # Different ip than originally logged in from
522 $info{'oldip'} = $ip;
523 $info{'newip'} = $ENV{'REMOTE_ADDR'};
524 $info{'different_ip'} = 1;
526 C4::Context->_unset_userenv($sessionID);
527 _session_log(sprintf "%20s from %16s logged out at %30s (ip changed to %16s).\n", $userid,$ip,localtime, $info{'newip'});
532 $cookie = $query->cookie( CGISESSID => $session->id );
533 $session->param('lasttime',time());
534 $flags = haspermission( $dbh, $userid, $flagsrequired );
538 $info{'nopermission'} = 1;
543 my $session = get_session("") or die "Auth ERROR: Cannot get_session()";
544 my $sessionID = $session->id;
545 $userid = $query->param('userid');
546 my $password = $query->param('password');
547 C4::Context->_new_userenv($sessionID);
548 my ( $return, $cardnumber ) = checkpw( $dbh, $userid, $password );
550 _session_log(sprintf "%20s from %16s logged in at %30s.\n", $userid,$ENV{'REMOTE_ADDR'},localtime);
551 $cookie = $query->cookie(CGISESSID => $sessionID);
552 if ( $flags = haspermission( $dbh, $userid, $flagsrequired ) ) {
556 $info{'nopermission'} = 1;
557 C4::Context->_unset_userenv($sessionID);
560 my ($borrowernumber, $firstname, $surname, $userflags,
561 $branchcode, $branchname, $branchprinter, $emailaddress);
563 if ( $return == 1 ) {
565 SELECT borrowernumber, firstname, surname, flags, borrowers.branchcode,
566 branches.branchname as branchname,
567 branches.branchprinter as branchprinter,
570 LEFT JOIN branches on borrowers.branchcode=branches.branchcode
572 my $sth = $dbh->prepare("$select where userid=?");
573 $sth->execute($userid);
574 unless ($sth->rows) {
575 $debug and print STDERR "AUTH_1: no rows for userid='$userid'\n";
576 $sth = $dbh->prepare("$select where cardnumber=?");
577 $sth->execute($cardnumber);
578 unless ($sth->rows) {
579 $debug and print STDERR "AUTH_2a: no rows for cardnumber='$cardnumber'\n";
580 $sth->execute($userid);
581 unless ($sth->rows) {
582 $debug and print STDERR "AUTH_2b: no rows for userid='$userid' AS cardnumber\n";
587 ($borrowernumber, $firstname, $surname, $userflags,
588 $branchcode, $branchname, $branchprinter, $emailaddress) = $sth->fetchrow;
589 $debug and print STDERR "AUTH_3 results: " .
590 "$cardnumber,$borrowernumber,$userid,$firstname,$surname,$userflags,$branchcode,$emailaddress\n";
592 print STDERR "AUTH_3: no results for userid='$userid', cardnumber='$cardnumber'.\n";
595 # launch a sequence to check if we have a ip for the branch, i
596 # if we have one we replace the branchcode of the userenv by the branch bound in the ip.
598 my $ip = $ENV{'REMOTE_ADDR'};
599 # if they specify at login, use that
600 if ($query->param('branch')) {
601 $branchcode = $query->param('branch');
602 $branchname = GetBranchName($branchcode);
604 my $branches = GetBranches();
605 if (C4::Context->boolean_preference('IndependantBranches') && C4::Context->boolean_preference('Autolocation')){
606 # we have to check they are coming from the right ip range
607 my $domain = $branches->{$branchcode}->{'branchip'};
608 if ($ip !~ /^$domain/){
610 $info{'wrongip'} = 1;
615 foreach my $br ( keys %$branches ) {
616 # now we work with the treatment of ip
617 my $domain = $branches->{$br}->{'branchip'};
618 if ( $domain && $ip =~ /^$domain/ ) {
619 $branchcode = $branches->{$br}->{'branchcode'};
621 # new op dev : add the branchprinter and branchname in the cookie
622 $branchprinter = $branches->{$br}->{'branchprinter'};
623 $branchname = $branches->{$br}->{'branchname'};
626 $session->param('number',$borrowernumber);
627 $session->param('id',$userid);
628 $session->param('cardnumber',$cardnumber);
629 $session->param('firstname',$firstname);
630 $session->param('surname',$surname);
631 $session->param('branch',$branchcode);
632 $session->param('branchname',$branchname);
633 $session->param('flags',$userflags);
634 $session->param('emailaddress',$emailaddress);
635 $session->param('ip',$session->remote_addr());
636 $session->param('lasttime',time());
637 $debug and printf STDERR "AUTH_4: (%s)\t%s %s - %s\n", map {$session->param($_)} qw(cardnumber firstname surname branch) ;
639 elsif ( $return == 2 ) {
640 #We suppose the user is the superlibrarian
642 $session->param('number',0);
643 $session->param('id',C4::Context->config('user'));
644 $session->param('cardnumber',C4::Context->config('user'));
645 $session->param('firstname',C4::Context->config('user'));
646 $session->param('surname',C4::Context->config('user'));
647 $session->param('branch','NO_LIBRARY_SET');
648 $session->param('branchname','NO_LIBRARY_SET');
649 $session->param('flags',1);
650 $session->param('emailaddress', C4::Context->preference('KohaAdminEmailAddress'));
651 $session->param('ip',$session->remote_addr());
652 $session->param('lasttime',time());
654 C4::Context::set_userenv(
655 $session->param('number'), $session->param('id'),
656 $session->param('cardnumber'), $session->param('firstname'),
657 $session->param('surname'), $session->param('branch'),
658 $session->param('branchname'), $session->param('flags'),
659 $session->param('emailaddress'), $session->param('branchprinter')
661 $shelves = GetShelvesSummary($borrowernumber,2,10);
662 $session->param('shelves', $shelves);
663 C4::Context::set_shelves_userenv($shelves);
667 $info{'invalid_username_or_password'} = 1;
668 C4::Context->_unset_userenv($sessionID);
672 } # END unless ($userid)
673 my $insecure = C4::Context->boolean_preference('insecure');
675 # finished authentification, now respond
676 if ( $loggedin || $authnotrequired || ( defined($insecure) && $insecure ) )
680 $cookie = $query->cookie( CGISESSID => '' );
682 return ( $userid, $cookie, $sessionID, $flags );
687 # AUTH rejected, show the login/password template, after checking the DB.
691 # get the inputs from the incoming query
693 foreach my $name ( param $query) {
694 (next) if ( $name eq 'userid' || $name eq 'password' );
695 my $value = $query->param($name);
696 push @inputs, { name => $name, value => $value };
698 # get the branchloop, which we need for authentication
699 my $branches = GetBranches();
701 for my $branch_hash (sort keys %$branches) {
702 push @branch_loop, {branchcode => "$branch_hash", branchname => $branches->{$branch_hash}->{'branchname'}, };
705 my $template_name = ( $type eq 'opac' ) ? 'opac-auth.tmpl' : 'auth.tmpl';
706 my $template = gettemplate( $template_name, $type, $query );
707 $template->param(branchloop => \@branch_loop,);
711 suggestion => C4::Context->preference("suggestion"),
712 virtualshelves => C4::Context->preference("virtualshelves"),
713 opaclargeimage => C4::Context->preference("opaclargeimage"),
714 LibraryName => C4::Context->preference("LibraryName"),
715 opacuserlogin => C4::Context->preference("opacuserlogin"),
716 OpacNav => C4::Context->preference("OpacNav"),
717 opaccredits => C4::Context->preference("opaccredits"),
718 opacreadinghistory => C4::Context->preference("opacreadinghistory"),
719 opacsmallimage => C4::Context->preference("opacsmallimage"),
720 opaclayoutstylesheet => C4::Context->preference("opaclayoutstylesheet"),
721 opaccolorstylesheet => C4::Context->preference("opaccolorstylesheet"),
722 opaclanguagesdisplay => C4::Context->preference("opaclanguagesdisplay"),
723 opacuserjs => C4::Context->preference("opacuserjs"),
724 opacbookbag => "" . C4::Context->preference("opacbookbag"),
725 OpacCloud => C4::Context->preference("OpacCloud"),
726 OpacTopissue => C4::Context->preference("OpacTopissue"),
727 OpacAuthorities => C4::Context->preference("OpacAuthorities"),
728 OpacBrowser => C4::Context->preference("OpacBrowser"),
729 intranetcolorstylesheet =>
730 C4::Context->preference("intranetcolorstylesheet"),
731 intranetstylesheet => C4::Context->preference("intranetstylesheet"),
732 IntranetNav => C4::Context->preference("IntranetNav"),
733 intranetuserjs => C4::Context->preference("intranetuserjs"),
734 TemplateEncoding => C4::Context->preference("TemplateEncoding"),
735 IndependantBranches=> C4::Context->preference("IndependantBranches"),
736 AutoLocation => C4::Context->preference("AutoLocation"),
737 yuipath => C4::Context->preference("yuipath"),
738 wrongip => $info{'wrongip'}
741 $template->param( loginprompt => 1 ) unless $info{'nopermission'};
743 my $self_url = $query->url( -absolute => 1 );
746 LibraryName => C4::Context->preference("LibraryName"),
748 $template->param( \%info );
749 # $cookie = $query->cookie(CGISESSID => $session->id
751 print $query->header(
752 -type => 'text/html',
762 ($status, $cookie, $sessionId) = check_api_auth($query, $userflags);
764 Given a CGI query containing the parameters 'userid' and 'password' and/or a session
765 cookie, determine if the user has the privileges specified by C<$userflags>.
767 C<check_api_auth> is is meant for authenticating users of web services, and
768 consequently will always return and will not attempt to redirect the user
771 If a valid session cookie is already present, check_api_auth will return a status
772 of "ok", the cookie, and the Koha session ID.
774 If no session cookie is present, check_api_auth will check the 'userid' and 'password
775 parameters and create a session cookie and Koha session if the supplied credentials
778 Possible return values in C<$status> are:
782 =item "ok" -- user authenticated; C<$cookie> and C<$sessionid> have valid values.
784 =item "failed" -- credentials are not correct; C<$cookie> and C<$sessionid> are undef
786 =item "maintenance" -- DB is in maintenance mode; no login possible at the moment
788 =item "expired -- session cookie has expired; API user should resubmit userid and password
796 my $flagsrequired = shift;
798 my $dbh = C4::Context->dbh;
799 my $timeout = C4::Context->preference('timeout');
800 $timeout = 600 unless $timeout;
802 unless (C4::Context->preference('Version')) {
803 # database has not been installed yet
804 return ("maintenance", undef, undef);
806 my $kohaversion=C4::Context::KOHAVERSION;
807 $kohaversion =~ s/(.*\..*)\.(.*)\.(.*)/$1$2$3/;
808 if (C4::Context->preference('Version') < $kohaversion) {
809 # database in need of version update; assume that
810 # no API should be called while databsae is in
812 return ("maintenance", undef, undef);
815 # FIXME -- most of what follows is a copy-and-paste
816 # of code from checkauth. There is an obvious need
817 # for refactoring to separate the various parts of
818 # the authentication code, but as of 2007-11-19 this
819 # is deferred so as to not introduce bugs into the
820 # regular authentication code for Koha 3.0.
822 # see if we have a valid session cookie already
823 # however, if a userid parameter is present (i.e., from
824 # a form submission, assume that any current cookie
826 my $sessionID = undef;
827 unless ($query->param('userid')) {
828 $sessionID = $query->cookie("CGISESSID");
831 my $session = get_session($sessionID);
832 C4::Context->_new_userenv($sessionID);
834 C4::Context::set_userenv(
835 $session->param('number'), $session->param('id'),
836 $session->param('cardnumber'), $session->param('firstname'),
837 $session->param('surname'), $session->param('branch'),
838 $session->param('branchname'), $session->param('flags'),
839 $session->param('emailaddress'), $session->param('branchprinter')
842 my $ip = $session->param('ip');
843 my $lasttime = $session->param('lasttime');
844 my $userid = $session->param('id');
845 if ( $lasttime < time() - $timeout ) {
848 C4::Context->_unset_userenv($sessionID);
851 return ("expired", undef, undef);
852 } elsif ( $ip ne $ENV{'REMOTE_ADDR'} ) {
855 C4::Context->_unset_userenv($sessionID);
858 return ("expired", undef, undef);
860 my $cookie = $query->cookie( CGISESSID => $session->id );
861 $session->param('lasttime',time());
862 my $flags = haspermission( $dbh, $userid, $flagsrequired );
864 return ("ok", $cookie, $sessionID);
867 C4::Context->_unset_userenv($sessionID);
870 return ("failed", undef, undef);
874 return ("expired", undef, undef);
878 my $userid = $query->param('userid');
879 my $password = $query->param('password');
880 unless ($userid and $password) {
881 # caller did something wrong, fail the authenticateion
882 return ("failed", undef, undef);
884 my ( $return, $cardnumber ) = checkpw( $dbh, $userid, $password );
885 if ($return and haspermission( $dbh, $userid, $flagsrequired)) {
886 my $session = get_session("");
887 return ("failed", undef, undef) unless $session;
889 my $sessionID = $session->id;
890 C4::Context->_new_userenv($sessionID);
891 my $cookie = $query->cookie(CGISESSID => $sessionID);
892 if ( $return == 1 ) {
894 $borrowernumber, $firstname, $surname,
895 $userflags, $branchcode, $branchname,
896 $branchprinter, $emailaddress
900 "select borrowernumber, firstname, surname, flags, borrowers.branchcode, branches.branchname as branchname,branches.branchprinter as branchprinter, email from borrowers left join branches on borrowers.branchcode=branches.branchcode where userid=?"
902 $sth->execute($userid);
904 $borrowernumber, $firstname, $surname,
905 $userflags, $branchcode, $branchname,
906 $branchprinter, $emailaddress
907 ) = $sth->fetchrow if ( $sth->rows );
909 unless ($sth->rows ) {
910 my $sth = $dbh->prepare(
911 "select borrowernumber, firstname, surname, flags, borrowers.branchcode, branches.branchname as branchname, branches.branchprinter as branchprinter, email from borrowers left join branches on borrowers.branchcode=branches.branchcode where cardnumber=?"
913 $sth->execute($cardnumber);
915 $borrowernumber, $firstname, $surname,
916 $userflags, $branchcode, $branchname,
917 $branchprinter, $emailaddress
918 ) = $sth->fetchrow if ( $sth->rows );
920 unless ( $sth->rows ) {
921 $sth->execute($userid);
923 $borrowernumber, $firstname, $surname, $userflags,
924 $branchcode, $branchname, $branchprinter, $emailaddress
925 ) = $sth->fetchrow if ( $sth->rows );
929 my $ip = $ENV{'REMOTE_ADDR'};
930 # if they specify at login, use that
931 if ($query->param('branch')) {
932 $branchcode = $query->param('branch');
933 $branchname = GetBranchName($branchcode);
935 my $branches = GetBranches();
937 foreach my $br ( keys %$branches ) {
938 # now we work with the treatment of ip
939 my $domain = $branches->{$br}->{'branchip'};
940 if ( $domain && $ip =~ /^$domain/ ) {
941 $branchcode = $branches->{$br}->{'branchcode'};
943 # new op dev : add the branchprinter and branchname in the cookie
944 $branchprinter = $branches->{$br}->{'branchprinter'};
945 $branchname = $branches->{$br}->{'branchname'};
948 $session->param('number',$borrowernumber);
949 $session->param('id',$userid);
950 $session->param('cardnumber',$cardnumber);
951 $session->param('firstname',$firstname);
952 $session->param('surname',$surname);
953 $session->param('branch',$branchcode);
954 $session->param('branchname',$branchname);
955 $session->param('flags',$userflags);
956 $session->param('emailaddress',$emailaddress);
957 $session->param('ip',$session->remote_addr());
958 $session->param('lasttime',time());
959 } elsif ( $return == 2 ) {
960 #We suppose the user is the superlibrarian
961 $session->param('number',0);
962 $session->param('id',C4::Context->config('user'));
963 $session->param('cardnumber',C4::Context->config('user'));
964 $session->param('firstname',C4::Context->config('user'));
965 $session->param('surname',C4::Context->config('user'));
966 $session->param('branch','NO_LIBRARY_SET');
967 $session->param('branchname','NO_LIBRARY_SET');
968 $session->param('flags',1);
969 $session->param('emailaddress', C4::Context->preference('KohaAdminEmailAddress'));
970 $session->param('ip',$session->remote_addr());
971 $session->param('lasttime',time());
973 C4::Context::set_userenv(
974 $session->param('number'), $session->param('id'),
975 $session->param('cardnumber'), $session->param('firstname'),
976 $session->param('surname'), $session->param('branch'),
977 $session->param('branchname'), $session->param('flags'),
978 $session->param('emailaddress'), $session->param('branchprinter')
980 return ("ok", $cookie, $sessionID);
982 return ("failed", undef, undef);
987 =item check_cookie_auth
989 ($status, $sessionId) = check_api_auth($cookie, $userflags);
991 Given a CGISESSID cookie set during a previous login to Koha, determine
992 if the user has the privileges specified by C<$userflags>.
994 C<check_cookie_auth> is meant for authenticating special services
995 such as tools/upload-file.pl that are invoked by other pages that
996 have been authenticated in the usual way.
998 Possible return values in C<$status> are:
1002 =item "ok" -- user authenticated; C<$sessionID> have valid values.
1004 =item "failed" -- credentials are not correct; C<$sessionid> are undef
1006 =item "maintenance" -- DB is in maintenance mode; no login possible at the moment
1008 =item "expired -- session cookie has expired; API user should resubmit userid and password
1014 sub check_cookie_auth {
1016 my $flagsrequired = shift;
1018 my $dbh = C4::Context->dbh;
1019 my $timeout = C4::Context->preference('timeout');
1020 $timeout = 600 unless $timeout;
1022 unless (C4::Context->preference('Version')) {
1023 # database has not been installed yet
1024 return ("maintenance", undef);
1026 my $kohaversion=C4::Context::KOHAVERSION;
1027 $kohaversion =~ s/(.*\..*)\.(.*)\.(.*)/$1$2$3/;
1028 if (C4::Context->preference('Version') < $kohaversion) {
1029 # database in need of version update; assume that
1030 # no API should be called while databsae is in
1032 return ("maintenance", undef);
1035 # FIXME -- most of what follows is a copy-and-paste
1036 # of code from checkauth. There is an obvious need
1037 # for refactoring to separate the various parts of
1038 # the authentication code, but as of 2007-11-23 this
1039 # is deferred so as to not introduce bugs into the
1040 # regular authentication code for Koha 3.0.
1042 # see if we have a valid session cookie already
1043 # however, if a userid parameter is present (i.e., from
1044 # a form submission, assume that any current cookie
1046 unless (defined $cookie and $cookie) {
1047 return ("failed", undef);
1049 my $sessionID = $cookie;
1050 my $session = get_session($sessionID);
1051 C4::Context->_new_userenv($sessionID);
1053 C4::Context::set_userenv(
1054 $session->param('number'), $session->param('id'),
1055 $session->param('cardnumber'), $session->param('firstname'),
1056 $session->param('surname'), $session->param('branch'),
1057 $session->param('branchname'), $session->param('flags'),
1058 $session->param('emailaddress'), $session->param('branchprinter')
1061 my $ip = $session->param('ip');
1062 my $lasttime = $session->param('lasttime');
1063 my $userid = $session->param('id');
1064 if ( $lasttime < time() - $timeout ) {
1067 C4::Context->_unset_userenv($sessionID);
1070 return ("expired", undef);
1071 } elsif ( $ip ne $ENV{'REMOTE_ADDR'} ) {
1072 # IP address changed
1074 C4::Context->_unset_userenv($sessionID);
1077 return ("expired", undef);
1079 $session->param('lasttime',time());
1080 my $flags = haspermission( $dbh, $userid, $flagsrequired );
1082 return ("ok", $sessionID);
1085 C4::Context->_unset_userenv($sessionID);
1088 return ("failed", undef);
1092 return ("expired", undef);
1099 my $session = get_session($sessionID);
1101 Given a session ID, retrieve the CGI::Session object used to store
1102 the session's state. The session object can be used to store
1103 data that needs to be accessed by different scripts during a
1106 If the C<$sessionID> parameter is an empty string, a new session
1112 my $sessionID = shift;
1113 my $storage_method = C4::Context->preference('SessionStorage');
1114 my $dbh = C4::Context->dbh;
1116 if ($storage_method eq 'mysql'){
1117 $session = new CGI::Session("driver:MySQL;serializer:yaml", $sessionID, {Handle=>$dbh});
1119 elsif ($storage_method eq 'Pg') {
1120 $session = new CGI::Session("driver:PostgreSQL;serializer:yaml", $sessionID, {Handle=>$dbh});
1123 # catch all defaults to tmp should work on all systems
1124 $session = new CGI::Session("driver:File;serializer:yaml", $sessionID, {Directory=>'/tmp'});
1131 my ( $dbh, $userid, $password ) = @_;
1133 $debug and print "## checkpw - checking LDAP\n";
1134 my ($retval,$retcard) = checkpw_ldap(@_); # EXTERNAL AUTH
1135 ($retval) and return ($retval,$retcard);
1141 "select password,cardnumber,borrowernumber,userid,firstname,surname,branchcode,flags from borrowers where userid=?"
1143 $sth->execute($userid);
1145 my ( $md5password, $cardnumber, $borrowernumber, $userid, $firstname,
1146 $surname, $branchcode, $flags )
1148 if ( md5_base64($password) eq $md5password ) {
1150 C4::Context->set_userenv( "$borrowernumber", $userid, $cardnumber,
1151 $firstname, $surname, $branchcode, $flags );
1152 return 1, $cardnumber;
1157 "select password,cardnumber,borrowernumber,userid, firstname,surname,branchcode,flags from borrowers where cardnumber=?"
1159 $sth->execute($userid);
1161 my ( $md5password, $cardnumber, $borrowernumber, $userid, $firstname,
1162 $surname, $branchcode, $flags )
1164 if ( md5_base64($password) eq $md5password ) {
1166 C4::Context->set_userenv( $borrowernumber, $userid, $cardnumber,
1167 $firstname, $surname, $branchcode, $flags );
1171 if ( $userid && $userid eq C4::Context->config('user')
1172 && "$password" eq C4::Context->config('pass') )
1175 # Koha superuser account
1176 # C4::Context->set_userenv(0,0,C4::Context->config('user'),C4::Context->config('user'),C4::Context->config('user'),"",1);
1179 if ( $userid && $userid eq 'demo'
1180 && "$password" eq 'demo'
1181 && C4::Context->config('demo') )
1184 # DEMO => the demo user is allowed to do everything (if demo set to 1 in koha.conf
1185 # some features won't be effective : modify systempref, modify MARC structure,
1193 $authflags = getuserflags($flags,$dbh);
1194 Translates integer flags into permissions strings hash.
1196 C<$flags> is the integer userflags value ( borrowers.userflags )
1197 C<$authflags> is a hashref of permissions
1206 $flags = 0 unless $flags;
1207 my $sth = $dbh->prepare("SELECT bit, flag, defaulton FROM userflags");
1210 while ( my ( $bit, $flag, $defaulton ) = $sth->fetchrow ) {
1211 if ( ( $flags & ( 2**$bit ) ) || $defaulton ) {
1212 $userflags->{$flag} = 1;
1215 $userflags->{$flag} = 0;
1219 # get subpermissions and merge with top-level permissions
1220 my $user_subperms = get_user_subpermissions($userid);
1221 foreach my $module (keys %$user_subperms) {
1222 next if $userflags->{$module} == 1; # user already has permission for everything in this module
1223 $userflags->{$module} = $user_subperms->{$module};
1229 =item get_user_subpermissions
1233 my $user_perm_hashref = get_user_subpermissions($userid);
1237 Given the userid (note, not the borrowernumber) of a staff user,
1238 return a hashref of hashrefs of the specific subpermissions
1239 accorded to the user. An example return is
1243 export_catalog => 1,
1244 import_patrons => 1,
1248 The top-level hash-key is a module or function code from
1249 userflags.flag, while the second-level key is a code
1252 The results of this function do not give a complete picture
1253 of the functions that a staff user can access; it is also
1254 necessary to check borrowers.flags.
1258 sub get_user_subpermissions {
1261 my $dbh = C4::Context->dbh;
1262 my $sth = $dbh->prepare("SELECT flag, code
1263 FROM user_permissions
1264 JOIN permissions USING (module_bit, code)
1265 JOIN userflags ON (module_bit = bit)
1266 JOIN borrowers USING (borrowernumber)
1268 $sth->execute($userid);
1270 my $user_perms = {};
1271 while (my $perm = $sth->fetchrow_hashref) {
1272 $user_perms->{$perm->{'flag'}}->{$perm->{'code'}} = 1;
1277 =item get_all_subpermissions
1281 my $perm_hashref = get_all_subpermissions();
1285 Returns a hashref of hashrefs defining all specific
1286 permissions currently defined. The return value
1287 has the same structure as that of C<get_user_subpermissions>,
1288 except that the innermost hash value is the description
1289 of the subpermission.
1293 sub get_all_subpermissions {
1294 my $dbh = C4::Context->dbh;
1295 my $sth = $dbh->prepare("SELECT flag, code, description
1297 JOIN userflags ON (module_bit = bit)");
1301 while (my $perm = $sth->fetchrow_hashref) {
1302 $all_perms->{$perm->{'flag'}}->{$perm->{'code'}} = $perm->{'description'};
1309 $flags = ($dbh,$member,$flagsrequired);
1311 C<$member> may be either userid or overloaded with $borrower hashref from GetMemberDetails.
1312 C<$flags> is a hashref of required flags like C<$borrower-<{authflags}>
1314 Returns member's flags or 0 if a permission is not met.
1319 my ( $dbh, $userid, $flagsrequired ) = @_;
1320 my ($flags,$intflags);
1321 $dbh=C4::Context->dbh unless($dbh);
1323 $intflags = $userid->{'flags'};
1325 my $sth = $dbh->prepare("SELECT flags FROM borrowers WHERE userid=?");
1326 $sth->execute($userid);
1327 my ($intflags) = $sth->fetchrow;
1328 $flags = getuserflags( $intflags, $userid, $dbh );
1330 if ( $userid eq C4::Context->config('user') ) {
1331 # Super User Account from /etc/koha.conf
1332 $flags->{'superlibrarian'} = 1;
1334 if ( $userid eq 'demo' && C4::Context->config('demo') ) {
1335 # Demo user that can do "anything" (demo=1 in /etc/koha.conf)
1336 $flags->{'superlibrarian'} = 1;
1338 return $flags if $flags->{superlibrarian};
1339 foreach my $module ( keys %$flagsrequired ) {
1340 if (C4::Context->preference('GranularPermissions')) {
1341 my $subperm = $flagsrequired->{$module};
1342 if ($subperm eq '*') {
1343 return 0 unless ( $flags->{$module} == 1 or ref($flags->{$module}) );
1345 return 0 unless ( $flags->{$module} == 1 or
1346 ( ref($flags->{$module}) and
1347 exists $flags->{$module}->{$subperm} and
1348 $flags->{$module}->{$subperm} == 1
1353 return 0 unless ( $flags->{$module} );
1357 #FIXME - This fcn should return the failed permission so a suitable error msg can be delivered.
1361 sub getborrowernumber {
1363 my $dbh = C4::Context->dbh;
1364 for my $field ( 'userid', 'cardnumber' ) {
1366 $dbh->prepare("select borrowernumber from borrowers where $field=?");
1367 $sth->execute($userid);
1369 my ($bnumber) = $sth->fetchrow;
1376 END { } # module clean-up code here (global destructor)