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"),
262 GoogleJackets => C4::Context->preference("GoogleJackets"),
263 AuthorisedValueImages => C4::Context->preference("AuthorisedValueImages"),
267 warn "template type should be OPAC, here it is=[" . $in->{'type'} . "]" unless ( $in->{'type'} eq 'opac' );
268 my $LibraryNameTitle = C4::Context->preference("LibraryName");
269 $LibraryNameTitle =~ s/<(?:\/?)(?:br|p)\s*(?:\/?)>/ /sgi;
270 $LibraryNameTitle =~ s/<(?:[^<>'"]|'(?:[^']*)'|"(?:[^"]*)")*>//sg;
272 KohaAdminEmailAddress => "" . C4::Context->preference("KohaAdminEmailAddress"),
273 AnonSuggestions => "" . C4::Context->preference("AnonSuggestions"),
274 suggestion => "" . C4::Context->preference("suggestion"),
275 OPACViewOthersSuggestions => "" . C4::Context->preference("OPACViewOthersSuggestions"),
276 virtualshelves => "" . C4::Context->preference("virtualshelves"),
277 OpacNav => "" . C4::Context->preference("OpacNav"),
278 opacheader => "" . C4::Context->preference("opacheader"),
279 opaccredits => "" . C4::Context->preference("opaccredits"),
280 opacsmallimage => "" . C4::Context->preference("opacsmallimage"),
281 opaclargeimage => "" . C4::Context->preference("opaclargeimage"),
282 opaclayoutstylesheet => "". C4::Context->preference("opaclayoutstylesheet"),
283 opaccolorstylesheet => "". C4::Context->preference("opaccolorstylesheet"),
284 OPACUserCSS => "". C4::Context->preference("OPACUserCSS"),
285 opaclanguagesdisplay => "". C4::Context->preference("opaclanguagesdisplay"),
286 opacuserlogin => "" . C4::Context->preference("opacuserlogin"),
287 OpacMainUserBlock => "" . C4::Context->preference("OpacMainUserBlock"),
288 OPACURLOpenInNewWindow => "" . C4::Context->preference("OPACURLOpenInNewWindow"),
289 opacbookbag => "" . C4::Context->preference("opacbookbag"),
290 TemplateEncoding => "". C4::Context->preference("TemplateEncoding"),
291 AmazonContent => "" . C4::Context->preference("AmazonContent"),
292 OPACShelfBrowser => "". C4::Context->preference("OPACShelfBrowser"),
293 OPACAmazonSimilarItems => "" . C4::Context->preference("OPACAmazonSimilarItems"),
294 LibraryName => "" . C4::Context->preference("LibraryName"),
295 LibraryNameTitle => "" . $LibraryNameTitle,
296 LoginBranchcode => (C4::Context->userenv?C4::Context->userenv->{"branch"}:"insecure"),
297 LoginBranchname => C4::Context->userenv?C4::Context->userenv->{"branchname"}:"",
298 LoginFirstname => (C4::Context->userenv?C4::Context->userenv->{"firstname"}:"Bel"),
299 LoginSurname => C4::Context->userenv?C4::Context->userenv->{"surname"}:"Inconnu",
300 OpacPasswordChange => C4::Context->preference("OpacPasswordChange"),
301 opacreadinghistory => C4::Context->preference("opacreadinghistory"),
302 opacuserjs => C4::Context->preference("opacuserjs"),
303 OpacCloud => C4::Context->preference("OpacCloud"),
304 OpacTopissue => C4::Context->preference("OpacTopissue"),
305 OpacAuthorities => C4::Context->preference("OpacAuthorities"),
306 OpacBrowser => C4::Context->preference("OpacBrowser"),
307 RequestOnOpac => C4::Context->preference("RequestOnOpac"),
308 OPACItemHolds => C4::Context->preference("OPACItemHolds"),
309 reviewson => C4::Context->preference("reviewson"),
310 hide_marc => C4::Context->preference("hide_marc"),
311 patronimages => C4::Context->preference("patronimages"),
312 hidelostitems => C4::Context->preference("hidelostitems"),
313 mylibraryfirst => C4::Context->preference("SearchMyLibraryFirst"),
314 "BiblioDefaultView".C4::Context->preference("BiblioDefaultView") => 1,
315 OPACFRBRizeEditions => C4::Context->preference("OPACFRBRizeEditions"),
316 'item-level_itypes' => C4::Context->preference('item-level_itypes'),
317 'Version' => C4::Context->preference('Version'),
318 yuipath => C4::Context->preference("yuipath"),
319 singleBranchMode => C4::Context->preference("singleBranchMode"),
320 XSLTResultsDisplay => C4::Context->preference("XSLTResultsDisplay"),
321 XSLTDetailsDisplay => C4::Context->preference("XSLTDetailsDisplay"),
322 TagsEnabled => C4::Context->preference("TagsEnabled"),
323 GoogleJackets => C4::Context->preference("GoogleJackets"),
324 AuthorisedValueImages => C4::Context->preference("AuthorisedValueImages"),
325 OPACBaseURL=> C4::Context->preference("OPACBaseURL"),
328 $template->param(listloop=>[{shelfname=>"Freelist", shelfnumber=>110}]);
329 return ( $template, $borrowernumber, $cookie, $flags);
334 ($userid, $cookie, $sessionID) = &checkauth($query, $noauth, $flagsrequired, $type);
336 Verifies that the user is authorized to run this script. If
337 the user is authorized, a (userid, cookie, session-id, flags)
338 quadruple is returned. If the user is not authorized but does
339 not have the required privilege (see $flagsrequired below), it
340 displays an error page and exits. Otherwise, it displays the
341 login page and exits.
343 Note that C<&checkauth> will return if and only if the user
344 is authorized, so it should be called early on, before any
345 unfinished operations (e.g., if you've opened a file, then
346 C<&checkauth> won't close it for you).
348 C<$query> is the CGI object for the script calling C<&checkauth>.
350 The C<$noauth> argument is optional. If it is set, then no
351 authorization is required for the script.
353 C<&checkauth> fetches user and session information from C<$query> and
354 ensures that the user is authorized to run scripts that require
357 The C<$flagsrequired> argument specifies the required privileges
358 the user must have if the username and password are correct.
359 It should be specified as a reference-to-hash; keys in the hash
360 should be the "flags" for the user, as specified in the Members
361 intranet module. Any key specified must correspond to a "flag"
362 in the userflags table. E.g., { circulate => 1 } would specify
363 that the user must have the "circulate" privilege in order to
364 proceed. To make sure that access control is correct, the
365 C<$flagsrequired> parameter must be specified correctly.
367 If the GranularPermissions system preference is ON, the
368 value of each key in the C<flagsrequired> hash takes on an additional
373 The user must have access to all subfunctions of the module
374 specified by the hash key.
378 The user must have access to at least one subfunction of the module
379 specified by the hash key.
381 =item specific permission, e.g., 'export_catalog'
383 The user must have access to the specific subfunction list, which
384 must correspond to a row in the permissions table.
386 The C<$type> argument specifies whether the template should be
387 retrieved from the opac or intranet directory tree. "opac" is
388 assumed if it is not specified; however, if C<$type> is specified,
389 "intranet" is assumed if it is not "opac".
391 If C<$query> does not have a valid session ID associated with it
392 (i.e., the user has not logged in) or if the session has expired,
393 C<&checkauth> presents the user with a login page (from the point of
394 view of the original script, C<&checkauth> does not return). Once the
395 user has authenticated, C<&checkauth> restarts the original script
396 (this time, C<&checkauth> returns).
398 The login page is provided using a HTML::Template, which is set in the
399 systempreferences table or at the top of this file. The variable C<$type>
400 selects which template to use, either the opac or the intranet
401 authentification template.
403 C<&checkauth> returns a user ID, a cookie, and a session ID. The
404 cookie should be sent back to the browser; it verifies that the user
409 sub _version_check ($$) {
413 # If Version syspref is unavailable, it means Koha is beeing installed,
414 # and so we must redirect to OPAC maintenance page or to the WebInstaller
415 #warn "about to check version";
416 unless ($version = C4::Context->preference('Version')) { # assignment, not comparison
417 if ($type ne 'opac') {
418 warn "Install required, redirecting to Installer";
419 print $query->redirect("/cgi-bin/koha/installer/install.pl");
422 warn "OPAC Install required, redirecting to maintenance";
423 print $query->redirect("/cgi-bin/koha/maintenance.pl");
428 # check that database and koha version are the same
429 # there is no DB version, it's a fresh install,
430 # go to web installer
431 # there is a DB version, compare it to the code version
432 my $kohaversion=C4::Context::KOHAVERSION;
433 # remove the 3 last . to have a Perl number
434 $kohaversion =~ s/(.*\..*)\.(.*)\.(.*)/$1$2$3/;
435 $debug and print STDERR "kohaversion : $kohaversion\n";
436 if ($version < $kohaversion){
437 my $warning = "Database update needed, redirecting to %s. Database is $version and Koha is $kohaversion";
438 if ($type ne 'opac'){
439 warn sprintf($warning, 'Installer');
440 print $query->redirect("/cgi-bin/koha/installer/install.pl?step=3");
442 warn sprintf("OPAC: " . $warning, 'maintenance');
443 print $query->redirect("/cgi-bin/koha/maintenance.pl");
451 open L, ">>/tmp/sessionlog" or warn "ERROR: Cannot append to /tmp/sessionlog";
452 printf L join("\n",@_);
458 $debug and warn "Checking Auth";
459 # $authnotrequired will be set for scripts which will run without authentication
460 my $authnotrequired = shift;
461 my $flagsrequired = shift;
463 $type = 'opac' unless $type;
465 my $dbh = C4::Context->dbh;
466 my $timeout = C4::Context->preference('timeout');
468 if ($timeout =~ /(\d+)[dD]/) {
469 $timeout = $1 * 86400;
471 $timeout = 600 unless $timeout;
473 _version_check($type,$query);
477 my ( $userid, $cookie, $sessionID, $flags, $shelves );
478 my $logout = $query->param('logout.x');
479 if ( $userid = $ENV{'REMOTE_USER'} ) {
480 # Using Basic Authentication, no cookies required
481 $cookie = $query->cookie(
482 -name => 'CGISESSID',
488 elsif ( $sessionID = $query->cookie("CGISESSID")) { # assignment, not comparison
489 my $session = get_session($sessionID);
490 C4::Context->_new_userenv($sessionID);
493 C4::Context::set_userenv(
494 $session->param('number'), $session->param('id'),
495 $session->param('cardnumber'), $session->param('firstname'),
496 $session->param('surname'), $session->param('branch'),
497 $session->param('branchname'), $session->param('flags'),
498 $session->param('emailaddress'), $session->param('branchprinter')
500 C4::Context::set_shelves_userenv($session->param('shelves'));
501 $debug and printf STDERR "AUTH_SESSION: (%s)\t%s %s - %s\n", map {$session->param($_)} qw(cardnumber firstname surname branch) ;
502 $ip = $session->param('ip');
503 $lasttime = $session->param('lasttime');
504 $userid = $session->param('id');
508 # voluntary logout the user
511 C4::Context->_unset_userenv($sessionID);
512 _session_log(sprintf "%20s from %16s logged out at %30s (manually).\n", $userid,$ip,localtime);
516 elsif ( $lasttime < time() - $timeout ) {
518 $info{'timed_out'} = 1;
520 C4::Context->_unset_userenv($sessionID);
521 _session_log(sprintf "%20s from %16s logged out at %30s (inactivity).\n", $userid,$ip,localtime);
525 elsif ( $ip ne $ENV{'REMOTE_ADDR'} ) {
526 # Different ip than originally logged in from
527 $info{'oldip'} = $ip;
528 $info{'newip'} = $ENV{'REMOTE_ADDR'};
529 $info{'different_ip'} = 1;
531 C4::Context->_unset_userenv($sessionID);
532 _session_log(sprintf "%20s from %16s logged out at %30s (ip changed to %16s).\n", $userid,$ip,localtime, $info{'newip'});
537 $cookie = $query->cookie( CGISESSID => $session->id );
538 $session->param('lasttime',time());
539 $flags = haspermission( $dbh, $userid, $flagsrequired );
543 $info{'nopermission'} = 1;
548 my $session = get_session("") or die "Auth ERROR: Cannot get_session()";
549 my $sessionID = $session->id;
550 $userid = $query->param('userid');
551 my $password = $query->param('password');
552 C4::Context->_new_userenv($sessionID);
553 my ( $return, $cardnumber ) = checkpw( $dbh, $userid, $password );
555 _session_log(sprintf "%20s from %16s logged in at %30s.\n", $userid,$ENV{'REMOTE_ADDR'},localtime);
556 $cookie = $query->cookie(CGISESSID => $sessionID);
557 if ( $flags = haspermission( $dbh, $userid, $flagsrequired ) ) {
561 $info{'nopermission'} = 1;
562 C4::Context->_unset_userenv($sessionID);
565 my ($borrowernumber, $firstname, $surname, $userflags,
566 $branchcode, $branchname, $branchprinter, $emailaddress);
568 if ( $return == 1 ) {
570 SELECT borrowernumber, firstname, surname, flags, borrowers.branchcode,
571 branches.branchname as branchname,
572 branches.branchprinter as branchprinter,
575 LEFT JOIN branches on borrowers.branchcode=branches.branchcode
577 my $sth = $dbh->prepare("$select where userid=?");
578 $sth->execute($userid);
579 unless ($sth->rows) {
580 $debug and print STDERR "AUTH_1: no rows for userid='$userid'\n";
581 $sth = $dbh->prepare("$select where cardnumber=?");
582 $sth->execute($cardnumber);
583 unless ($sth->rows) {
584 $debug and print STDERR "AUTH_2a: no rows for cardnumber='$cardnumber'\n";
585 $sth->execute($userid);
586 unless ($sth->rows) {
587 $debug and print STDERR "AUTH_2b: no rows for userid='$userid' AS cardnumber\n";
592 ($borrowernumber, $firstname, $surname, $userflags,
593 $branchcode, $branchname, $branchprinter, $emailaddress) = $sth->fetchrow;
594 $debug and print STDERR "AUTH_3 results: " .
595 "$cardnumber,$borrowernumber,$userid,$firstname,$surname,$userflags,$branchcode,$emailaddress\n";
597 print STDERR "AUTH_3: no results for userid='$userid', cardnumber='$cardnumber'.\n";
600 # launch a sequence to check if we have a ip for the branch, i
601 # if we have one we replace the branchcode of the userenv by the branch bound in the ip.
603 my $ip = $ENV{'REMOTE_ADDR'};
604 # if they specify at login, use that
605 if ($query->param('branch')) {
606 $branchcode = $query->param('branch');
607 $branchname = GetBranchName($branchcode);
609 my $branches = GetBranches();
610 if (C4::Context->boolean_preference('IndependantBranches') && C4::Context->boolean_preference('Autolocation')){
611 # we have to check they are coming from the right ip range
612 my $domain = $branches->{$branchcode}->{'branchip'};
613 if ($ip !~ /^$domain/){
615 $info{'wrongip'} = 1;
620 foreach my $br ( keys %$branches ) {
621 # now we work with the treatment of ip
622 my $domain = $branches->{$br}->{'branchip'};
623 if ( $domain && $ip =~ /^$domain/ ) {
624 $branchcode = $branches->{$br}->{'branchcode'};
626 # new op dev : add the branchprinter and branchname in the cookie
627 $branchprinter = $branches->{$br}->{'branchprinter'};
628 $branchname = $branches->{$br}->{'branchname'};
631 $session->param('number',$borrowernumber);
632 $session->param('id',$userid);
633 $session->param('cardnumber',$cardnumber);
634 $session->param('firstname',$firstname);
635 $session->param('surname',$surname);
636 $session->param('branch',$branchcode);
637 $session->param('branchname',$branchname);
638 $session->param('flags',$userflags);
639 $session->param('emailaddress',$emailaddress);
640 $session->param('ip',$session->remote_addr());
641 $session->param('lasttime',time());
642 $debug and printf STDERR "AUTH_4: (%s)\t%s %s - %s\n", map {$session->param($_)} qw(cardnumber firstname surname branch) ;
644 elsif ( $return == 2 ) {
645 #We suppose the user is the superlibrarian
647 $session->param('number',0);
648 $session->param('id',C4::Context->config('user'));
649 $session->param('cardnumber',C4::Context->config('user'));
650 $session->param('firstname',C4::Context->config('user'));
651 $session->param('surname',C4::Context->config('user'));
652 $session->param('branch','NO_LIBRARY_SET');
653 $session->param('branchname','NO_LIBRARY_SET');
654 $session->param('flags',1);
655 $session->param('emailaddress', C4::Context->preference('KohaAdminEmailAddress'));
656 $session->param('ip',$session->remote_addr());
657 $session->param('lasttime',time());
659 C4::Context::set_userenv(
660 $session->param('number'), $session->param('id'),
661 $session->param('cardnumber'), $session->param('firstname'),
662 $session->param('surname'), $session->param('branch'),
663 $session->param('branchname'), $session->param('flags'),
664 $session->param('emailaddress'), $session->param('branchprinter')
666 $shelves = GetShelvesSummary($borrowernumber,2,10);
667 $session->param('shelves', $shelves);
668 C4::Context::set_shelves_userenv($shelves);
672 $info{'invalid_username_or_password'} = 1;
673 C4::Context->_unset_userenv($sessionID);
677 } # END unless ($userid)
678 my $insecure = C4::Context->boolean_preference('insecure');
680 # finished authentification, now respond
681 if ( $loggedin || $authnotrequired || ( defined($insecure) && $insecure ) )
685 $cookie = $query->cookie( CGISESSID => '' );
687 return ( $userid, $cookie, $sessionID, $flags );
692 # AUTH rejected, show the login/password template, after checking the DB.
696 # get the inputs from the incoming query
698 foreach my $name ( param $query) {
699 (next) if ( $name eq 'userid' || $name eq 'password' );
700 my $value = $query->param($name);
701 push @inputs, { name => $name, value => $value };
703 # get the branchloop, which we need for authentication
704 my $branches = GetBranches();
706 for my $branch_hash (sort keys %$branches) {
707 push @branch_loop, {branchcode => "$branch_hash", branchname => $branches->{$branch_hash}->{'branchname'}, };
710 my $template_name = ( $type eq 'opac' ) ? 'opac-auth.tmpl' : 'auth.tmpl';
711 my $template = gettemplate( $template_name, $type, $query );
712 $template->param(branchloop => \@branch_loop,);
716 suggestion => C4::Context->preference("suggestion"),
717 virtualshelves => C4::Context->preference("virtualshelves"),
718 opaclargeimage => C4::Context->preference("opaclargeimage"),
719 LibraryName => C4::Context->preference("LibraryName"),
720 opacuserlogin => C4::Context->preference("opacuserlogin"),
721 OpacNav => C4::Context->preference("OpacNav"),
722 opaccredits => C4::Context->preference("opaccredits"),
723 opacreadinghistory => C4::Context->preference("opacreadinghistory"),
724 opacsmallimage => C4::Context->preference("opacsmallimage"),
725 opaclayoutstylesheet => C4::Context->preference("opaclayoutstylesheet"),
726 opaccolorstylesheet => C4::Context->preference("opaccolorstylesheet"),
727 opaclanguagesdisplay => C4::Context->preference("opaclanguagesdisplay"),
728 opacuserjs => C4::Context->preference("opacuserjs"),
729 opacbookbag => "" . C4::Context->preference("opacbookbag"),
730 OpacCloud => C4::Context->preference("OpacCloud"),
731 OpacTopissue => C4::Context->preference("OpacTopissue"),
732 OpacAuthorities => C4::Context->preference("OpacAuthorities"),
733 OpacBrowser => C4::Context->preference("OpacBrowser"),
734 opacheader => C4::Context->preference("opacheader"),
735 OPACUserCSS => C4::Context->preference("OPACUserCSS"),
736 intranetcolorstylesheet =>
737 C4::Context->preference("intranetcolorstylesheet"),
738 intranetstylesheet => C4::Context->preference("intranetstylesheet"),
739 IntranetNav => C4::Context->preference("IntranetNav"),
740 intranetuserjs => C4::Context->preference("intranetuserjs"),
741 TemplateEncoding => C4::Context->preference("TemplateEncoding"),
742 IndependantBranches=> C4::Context->preference("IndependantBranches"),
743 AutoLocation => C4::Context->preference("AutoLocation"),
744 yuipath => C4::Context->preference("yuipath"),
745 wrongip => $info{'wrongip'}
748 $template->param( loginprompt => 1 ) unless $info{'nopermission'};
750 my $self_url = $query->url( -absolute => 1 );
753 LibraryName => C4::Context->preference("LibraryName"),
755 $template->param( \%info );
756 # $cookie = $query->cookie(CGISESSID => $session->id
758 print $query->header(
759 -type => 'text/html',
769 ($status, $cookie, $sessionId) = check_api_auth($query, $userflags);
771 Given a CGI query containing the parameters 'userid' and 'password' and/or a session
772 cookie, determine if the user has the privileges specified by C<$userflags>.
774 C<check_api_auth> is is meant for authenticating users of web services, and
775 consequently will always return and will not attempt to redirect the user
778 If a valid session cookie is already present, check_api_auth will return a status
779 of "ok", the cookie, and the Koha session ID.
781 If no session cookie is present, check_api_auth will check the 'userid' and 'password
782 parameters and create a session cookie and Koha session if the supplied credentials
785 Possible return values in C<$status> are:
789 =item "ok" -- user authenticated; C<$cookie> and C<$sessionid> have valid values.
791 =item "failed" -- credentials are not correct; C<$cookie> and C<$sessionid> are undef
793 =item "maintenance" -- DB is in maintenance mode; no login possible at the moment
795 =item "expired -- session cookie has expired; API user should resubmit userid and password
803 my $flagsrequired = shift;
805 my $dbh = C4::Context->dbh;
806 my $timeout = C4::Context->preference('timeout');
807 $timeout = 600 unless $timeout;
809 unless (C4::Context->preference('Version')) {
810 # database has not been installed yet
811 return ("maintenance", undef, undef);
813 my $kohaversion=C4::Context::KOHAVERSION;
814 $kohaversion =~ s/(.*\..*)\.(.*)\.(.*)/$1$2$3/;
815 if (C4::Context->preference('Version') < $kohaversion) {
816 # database in need of version update; assume that
817 # no API should be called while databsae is in
819 return ("maintenance", undef, undef);
822 # FIXME -- most of what follows is a copy-and-paste
823 # of code from checkauth. There is an obvious need
824 # for refactoring to separate the various parts of
825 # the authentication code, but as of 2007-11-19 this
826 # is deferred so as to not introduce bugs into the
827 # regular authentication code for Koha 3.0.
829 # see if we have a valid session cookie already
830 # however, if a userid parameter is present (i.e., from
831 # a form submission, assume that any current cookie
833 my $sessionID = undef;
834 unless ($query->param('userid')) {
835 $sessionID = $query->cookie("CGISESSID");
838 my $session = get_session($sessionID);
839 C4::Context->_new_userenv($sessionID);
841 C4::Context::set_userenv(
842 $session->param('number'), $session->param('id'),
843 $session->param('cardnumber'), $session->param('firstname'),
844 $session->param('surname'), $session->param('branch'),
845 $session->param('branchname'), $session->param('flags'),
846 $session->param('emailaddress'), $session->param('branchprinter')
849 my $ip = $session->param('ip');
850 my $lasttime = $session->param('lasttime');
851 my $userid = $session->param('id');
852 if ( $lasttime < time() - $timeout ) {
855 C4::Context->_unset_userenv($sessionID);
858 return ("expired", undef, undef);
859 } elsif ( $ip ne $ENV{'REMOTE_ADDR'} ) {
862 C4::Context->_unset_userenv($sessionID);
865 return ("expired", undef, undef);
867 my $cookie = $query->cookie( CGISESSID => $session->id );
868 $session->param('lasttime',time());
869 my $flags = haspermission( $dbh, $userid, $flagsrequired );
871 return ("ok", $cookie, $sessionID);
874 C4::Context->_unset_userenv($sessionID);
877 return ("failed", undef, undef);
881 return ("expired", undef, undef);
885 my $userid = $query->param('userid');
886 my $password = $query->param('password');
887 unless ($userid and $password) {
888 # caller did something wrong, fail the authenticateion
889 return ("failed", undef, undef);
891 my ( $return, $cardnumber ) = checkpw( $dbh, $userid, $password );
892 if ($return and haspermission( $dbh, $userid, $flagsrequired)) {
893 my $session = get_session("");
894 return ("failed", undef, undef) unless $session;
896 my $sessionID = $session->id;
897 C4::Context->_new_userenv($sessionID);
898 my $cookie = $query->cookie(CGISESSID => $sessionID);
899 if ( $return == 1 ) {
901 $borrowernumber, $firstname, $surname,
902 $userflags, $branchcode, $branchname,
903 $branchprinter, $emailaddress
907 "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=?"
909 $sth->execute($userid);
911 $borrowernumber, $firstname, $surname,
912 $userflags, $branchcode, $branchname,
913 $branchprinter, $emailaddress
914 ) = $sth->fetchrow if ( $sth->rows );
916 unless ($sth->rows ) {
917 my $sth = $dbh->prepare(
918 "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=?"
920 $sth->execute($cardnumber);
922 $borrowernumber, $firstname, $surname,
923 $userflags, $branchcode, $branchname,
924 $branchprinter, $emailaddress
925 ) = $sth->fetchrow if ( $sth->rows );
927 unless ( $sth->rows ) {
928 $sth->execute($userid);
930 $borrowernumber, $firstname, $surname, $userflags,
931 $branchcode, $branchname, $branchprinter, $emailaddress
932 ) = $sth->fetchrow if ( $sth->rows );
936 my $ip = $ENV{'REMOTE_ADDR'};
937 # if they specify at login, use that
938 if ($query->param('branch')) {
939 $branchcode = $query->param('branch');
940 $branchname = GetBranchName($branchcode);
942 my $branches = GetBranches();
944 foreach my $br ( keys %$branches ) {
945 # now we work with the treatment of ip
946 my $domain = $branches->{$br}->{'branchip'};
947 if ( $domain && $ip =~ /^$domain/ ) {
948 $branchcode = $branches->{$br}->{'branchcode'};
950 # new op dev : add the branchprinter and branchname in the cookie
951 $branchprinter = $branches->{$br}->{'branchprinter'};
952 $branchname = $branches->{$br}->{'branchname'};
955 $session->param('number',$borrowernumber);
956 $session->param('id',$userid);
957 $session->param('cardnumber',$cardnumber);
958 $session->param('firstname',$firstname);
959 $session->param('surname',$surname);
960 $session->param('branch',$branchcode);
961 $session->param('branchname',$branchname);
962 $session->param('flags',$userflags);
963 $session->param('emailaddress',$emailaddress);
964 $session->param('ip',$session->remote_addr());
965 $session->param('lasttime',time());
966 } elsif ( $return == 2 ) {
967 #We suppose the user is the superlibrarian
968 $session->param('number',0);
969 $session->param('id',C4::Context->config('user'));
970 $session->param('cardnumber',C4::Context->config('user'));
971 $session->param('firstname',C4::Context->config('user'));
972 $session->param('surname',C4::Context->config('user'));
973 $session->param('branch','NO_LIBRARY_SET');
974 $session->param('branchname','NO_LIBRARY_SET');
975 $session->param('flags',1);
976 $session->param('emailaddress', C4::Context->preference('KohaAdminEmailAddress'));
977 $session->param('ip',$session->remote_addr());
978 $session->param('lasttime',time());
980 C4::Context::set_userenv(
981 $session->param('number'), $session->param('id'),
982 $session->param('cardnumber'), $session->param('firstname'),
983 $session->param('surname'), $session->param('branch'),
984 $session->param('branchname'), $session->param('flags'),
985 $session->param('emailaddress'), $session->param('branchprinter')
987 return ("ok", $cookie, $sessionID);
989 return ("failed", undef, undef);
994 =item check_cookie_auth
996 ($status, $sessionId) = check_api_auth($cookie, $userflags);
998 Given a CGISESSID cookie set during a previous login to Koha, determine
999 if the user has the privileges specified by C<$userflags>.
1001 C<check_cookie_auth> is meant for authenticating special services
1002 such as tools/upload-file.pl that are invoked by other pages that
1003 have been authenticated in the usual way.
1005 Possible return values in C<$status> are:
1009 =item "ok" -- user authenticated; C<$sessionID> have valid values.
1011 =item "failed" -- credentials are not correct; C<$sessionid> are undef
1013 =item "maintenance" -- DB is in maintenance mode; no login possible at the moment
1015 =item "expired -- session cookie has expired; API user should resubmit userid and password
1021 sub check_cookie_auth {
1023 my $flagsrequired = shift;
1025 my $dbh = C4::Context->dbh;
1026 my $timeout = C4::Context->preference('timeout');
1027 $timeout = 600 unless $timeout;
1029 unless (C4::Context->preference('Version')) {
1030 # database has not been installed yet
1031 return ("maintenance", undef);
1033 my $kohaversion=C4::Context::KOHAVERSION;
1034 $kohaversion =~ s/(.*\..*)\.(.*)\.(.*)/$1$2$3/;
1035 if (C4::Context->preference('Version') < $kohaversion) {
1036 # database in need of version update; assume that
1037 # no API should be called while databsae is in
1039 return ("maintenance", undef);
1042 # FIXME -- most of what follows is a copy-and-paste
1043 # of code from checkauth. There is an obvious need
1044 # for refactoring to separate the various parts of
1045 # the authentication code, but as of 2007-11-23 this
1046 # is deferred so as to not introduce bugs into the
1047 # regular authentication code for Koha 3.0.
1049 # see if we have a valid session cookie already
1050 # however, if a userid parameter is present (i.e., from
1051 # a form submission, assume that any current cookie
1053 unless (defined $cookie and $cookie) {
1054 return ("failed", undef);
1056 my $sessionID = $cookie;
1057 my $session = get_session($sessionID);
1058 C4::Context->_new_userenv($sessionID);
1060 C4::Context::set_userenv(
1061 $session->param('number'), $session->param('id'),
1062 $session->param('cardnumber'), $session->param('firstname'),
1063 $session->param('surname'), $session->param('branch'),
1064 $session->param('branchname'), $session->param('flags'),
1065 $session->param('emailaddress'), $session->param('branchprinter')
1068 my $ip = $session->param('ip');
1069 my $lasttime = $session->param('lasttime');
1070 my $userid = $session->param('id');
1071 if ( $lasttime < time() - $timeout ) {
1074 C4::Context->_unset_userenv($sessionID);
1077 return ("expired", undef);
1078 } elsif ( $ip ne $ENV{'REMOTE_ADDR'} ) {
1079 # IP address changed
1081 C4::Context->_unset_userenv($sessionID);
1084 return ("expired", undef);
1086 $session->param('lasttime',time());
1087 my $flags = haspermission( $dbh, $userid, $flagsrequired );
1089 return ("ok", $sessionID);
1092 C4::Context->_unset_userenv($sessionID);
1095 return ("failed", undef);
1099 return ("expired", undef);
1106 my $session = get_session($sessionID);
1108 Given a session ID, retrieve the CGI::Session object used to store
1109 the session's state. The session object can be used to store
1110 data that needs to be accessed by different scripts during a
1113 If the C<$sessionID> parameter is an empty string, a new session
1119 my $sessionID = shift;
1120 my $storage_method = C4::Context->preference('SessionStorage');
1121 my $dbh = C4::Context->dbh;
1123 if ($storage_method eq 'mysql'){
1124 $session = new CGI::Session("driver:MySQL;serializer:yaml", $sessionID, {Handle=>$dbh});
1126 elsif ($storage_method eq 'Pg') {
1127 $session = new CGI::Session("driver:PostgreSQL;serializer:yaml", $sessionID, {Handle=>$dbh});
1130 # catch all defaults to tmp should work on all systems
1131 $session = new CGI::Session("driver:File;serializer:yaml", $sessionID, {Directory=>'/tmp'});
1138 my ( $dbh, $userid, $password ) = @_;
1140 $debug and print "## checkpw - checking LDAP\n";
1141 my ($retval,$retcard) = checkpw_ldap(@_); # EXTERNAL AUTH
1142 ($retval) and return ($retval,$retcard);
1148 "select password,cardnumber,borrowernumber,userid,firstname,surname,branchcode,flags from borrowers where userid=?"
1150 $sth->execute($userid);
1152 my ( $md5password, $cardnumber, $borrowernumber, $userid, $firstname,
1153 $surname, $branchcode, $flags )
1155 if ( md5_base64($password) eq $md5password ) {
1157 C4::Context->set_userenv( "$borrowernumber", $userid, $cardnumber,
1158 $firstname, $surname, $branchcode, $flags );
1159 return 1, $cardnumber;
1164 "select password,cardnumber,borrowernumber,userid, firstname,surname,branchcode,flags from borrowers where cardnumber=?"
1166 $sth->execute($userid);
1168 my ( $md5password, $cardnumber, $borrowernumber, $userid, $firstname,
1169 $surname, $branchcode, $flags )
1171 if ( md5_base64($password) eq $md5password ) {
1173 C4::Context->set_userenv( $borrowernumber, $userid, $cardnumber,
1174 $firstname, $surname, $branchcode, $flags );
1178 if ( $userid && $userid eq C4::Context->config('user')
1179 && "$password" eq C4::Context->config('pass') )
1182 # Koha superuser account
1183 # C4::Context->set_userenv(0,0,C4::Context->config('user'),C4::Context->config('user'),C4::Context->config('user'),"",1);
1186 if ( $userid && $userid eq 'demo'
1187 && "$password" eq 'demo'
1188 && C4::Context->config('demo') )
1191 # DEMO => the demo user is allowed to do everything (if demo set to 1 in koha.conf
1192 # some features won't be effective : modify systempref, modify MARC structure,
1200 $authflags = getuserflags($flags,$dbh);
1201 Translates integer flags into permissions strings hash.
1203 C<$flags> is the integer userflags value ( borrowers.userflags )
1204 C<$authflags> is a hashref of permissions
1213 $flags = 0 unless $flags;
1214 my $sth = $dbh->prepare("SELECT bit, flag, defaulton FROM userflags");
1217 while ( my ( $bit, $flag, $defaulton ) = $sth->fetchrow ) {
1218 if ( ( $flags & ( 2**$bit ) ) || $defaulton ) {
1219 $userflags->{$flag} = 1;
1222 $userflags->{$flag} = 0;
1226 # get subpermissions and merge with top-level permissions
1227 my $user_subperms = get_user_subpermissions($userid);
1228 foreach my $module (keys %$user_subperms) {
1229 next if $userflags->{$module} == 1; # user already has permission for everything in this module
1230 $userflags->{$module} = $user_subperms->{$module};
1236 =item get_user_subpermissions
1240 my $user_perm_hashref = get_user_subpermissions($userid);
1244 Given the userid (note, not the borrowernumber) of a staff user,
1245 return a hashref of hashrefs of the specific subpermissions
1246 accorded to the user. An example return is
1250 export_catalog => 1,
1251 import_patrons => 1,
1255 The top-level hash-key is a module or function code from
1256 userflags.flag, while the second-level key is a code
1259 The results of this function do not give a complete picture
1260 of the functions that a staff user can access; it is also
1261 necessary to check borrowers.flags.
1265 sub get_user_subpermissions {
1268 my $dbh = C4::Context->dbh;
1269 my $sth = $dbh->prepare("SELECT flag, code
1270 FROM user_permissions
1271 JOIN permissions USING (module_bit, code)
1272 JOIN userflags ON (module_bit = bit)
1273 JOIN borrowers USING (borrowernumber)
1275 $sth->execute($userid);
1277 my $user_perms = {};
1278 while (my $perm = $sth->fetchrow_hashref) {
1279 $user_perms->{$perm->{'flag'}}->{$perm->{'code'}} = 1;
1284 =item get_all_subpermissions
1288 my $perm_hashref = get_all_subpermissions();
1292 Returns a hashref of hashrefs defining all specific
1293 permissions currently defined. The return value
1294 has the same structure as that of C<get_user_subpermissions>,
1295 except that the innermost hash value is the description
1296 of the subpermission.
1300 sub get_all_subpermissions {
1301 my $dbh = C4::Context->dbh;
1302 my $sth = $dbh->prepare("SELECT flag, code, description
1304 JOIN userflags ON (module_bit = bit)");
1308 while (my $perm = $sth->fetchrow_hashref) {
1309 $all_perms->{$perm->{'flag'}}->{$perm->{'code'}} = $perm->{'description'};
1316 $flags = ($dbh,$member,$flagsrequired);
1318 C<$member> may be either userid or overloaded with $borrower hashref from GetMemberDetails.
1319 C<$flags> is a hashref of required flags like C<$borrower-<{authflags}>
1321 Returns member's flags or 0 if a permission is not met.
1326 my ( $dbh, $userid, $flagsrequired ) = @_;
1327 my ($flags,$intflags);
1328 $dbh=C4::Context->dbh unless($dbh);
1330 $intflags = $userid->{'flags'};
1332 my $sth = $dbh->prepare("SELECT flags FROM borrowers WHERE userid=?");
1333 $sth->execute($userid);
1334 my ($intflags) = $sth->fetchrow;
1335 $flags = getuserflags( $intflags, $userid, $dbh );
1337 if ( $userid eq C4::Context->config('user') ) {
1338 # Super User Account from /etc/koha.conf
1339 $flags->{'superlibrarian'} = 1;
1341 if ( $userid eq 'demo' && C4::Context->config('demo') ) {
1342 # Demo user that can do "anything" (demo=1 in /etc/koha.conf)
1343 $flags->{'superlibrarian'} = 1;
1345 return $flags if $flags->{superlibrarian};
1346 foreach my $module ( keys %$flagsrequired ) {
1347 if (C4::Context->preference('GranularPermissions')) {
1348 my $subperm = $flagsrequired->{$module};
1349 if ($subperm eq '*') {
1350 return 0 unless ( $flags->{$module} == 1 or ref($flags->{$module}) );
1352 return 0 unless ( $flags->{$module} == 1 or
1353 ( ref($flags->{$module}) and
1354 exists $flags->{$module}->{$subperm} and
1355 $flags->{$module}->{$subperm} == 1
1360 return 0 unless ( $flags->{$module} );
1364 #FIXME - This fcn should return the failed permission so a suitable error msg can be delivered.
1368 sub getborrowernumber {
1370 my $dbh = C4::Context->dbh;
1371 for my $field ( 'userid', 'cardnumber' ) {
1373 $dbh->prepare("select borrowernumber from borrowers where $field=?");
1374 $sth->execute($userid);
1376 my ($bnumber) = $sth->fetchrow;
1383 END { } # module clean-up code here (global destructor)