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"),
327 $template->param(listloop=>[{shelfname=>"Freelist", shelfnumber=>110}]);
328 return ( $template, $borrowernumber, $cookie, $flags);
333 ($userid, $cookie, $sessionID) = &checkauth($query, $noauth, $flagsrequired, $type);
335 Verifies that the user is authorized to run this script. If
336 the user is authorized, a (userid, cookie, session-id, flags)
337 quadruple is returned. If the user is not authorized but does
338 not have the required privilege (see $flagsrequired below), it
339 displays an error page and exits. Otherwise, it displays the
340 login page and exits.
342 Note that C<&checkauth> will return if and only if the user
343 is authorized, so it should be called early on, before any
344 unfinished operations (e.g., if you've opened a file, then
345 C<&checkauth> won't close it for you).
347 C<$query> is the CGI object for the script calling C<&checkauth>.
349 The C<$noauth> argument is optional. If it is set, then no
350 authorization is required for the script.
352 C<&checkauth> fetches user and session information from C<$query> and
353 ensures that the user is authorized to run scripts that require
356 The C<$flagsrequired> argument specifies the required privileges
357 the user must have if the username and password are correct.
358 It should be specified as a reference-to-hash; keys in the hash
359 should be the "flags" for the user, as specified in the Members
360 intranet module. Any key specified must correspond to a "flag"
361 in the userflags table. E.g., { circulate => 1 } would specify
362 that the user must have the "circulate" privilege in order to
363 proceed. To make sure that access control is correct, the
364 C<$flagsrequired> parameter must be specified correctly.
366 If the GranularPermissions system preference is ON, the
367 value of each key in the C<flagsrequired> hash takes on an additional
372 The user must have access to all subfunctions of the module
373 specified by the hash key.
377 The user must have access to at least one subfunction of the module
378 specified by the hash key.
380 =item specific permission, e.g., 'export_catalog'
382 The user must have access to the specific subfunction list, which
383 must correspond to a row in the permissions table.
385 The C<$type> argument specifies whether the template should be
386 retrieved from the opac or intranet directory tree. "opac" is
387 assumed if it is not specified; however, if C<$type> is specified,
388 "intranet" is assumed if it is not "opac".
390 If C<$query> does not have a valid session ID associated with it
391 (i.e., the user has not logged in) or if the session has expired,
392 C<&checkauth> presents the user with a login page (from the point of
393 view of the original script, C<&checkauth> does not return). Once the
394 user has authenticated, C<&checkauth> restarts the original script
395 (this time, C<&checkauth> returns).
397 The login page is provided using a HTML::Template, which is set in the
398 systempreferences table or at the top of this file. The variable C<$type>
399 selects which template to use, either the opac or the intranet
400 authentification template.
402 C<&checkauth> returns a user ID, a cookie, and a session ID. The
403 cookie should be sent back to the browser; it verifies that the user
408 sub _version_check ($$) {
412 # If Version syspref is unavailable, it means Koha is beeing installed,
413 # and so we must redirect to OPAC maintenance page or to the WebInstaller
414 #warn "about to check version";
415 unless ($version = C4::Context->preference('Version')) { # assignment, not comparison
416 if ($type ne 'opac') {
417 warn "Install required, redirecting to Installer";
418 print $query->redirect("/cgi-bin/koha/installer/install.pl");
421 warn "OPAC Install required, redirecting to maintenance";
422 print $query->redirect("/cgi-bin/koha/maintenance.pl");
427 # check that database and koha version are the same
428 # there is no DB version, it's a fresh install,
429 # go to web installer
430 # there is a DB version, compare it to the code version
431 my $kohaversion=C4::Context::KOHAVERSION;
432 # remove the 3 last . to have a Perl number
433 $kohaversion =~ s/(.*\..*)\.(.*)\.(.*)/$1$2$3/;
434 $debug and print STDERR "kohaversion : $kohaversion\n";
435 if ($version < $kohaversion){
436 my $warning = "Database update needed, redirecting to %s. Database is $version and Koha is $kohaversion";
437 if ($type ne 'opac'){
438 warn sprintf($warning, 'Installer');
439 print $query->redirect("/cgi-bin/koha/installer/install.pl?step=3");
441 warn sprintf("OPAC: " . $warning, 'maintenance');
442 print $query->redirect("/cgi-bin/koha/maintenance.pl");
450 open L, ">>/tmp/sessionlog" or warn "ERROR: Cannot append to /tmp/sessionlog";
451 printf L join("\n",@_);
457 $debug and warn "Checking Auth";
458 # $authnotrequired will be set for scripts which will run without authentication
459 my $authnotrequired = shift;
460 my $flagsrequired = shift;
462 $type = 'opac' unless $type;
464 my $dbh = C4::Context->dbh;
465 my $timeout = C4::Context->preference('timeout');
467 if ($timeout =~ /(\d+)[dD]/) {
468 $timeout = $1 * 86400;
470 $timeout = 600 unless $timeout;
472 _version_check($type,$query);
476 my ( $userid, $cookie, $sessionID, $flags, $shelves );
477 my $logout = $query->param('logout.x');
478 if ( $userid = $ENV{'REMOTE_USER'} ) {
479 # Using Basic Authentication, no cookies required
480 $cookie = $query->cookie(
481 -name => 'CGISESSID',
487 elsif ( $sessionID = $query->cookie("CGISESSID")) { # assignment, not comparison
488 my $session = get_session($sessionID);
489 C4::Context->_new_userenv($sessionID);
492 C4::Context::set_userenv(
493 $session->param('number'), $session->param('id'),
494 $session->param('cardnumber'), $session->param('firstname'),
495 $session->param('surname'), $session->param('branch'),
496 $session->param('branchname'), $session->param('flags'),
497 $session->param('emailaddress'), $session->param('branchprinter')
499 C4::Context::set_shelves_userenv($session->param('shelves'));
500 $debug and printf STDERR "AUTH_SESSION: (%s)\t%s %s - %s\n", map {$session->param($_)} qw(cardnumber firstname surname branch) ;
501 $ip = $session->param('ip');
502 $lasttime = $session->param('lasttime');
503 $userid = $session->param('id');
507 # voluntary logout the user
510 C4::Context->_unset_userenv($sessionID);
511 _session_log(sprintf "%20s from %16s logged out at %30s (manually).\n", $userid,$ip,localtime);
515 elsif ( $lasttime < time() - $timeout ) {
517 $info{'timed_out'} = 1;
519 C4::Context->_unset_userenv($sessionID);
520 _session_log(sprintf "%20s from %16s logged out at %30s (inactivity).\n", $userid,$ip,localtime);
524 elsif ( $ip ne $ENV{'REMOTE_ADDR'} ) {
525 # Different ip than originally logged in from
526 $info{'oldip'} = $ip;
527 $info{'newip'} = $ENV{'REMOTE_ADDR'};
528 $info{'different_ip'} = 1;
530 C4::Context->_unset_userenv($sessionID);
531 _session_log(sprintf "%20s from %16s logged out at %30s (ip changed to %16s).\n", $userid,$ip,localtime, $info{'newip'});
536 $cookie = $query->cookie( CGISESSID => $session->id );
537 $session->param('lasttime',time());
538 $flags = haspermission( $dbh, $userid, $flagsrequired );
542 $info{'nopermission'} = 1;
547 my $session = get_session("") or die "Auth ERROR: Cannot get_session()";
548 my $sessionID = $session->id;
549 $userid = $query->param('userid');
550 my $password = $query->param('password');
551 C4::Context->_new_userenv($sessionID);
552 my ( $return, $cardnumber ) = checkpw( $dbh, $userid, $password );
554 _session_log(sprintf "%20s from %16s logged in at %30s.\n", $userid,$ENV{'REMOTE_ADDR'},localtime);
555 $cookie = $query->cookie(CGISESSID => $sessionID);
556 if ( $flags = haspermission( $dbh, $userid, $flagsrequired ) ) {
560 $info{'nopermission'} = 1;
561 C4::Context->_unset_userenv($sessionID);
564 my ($borrowernumber, $firstname, $surname, $userflags,
565 $branchcode, $branchname, $branchprinter, $emailaddress);
567 if ( $return == 1 ) {
569 SELECT borrowernumber, firstname, surname, flags, borrowers.branchcode,
570 branches.branchname as branchname,
571 branches.branchprinter as branchprinter,
574 LEFT JOIN branches on borrowers.branchcode=branches.branchcode
576 my $sth = $dbh->prepare("$select where userid=?");
577 $sth->execute($userid);
578 unless ($sth->rows) {
579 $debug and print STDERR "AUTH_1: no rows for userid='$userid'\n";
580 $sth = $dbh->prepare("$select where cardnumber=?");
581 $sth->execute($cardnumber);
582 unless ($sth->rows) {
583 $debug and print STDERR "AUTH_2a: no rows for cardnumber='$cardnumber'\n";
584 $sth->execute($userid);
585 unless ($sth->rows) {
586 $debug and print STDERR "AUTH_2b: no rows for userid='$userid' AS cardnumber\n";
591 ($borrowernumber, $firstname, $surname, $userflags,
592 $branchcode, $branchname, $branchprinter, $emailaddress) = $sth->fetchrow;
593 $debug and print STDERR "AUTH_3 results: " .
594 "$cardnumber,$borrowernumber,$userid,$firstname,$surname,$userflags,$branchcode,$emailaddress\n";
596 print STDERR "AUTH_3: no results for userid='$userid', cardnumber='$cardnumber'.\n";
599 # launch a sequence to check if we have a ip for the branch, i
600 # if we have one we replace the branchcode of the userenv by the branch bound in the ip.
602 my $ip = $ENV{'REMOTE_ADDR'};
603 # if they specify at login, use that
604 if ($query->param('branch')) {
605 $branchcode = $query->param('branch');
606 $branchname = GetBranchName($branchcode);
608 my $branches = GetBranches();
609 if (C4::Context->boolean_preference('IndependantBranches') && C4::Context->boolean_preference('Autolocation')){
610 # we have to check they are coming from the right ip range
611 my $domain = $branches->{$branchcode}->{'branchip'};
612 if ($ip !~ /^$domain/){
614 $info{'wrongip'} = 1;
619 foreach my $br ( keys %$branches ) {
620 # now we work with the treatment of ip
621 my $domain = $branches->{$br}->{'branchip'};
622 if ( $domain && $ip =~ /^$domain/ ) {
623 $branchcode = $branches->{$br}->{'branchcode'};
625 # new op dev : add the branchprinter and branchname in the cookie
626 $branchprinter = $branches->{$br}->{'branchprinter'};
627 $branchname = $branches->{$br}->{'branchname'};
630 $session->param('number',$borrowernumber);
631 $session->param('id',$userid);
632 $session->param('cardnumber',$cardnumber);
633 $session->param('firstname',$firstname);
634 $session->param('surname',$surname);
635 $session->param('branch',$branchcode);
636 $session->param('branchname',$branchname);
637 $session->param('flags',$userflags);
638 $session->param('emailaddress',$emailaddress);
639 $session->param('ip',$session->remote_addr());
640 $session->param('lasttime',time());
641 $debug and printf STDERR "AUTH_4: (%s)\t%s %s - %s\n", map {$session->param($_)} qw(cardnumber firstname surname branch) ;
643 elsif ( $return == 2 ) {
644 #We suppose the user is the superlibrarian
646 $session->param('number',0);
647 $session->param('id',C4::Context->config('user'));
648 $session->param('cardnumber',C4::Context->config('user'));
649 $session->param('firstname',C4::Context->config('user'));
650 $session->param('surname',C4::Context->config('user'));
651 $session->param('branch','NO_LIBRARY_SET');
652 $session->param('branchname','NO_LIBRARY_SET');
653 $session->param('flags',1);
654 $session->param('emailaddress', C4::Context->preference('KohaAdminEmailAddress'));
655 $session->param('ip',$session->remote_addr());
656 $session->param('lasttime',time());
658 C4::Context::set_userenv(
659 $session->param('number'), $session->param('id'),
660 $session->param('cardnumber'), $session->param('firstname'),
661 $session->param('surname'), $session->param('branch'),
662 $session->param('branchname'), $session->param('flags'),
663 $session->param('emailaddress'), $session->param('branchprinter')
665 $shelves = GetShelvesSummary($borrowernumber,2,10);
666 $session->param('shelves', $shelves);
667 C4::Context::set_shelves_userenv($shelves);
671 $info{'invalid_username_or_password'} = 1;
672 C4::Context->_unset_userenv($sessionID);
676 } # END unless ($userid)
677 my $insecure = C4::Context->boolean_preference('insecure');
679 # finished authentification, now respond
680 if ( $loggedin || $authnotrequired || ( defined($insecure) && $insecure ) )
684 $cookie = $query->cookie( CGISESSID => '' );
686 return ( $userid, $cookie, $sessionID, $flags );
691 # AUTH rejected, show the login/password template, after checking the DB.
695 # get the inputs from the incoming query
697 foreach my $name ( param $query) {
698 (next) if ( $name eq 'userid' || $name eq 'password' );
699 my $value = $query->param($name);
700 push @inputs, { name => $name, value => $value };
702 # get the branchloop, which we need for authentication
703 my $branches = GetBranches();
705 for my $branch_hash (sort keys %$branches) {
706 push @branch_loop, {branchcode => "$branch_hash", branchname => $branches->{$branch_hash}->{'branchname'}, };
709 my $template_name = ( $type eq 'opac' ) ? 'opac-auth.tmpl' : 'auth.tmpl';
710 my $template = gettemplate( $template_name, $type, $query );
711 $template->param(branchloop => \@branch_loop,);
715 suggestion => C4::Context->preference("suggestion"),
716 virtualshelves => C4::Context->preference("virtualshelves"),
717 opaclargeimage => C4::Context->preference("opaclargeimage"),
718 LibraryName => C4::Context->preference("LibraryName"),
719 opacuserlogin => C4::Context->preference("opacuserlogin"),
720 OpacNav => C4::Context->preference("OpacNav"),
721 opaccredits => C4::Context->preference("opaccredits"),
722 opacreadinghistory => C4::Context->preference("opacreadinghistory"),
723 opacsmallimage => C4::Context->preference("opacsmallimage"),
724 opaclayoutstylesheet => C4::Context->preference("opaclayoutstylesheet"),
725 opaccolorstylesheet => C4::Context->preference("opaccolorstylesheet"),
726 opaclanguagesdisplay => C4::Context->preference("opaclanguagesdisplay"),
727 opacuserjs => C4::Context->preference("opacuserjs"),
728 opacbookbag => "" . C4::Context->preference("opacbookbag"),
729 OpacCloud => C4::Context->preference("OpacCloud"),
730 OpacTopissue => C4::Context->preference("OpacTopissue"),
731 OpacAuthorities => C4::Context->preference("OpacAuthorities"),
732 OpacBrowser => C4::Context->preference("OpacBrowser"),
733 opacheader => C4::Context->preference("opacheader"),
734 OPACUserCSS => C4::Context->preference("OPACUserCSS"),
735 intranetcolorstylesheet =>
736 C4::Context->preference("intranetcolorstylesheet"),
737 intranetstylesheet => C4::Context->preference("intranetstylesheet"),
738 IntranetNav => C4::Context->preference("IntranetNav"),
739 intranetuserjs => C4::Context->preference("intranetuserjs"),
740 TemplateEncoding => C4::Context->preference("TemplateEncoding"),
741 IndependantBranches=> C4::Context->preference("IndependantBranches"),
742 AutoLocation => C4::Context->preference("AutoLocation"),
743 yuipath => C4::Context->preference("yuipath"),
744 wrongip => $info{'wrongip'}
747 $template->param( loginprompt => 1 ) unless $info{'nopermission'};
749 my $self_url = $query->url( -absolute => 1 );
752 LibraryName => C4::Context->preference("LibraryName"),
754 $template->param( \%info );
755 # $cookie = $query->cookie(CGISESSID => $session->id
757 print $query->header(
758 -type => 'text/html',
768 ($status, $cookie, $sessionId) = check_api_auth($query, $userflags);
770 Given a CGI query containing the parameters 'userid' and 'password' and/or a session
771 cookie, determine if the user has the privileges specified by C<$userflags>.
773 C<check_api_auth> is is meant for authenticating users of web services, and
774 consequently will always return and will not attempt to redirect the user
777 If a valid session cookie is already present, check_api_auth will return a status
778 of "ok", the cookie, and the Koha session ID.
780 If no session cookie is present, check_api_auth will check the 'userid' and 'password
781 parameters and create a session cookie and Koha session if the supplied credentials
784 Possible return values in C<$status> are:
788 =item "ok" -- user authenticated; C<$cookie> and C<$sessionid> have valid values.
790 =item "failed" -- credentials are not correct; C<$cookie> and C<$sessionid> are undef
792 =item "maintenance" -- DB is in maintenance mode; no login possible at the moment
794 =item "expired -- session cookie has expired; API user should resubmit userid and password
802 my $flagsrequired = shift;
804 my $dbh = C4::Context->dbh;
805 my $timeout = C4::Context->preference('timeout');
806 $timeout = 600 unless $timeout;
808 unless (C4::Context->preference('Version')) {
809 # database has not been installed yet
810 return ("maintenance", undef, undef);
812 my $kohaversion=C4::Context::KOHAVERSION;
813 $kohaversion =~ s/(.*\..*)\.(.*)\.(.*)/$1$2$3/;
814 if (C4::Context->preference('Version') < $kohaversion) {
815 # database in need of version update; assume that
816 # no API should be called while databsae is in
818 return ("maintenance", undef, undef);
821 # FIXME -- most of what follows is a copy-and-paste
822 # of code from checkauth. There is an obvious need
823 # for refactoring to separate the various parts of
824 # the authentication code, but as of 2007-11-19 this
825 # is deferred so as to not introduce bugs into the
826 # regular authentication code for Koha 3.0.
828 # see if we have a valid session cookie already
829 # however, if a userid parameter is present (i.e., from
830 # a form submission, assume that any current cookie
832 my $sessionID = undef;
833 unless ($query->param('userid')) {
834 $sessionID = $query->cookie("CGISESSID");
837 my $session = get_session($sessionID);
838 C4::Context->_new_userenv($sessionID);
840 C4::Context::set_userenv(
841 $session->param('number'), $session->param('id'),
842 $session->param('cardnumber'), $session->param('firstname'),
843 $session->param('surname'), $session->param('branch'),
844 $session->param('branchname'), $session->param('flags'),
845 $session->param('emailaddress'), $session->param('branchprinter')
848 my $ip = $session->param('ip');
849 my $lasttime = $session->param('lasttime');
850 my $userid = $session->param('id');
851 if ( $lasttime < time() - $timeout ) {
854 C4::Context->_unset_userenv($sessionID);
857 return ("expired", undef, undef);
858 } elsif ( $ip ne $ENV{'REMOTE_ADDR'} ) {
861 C4::Context->_unset_userenv($sessionID);
864 return ("expired", undef, undef);
866 my $cookie = $query->cookie( CGISESSID => $session->id );
867 $session->param('lasttime',time());
868 my $flags = haspermission( $dbh, $userid, $flagsrequired );
870 return ("ok", $cookie, $sessionID);
873 C4::Context->_unset_userenv($sessionID);
876 return ("failed", undef, undef);
880 return ("expired", undef, undef);
884 my $userid = $query->param('userid');
885 my $password = $query->param('password');
886 unless ($userid and $password) {
887 # caller did something wrong, fail the authenticateion
888 return ("failed", undef, undef);
890 my ( $return, $cardnumber ) = checkpw( $dbh, $userid, $password );
891 if ($return and haspermission( $dbh, $userid, $flagsrequired)) {
892 my $session = get_session("");
893 return ("failed", undef, undef) unless $session;
895 my $sessionID = $session->id;
896 C4::Context->_new_userenv($sessionID);
897 my $cookie = $query->cookie(CGISESSID => $sessionID);
898 if ( $return == 1 ) {
900 $borrowernumber, $firstname, $surname,
901 $userflags, $branchcode, $branchname,
902 $branchprinter, $emailaddress
906 "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=?"
908 $sth->execute($userid);
910 $borrowernumber, $firstname, $surname,
911 $userflags, $branchcode, $branchname,
912 $branchprinter, $emailaddress
913 ) = $sth->fetchrow if ( $sth->rows );
915 unless ($sth->rows ) {
916 my $sth = $dbh->prepare(
917 "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=?"
919 $sth->execute($cardnumber);
921 $borrowernumber, $firstname, $surname,
922 $userflags, $branchcode, $branchname,
923 $branchprinter, $emailaddress
924 ) = $sth->fetchrow if ( $sth->rows );
926 unless ( $sth->rows ) {
927 $sth->execute($userid);
929 $borrowernumber, $firstname, $surname, $userflags,
930 $branchcode, $branchname, $branchprinter, $emailaddress
931 ) = $sth->fetchrow if ( $sth->rows );
935 my $ip = $ENV{'REMOTE_ADDR'};
936 # if they specify at login, use that
937 if ($query->param('branch')) {
938 $branchcode = $query->param('branch');
939 $branchname = GetBranchName($branchcode);
941 my $branches = GetBranches();
943 foreach my $br ( keys %$branches ) {
944 # now we work with the treatment of ip
945 my $domain = $branches->{$br}->{'branchip'};
946 if ( $domain && $ip =~ /^$domain/ ) {
947 $branchcode = $branches->{$br}->{'branchcode'};
949 # new op dev : add the branchprinter and branchname in the cookie
950 $branchprinter = $branches->{$br}->{'branchprinter'};
951 $branchname = $branches->{$br}->{'branchname'};
954 $session->param('number',$borrowernumber);
955 $session->param('id',$userid);
956 $session->param('cardnumber',$cardnumber);
957 $session->param('firstname',$firstname);
958 $session->param('surname',$surname);
959 $session->param('branch',$branchcode);
960 $session->param('branchname',$branchname);
961 $session->param('flags',$userflags);
962 $session->param('emailaddress',$emailaddress);
963 $session->param('ip',$session->remote_addr());
964 $session->param('lasttime',time());
965 } elsif ( $return == 2 ) {
966 #We suppose the user is the superlibrarian
967 $session->param('number',0);
968 $session->param('id',C4::Context->config('user'));
969 $session->param('cardnumber',C4::Context->config('user'));
970 $session->param('firstname',C4::Context->config('user'));
971 $session->param('surname',C4::Context->config('user'));
972 $session->param('branch','NO_LIBRARY_SET');
973 $session->param('branchname','NO_LIBRARY_SET');
974 $session->param('flags',1);
975 $session->param('emailaddress', C4::Context->preference('KohaAdminEmailAddress'));
976 $session->param('ip',$session->remote_addr());
977 $session->param('lasttime',time());
979 C4::Context::set_userenv(
980 $session->param('number'), $session->param('id'),
981 $session->param('cardnumber'), $session->param('firstname'),
982 $session->param('surname'), $session->param('branch'),
983 $session->param('branchname'), $session->param('flags'),
984 $session->param('emailaddress'), $session->param('branchprinter')
986 return ("ok", $cookie, $sessionID);
988 return ("failed", undef, undef);
993 =item check_cookie_auth
995 ($status, $sessionId) = check_api_auth($cookie, $userflags);
997 Given a CGISESSID cookie set during a previous login to Koha, determine
998 if the user has the privileges specified by C<$userflags>.
1000 C<check_cookie_auth> is meant for authenticating special services
1001 such as tools/upload-file.pl that are invoked by other pages that
1002 have been authenticated in the usual way.
1004 Possible return values in C<$status> are:
1008 =item "ok" -- user authenticated; C<$sessionID> have valid values.
1010 =item "failed" -- credentials are not correct; C<$sessionid> are undef
1012 =item "maintenance" -- DB is in maintenance mode; no login possible at the moment
1014 =item "expired -- session cookie has expired; API user should resubmit userid and password
1020 sub check_cookie_auth {
1022 my $flagsrequired = shift;
1024 my $dbh = C4::Context->dbh;
1025 my $timeout = C4::Context->preference('timeout');
1026 $timeout = 600 unless $timeout;
1028 unless (C4::Context->preference('Version')) {
1029 # database has not been installed yet
1030 return ("maintenance", undef);
1032 my $kohaversion=C4::Context::KOHAVERSION;
1033 $kohaversion =~ s/(.*\..*)\.(.*)\.(.*)/$1$2$3/;
1034 if (C4::Context->preference('Version') < $kohaversion) {
1035 # database in need of version update; assume that
1036 # no API should be called while databsae is in
1038 return ("maintenance", undef);
1041 # FIXME -- most of what follows is a copy-and-paste
1042 # of code from checkauth. There is an obvious need
1043 # for refactoring to separate the various parts of
1044 # the authentication code, but as of 2007-11-23 this
1045 # is deferred so as to not introduce bugs into the
1046 # regular authentication code for Koha 3.0.
1048 # see if we have a valid session cookie already
1049 # however, if a userid parameter is present (i.e., from
1050 # a form submission, assume that any current cookie
1052 unless (defined $cookie and $cookie) {
1053 return ("failed", undef);
1055 my $sessionID = $cookie;
1056 my $session = get_session($sessionID);
1057 C4::Context->_new_userenv($sessionID);
1059 C4::Context::set_userenv(
1060 $session->param('number'), $session->param('id'),
1061 $session->param('cardnumber'), $session->param('firstname'),
1062 $session->param('surname'), $session->param('branch'),
1063 $session->param('branchname'), $session->param('flags'),
1064 $session->param('emailaddress'), $session->param('branchprinter')
1067 my $ip = $session->param('ip');
1068 my $lasttime = $session->param('lasttime');
1069 my $userid = $session->param('id');
1070 if ( $lasttime < time() - $timeout ) {
1073 C4::Context->_unset_userenv($sessionID);
1076 return ("expired", undef);
1077 } elsif ( $ip ne $ENV{'REMOTE_ADDR'} ) {
1078 # IP address changed
1080 C4::Context->_unset_userenv($sessionID);
1083 return ("expired", undef);
1085 $session->param('lasttime',time());
1086 my $flags = haspermission( $dbh, $userid, $flagsrequired );
1088 return ("ok", $sessionID);
1091 C4::Context->_unset_userenv($sessionID);
1094 return ("failed", undef);
1098 return ("expired", undef);
1105 my $session = get_session($sessionID);
1107 Given a session ID, retrieve the CGI::Session object used to store
1108 the session's state. The session object can be used to store
1109 data that needs to be accessed by different scripts during a
1112 If the C<$sessionID> parameter is an empty string, a new session
1118 my $sessionID = shift;
1119 my $storage_method = C4::Context->preference('SessionStorage');
1120 my $dbh = C4::Context->dbh;
1122 if ($storage_method eq 'mysql'){
1123 $session = new CGI::Session("driver:MySQL;serializer:yaml", $sessionID, {Handle=>$dbh});
1125 elsif ($storage_method eq 'Pg') {
1126 $session = new CGI::Session("driver:PostgreSQL;serializer:yaml", $sessionID, {Handle=>$dbh});
1129 # catch all defaults to tmp should work on all systems
1130 $session = new CGI::Session("driver:File;serializer:yaml", $sessionID, {Directory=>'/tmp'});
1137 my ( $dbh, $userid, $password ) = @_;
1139 $debug and print "## checkpw - checking LDAP\n";
1140 my ($retval,$retcard) = checkpw_ldap(@_); # EXTERNAL AUTH
1141 ($retval) and return ($retval,$retcard);
1147 "select password,cardnumber,borrowernumber,userid,firstname,surname,branchcode,flags from borrowers where userid=?"
1149 $sth->execute($userid);
1151 my ( $md5password, $cardnumber, $borrowernumber, $userid, $firstname,
1152 $surname, $branchcode, $flags )
1154 if ( md5_base64($password) eq $md5password ) {
1156 C4::Context->set_userenv( "$borrowernumber", $userid, $cardnumber,
1157 $firstname, $surname, $branchcode, $flags );
1158 return 1, $cardnumber;
1163 "select password,cardnumber,borrowernumber,userid, firstname,surname,branchcode,flags from borrowers where cardnumber=?"
1165 $sth->execute($userid);
1167 my ( $md5password, $cardnumber, $borrowernumber, $userid, $firstname,
1168 $surname, $branchcode, $flags )
1170 if ( md5_base64($password) eq $md5password ) {
1172 C4::Context->set_userenv( $borrowernumber, $userid, $cardnumber,
1173 $firstname, $surname, $branchcode, $flags );
1177 if ( $userid && $userid eq C4::Context->config('user')
1178 && "$password" eq C4::Context->config('pass') )
1181 # Koha superuser account
1182 # C4::Context->set_userenv(0,0,C4::Context->config('user'),C4::Context->config('user'),C4::Context->config('user'),"",1);
1185 if ( $userid && $userid eq 'demo'
1186 && "$password" eq 'demo'
1187 && C4::Context->config('demo') )
1190 # DEMO => the demo user is allowed to do everything (if demo set to 1 in koha.conf
1191 # some features won't be effective : modify systempref, modify MARC structure,
1199 $authflags = getuserflags($flags,$dbh);
1200 Translates integer flags into permissions strings hash.
1202 C<$flags> is the integer userflags value ( borrowers.userflags )
1203 C<$authflags> is a hashref of permissions
1212 $flags = 0 unless $flags;
1213 my $sth = $dbh->prepare("SELECT bit, flag, defaulton FROM userflags");
1216 while ( my ( $bit, $flag, $defaulton ) = $sth->fetchrow ) {
1217 if ( ( $flags & ( 2**$bit ) ) || $defaulton ) {
1218 $userflags->{$flag} = 1;
1221 $userflags->{$flag} = 0;
1225 # get subpermissions and merge with top-level permissions
1226 my $user_subperms = get_user_subpermissions($userid);
1227 foreach my $module (keys %$user_subperms) {
1228 next if $userflags->{$module} == 1; # user already has permission for everything in this module
1229 $userflags->{$module} = $user_subperms->{$module};
1235 =item get_user_subpermissions
1239 my $user_perm_hashref = get_user_subpermissions($userid);
1243 Given the userid (note, not the borrowernumber) of a staff user,
1244 return a hashref of hashrefs of the specific subpermissions
1245 accorded to the user. An example return is
1249 export_catalog => 1,
1250 import_patrons => 1,
1254 The top-level hash-key is a module or function code from
1255 userflags.flag, while the second-level key is a code
1258 The results of this function do not give a complete picture
1259 of the functions that a staff user can access; it is also
1260 necessary to check borrowers.flags.
1264 sub get_user_subpermissions {
1267 my $dbh = C4::Context->dbh;
1268 my $sth = $dbh->prepare("SELECT flag, code
1269 FROM user_permissions
1270 JOIN permissions USING (module_bit, code)
1271 JOIN userflags ON (module_bit = bit)
1272 JOIN borrowers USING (borrowernumber)
1274 $sth->execute($userid);
1276 my $user_perms = {};
1277 while (my $perm = $sth->fetchrow_hashref) {
1278 $user_perms->{$perm->{'flag'}}->{$perm->{'code'}} = 1;
1283 =item get_all_subpermissions
1287 my $perm_hashref = get_all_subpermissions();
1291 Returns a hashref of hashrefs defining all specific
1292 permissions currently defined. The return value
1293 has the same structure as that of C<get_user_subpermissions>,
1294 except that the innermost hash value is the description
1295 of the subpermission.
1299 sub get_all_subpermissions {
1300 my $dbh = C4::Context->dbh;
1301 my $sth = $dbh->prepare("SELECT flag, code, description
1303 JOIN userflags ON (module_bit = bit)");
1307 while (my $perm = $sth->fetchrow_hashref) {
1308 $all_perms->{$perm->{'flag'}}->{$perm->{'code'}} = $perm->{'description'};
1315 $flags = ($dbh,$member,$flagsrequired);
1317 C<$member> may be either userid or overloaded with $borrower hashref from GetMemberDetails.
1318 C<$flags> is a hashref of required flags like C<$borrower-<{authflags}>
1320 Returns member's flags or 0 if a permission is not met.
1325 my ( $dbh, $userid, $flagsrequired ) = @_;
1326 my ($flags,$intflags);
1327 $dbh=C4::Context->dbh unless($dbh);
1329 $intflags = $userid->{'flags'};
1331 my $sth = $dbh->prepare("SELECT flags FROM borrowers WHERE userid=?");
1332 $sth->execute($userid);
1333 my ($intflags) = $sth->fetchrow;
1334 $flags = getuserflags( $intflags, $userid, $dbh );
1336 if ( $userid eq C4::Context->config('user') ) {
1337 # Super User Account from /etc/koha.conf
1338 $flags->{'superlibrarian'} = 1;
1340 if ( $userid eq 'demo' && C4::Context->config('demo') ) {
1341 # Demo user that can do "anything" (demo=1 in /etc/koha.conf)
1342 $flags->{'superlibrarian'} = 1;
1344 return $flags if $flags->{superlibrarian};
1345 foreach my $module ( keys %$flagsrequired ) {
1346 if (C4::Context->preference('GranularPermissions')) {
1347 my $subperm = $flagsrequired->{$module};
1348 if ($subperm eq '*') {
1349 return 0 unless ( $flags->{$module} == 1 or ref($flags->{$module}) );
1351 return 0 unless ( $flags->{$module} == 1 or
1352 ( ref($flags->{$module}) and
1353 exists $flags->{$module}->{$subperm} and
1354 $flags->{$module}->{$subperm} == 1
1359 return 0 unless ( $flags->{$module} );
1363 #FIXME - This fcn should return the failed permission so a suitable error msg can be delivered.
1367 sub getborrowernumber {
1369 my $dbh = C4::Context->dbh;
1370 for my $field ( 'userid', 'cardnumber' ) {
1372 $dbh->prepare("select borrowernumber from borrowers where $field=?");
1373 $sth->execute($userid);
1375 my ($bnumber) = $sth->fetchrow;
1382 END { } # module clean-up code here (global destructor)