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
65 my ($template, $borrowernumber, $cookie)
66 = get_template_and_user(
68 template_name => "opac-main.tmpl",
72 flagsrequired => {borrow => 1, catalogue => '*', tools => 'import_patrons' },
76 output_html_with_http_headers $query, $cookie, $template->output;
80 The main function of this module is to provide
81 authentification. However the get_template_and_user function has
82 been provided so that a users login information is passed along
83 automatically. This gets loaded into the template.
89 =item get_template_and_user
91 my ($template, $borrowernumber, $cookie)
92 = get_template_and_user(
94 template_name => "opac-main.tmpl",
98 flagsrequired => {borrow => 1, catalogue => '*', tools => 'import_patrons' },
102 This call passes the C<query>, C<flagsrequired> and C<authnotrequired>
103 to C<&checkauth> (in this module) to perform authentification.
104 See C<&checkauth> for an explanation of these parameters.
106 The C<template_name> is then used to find the correct template for
107 the page. The authenticated users details are loaded onto the
108 template in the HTML::Template LOOP variable C<USER_INFO>. Also the
109 C<sessionID> is passed to the template. This can be used in templates
110 if cookies are disabled. It needs to be put as and input to every
113 More information on the C<gettemplate> sub can be found in the
118 sub get_template_and_user {
121 gettemplate( $in->{'template_name'}, $in->{'type'}, $in->{'query'} );
122 my ( $user, $cookie, $sessionID, $flags ) = checkauth(
124 $in->{'authnotrequired'},
125 $in->{'flagsrequired'},
127 ) unless ($in->{'template_name'}=~/maintenance/);
130 my $insecure = C4::Context->preference('insecure');
131 if ($user or $insecure) {
133 # load the template variables for stylesheets and JavaScript
134 $template->param( css_libs => $in->{'css_libs'} );
135 $template->param( css_module => $in->{'css_module'} );
136 $template->param( css_page => $in->{'css_page'} );
137 $template->param( css_widgets => $in->{'css_widgets'} );
139 $template->param( js_libs => $in->{'js_libs'} );
140 $template->param( js_module => $in->{'js_module'} );
141 $template->param( js_page => $in->{'js_page'} );
142 $template->param( js_widgets => $in->{'js_widgets'} );
145 $template->param( loggedinusername => $user );
146 $template->param( sessionID => $sessionID );
148 if ($shelves = C4::Context->get_shelves_userenv()) {
149 $template->param( barshelves => scalar (@$shelves));
150 $template->param( barshelvesloop => $shelves);
153 $borrowernumber = getborrowernumber($user);
154 my ( $borr, $alternativeflags ) =
155 GetMemberDetails( $borrowernumber );
158 $template->param( "USER_INFO" => \@bordat );
160 my $all_perms = get_all_subpermissions();
162 my @flagroots = qw(circulate catalogue parameters borrowers permissions reserveforothers borrow
163 editcatalogue updatecharges management tools editauthorities serials reports);
164 # We are going to use the $flags returned by checkauth
165 # to create the template's parameters that will indicate
166 # which menus the user can access.
167 if (( $flags && $flags->{superlibrarian}==1) or $insecure==1) {
168 $template->param( CAN_user_circulate => 1 );
169 $template->param( CAN_user_catalogue => 1 );
170 $template->param( CAN_user_parameters => 1 );
171 $template->param( CAN_user_borrowers => 1 );
172 $template->param( CAN_user_permissions => 1 );
173 $template->param( CAN_user_reserveforothers => 1 );
174 $template->param( CAN_user_borrow => 1 );
175 $template->param( CAN_user_editcatalogue => 1 );
176 $template->param( CAN_user_updatecharges => 1 );
177 $template->param( CAN_user_acquisition => 1 );
178 $template->param( CAN_user_management => 1 );
179 $template->param( CAN_user_tools => 1 );
180 $template->param( CAN_user_editauthorities => 1 );
181 $template->param( CAN_user_serials => 1 );
182 $template->param( CAN_user_reports => 1 );
183 $template->param( CAN_user_staffaccess => 1 );
184 foreach my $module (keys %$all_perms) {
185 foreach my $subperm (keys %{ $all_perms->{$module} }) {
186 $template->param( "CAN_user_${module}_${subperm}" => 1 );
191 if (C4::Context->preference('GranularPermissions')) {
193 foreach my $module (keys %$all_perms) {
194 if ( $flags->{$module} == 1) {
195 foreach my $subperm (keys %{ $all_perms->{$module} }) {
196 $template->param( "CAN_user_${module}_${subperm}" => 1 );
198 } elsif ( ref($flags->{$module}) ) {
199 foreach my $subperm (keys %{ $flags->{$module} } ) {
200 $template->param( "CAN_user_${module}_${subperm}" => 1 );
206 foreach my $module (keys %$all_perms) {
207 foreach my $subperm (keys %{ $all_perms->{$module} }) {
208 $template->param( "CAN_user_${module}_${subperm}" => 1 );
214 foreach my $module (keys %$flags) {
215 if ( $flags->{$module} == 1 or ref($flags->{$module}) ) {
216 $template->param( "CAN_user_$module" => 1 );
217 if ($module eq "parameters") {
218 $template->param( CAN_user_management => 1 );
225 if ( $in->{'type'} eq "intranet" ) {
227 intranetcolorstylesheet => C4::Context->preference("intranetcolorstylesheet"),
228 intranetstylesheet => C4::Context->preference("intranetstylesheet"),
229 IntranetNav => C4::Context->preference("IntranetNav"),
230 intranetuserjs => C4::Context->preference("intranetuserjs"),
231 TemplateEncoding => C4::Context->preference("TemplateEncoding"),
232 AmazonContent => C4::Context->preference("AmazonContent"),
233 LibraryName => C4::Context->preference("LibraryName"),
234 LoginBranchcode => (C4::Context->userenv?C4::Context->userenv->{"branch"}:"insecure"),
235 LoginBranchname => (C4::Context->userenv?C4::Context->userenv->{"branchname"}:"insecure"),
236 LoginFirstname => (C4::Context->userenv?C4::Context->userenv->{"firstname"}:"Bel"),
237 LoginSurname => C4::Context->userenv?C4::Context->userenv->{"surname"}:"Inconnu",
238 AutoLocation => C4::Context->preference("AutoLocation"),
239 hide_marc => C4::Context->preference("hide_marc"),
240 patronimages => C4::Context->preference("patronimages"),
241 "BiblioDefaultView".C4::Context->preference("IntranetBiblioDefaultView") => 1,
242 advancedMARCEditor => C4::Context->preference("advancedMARCEditor"),
243 suggestion => C4::Context->preference("suggestion"),
244 virtualshelves => C4::Context->preference("virtualshelves"),
245 LibraryName => C4::Context->preference("LibraryName"),
246 KohaAdminEmailAddress => "" . C4::Context->preference("KohaAdminEmailAddress"),
247 IntranetmainUserblock => C4::Context->preference("IntranetmainUserblock"),
248 IndependantBranches => C4::Context->preference("IndependantBranches"),
249 CircAutocompl => C4::Context->preference("CircAutocompl"),
250 FRBRizeEditions => C4::Context->preference("FRBRizeEditions"),
251 AmazonSimilarItems => C4::Context->preference("AmazonSimilarItems"),
252 'item-level_itypes' => C4::Context->preference('item-level_itypes'),
253 canreservefromotherbranches => C4::Context->preference('canreservefromotherbranches'),
254 intranetreadinghistory => C4::Context->preference("intranetreadinghistory"),
255 noItemTypeImages => C4::Context->preference("noItemTypeImages"),
256 singleBranchMode => C4::Context->preference("singleBranchMode"),
257 TagsEnabled => C4::Context->preference("TagsEnabled"),
258 GoogleJackets => C4::Context->preference("GoogleJackets"),
259 AuthorisedValueImages => C4::Context->preference("AuthorisedValueImages"),
263 warn "template type should be OPAC, here it is=[" . $in->{'type'} . "]" unless ( $in->{'type'} eq 'opac' );
264 my $LibraryNameTitle = C4::Context->preference("LibraryName");
265 $LibraryNameTitle =~ s/<(?:\/?)(?:br|p)\s*(?:\/?)>/ /sgi;
266 $LibraryNameTitle =~ s/<(?:[^<>'"]|'(?:[^']*)'|"(?:[^"]*)")*>//sg;
268 KohaAdminEmailAddress => "" . C4::Context->preference("KohaAdminEmailAddress"),
269 AnonSuggestions => "" . C4::Context->preference("AnonSuggestions"),
270 suggestion => "" . C4::Context->preference("suggestion"),
271 OPACViewOthersSuggestions => "" . C4::Context->preference("OPACViewOthersSuggestions"),
272 virtualshelves => "" . C4::Context->preference("virtualshelves"),
273 OpacNav => "" . C4::Context->preference("OpacNav"),
274 opacheader => "" . C4::Context->preference("opacheader"),
275 opaccredits => "" . C4::Context->preference("opaccredits"),
276 opacsmallimage => "" . C4::Context->preference("opacsmallimage"),
277 opaclargeimage => "" . C4::Context->preference("opaclargeimage"),
278 opaclayoutstylesheet => "". C4::Context->preference("opaclayoutstylesheet"),
279 opaccolorstylesheet => "". C4::Context->preference("opaccolorstylesheet"),
280 OPACUserCSS => "". C4::Context->preference("OPACUserCSS"),
281 opaclanguagesdisplay => "". C4::Context->preference("opaclanguagesdisplay"),
282 opacuserlogin => "" . C4::Context->preference("opacuserlogin"),
283 OpacMainUserBlock => "" . C4::Context->preference("OpacMainUserBlock"),
284 OPACURLOpenInNewWindow => "" . C4::Context->preference("OPACURLOpenInNewWindow"),
285 opacbookbag => "" . C4::Context->preference("opacbookbag"),
286 TemplateEncoding => "". C4::Context->preference("TemplateEncoding"),
287 AmazonContent => "" . C4::Context->preference("AmazonContent"),
288 OPACShelfBrowser => "". C4::Context->preference("OPACShelfBrowser"),
289 OPACAmazonSimilarItems => "" . C4::Context->preference("OPACAmazonSimilarItems"),
290 LibraryName => "" . C4::Context->preference("LibraryName"),
291 LibraryNameTitle => "" . $LibraryNameTitle,
292 LoginBranchcode => (C4::Context->userenv?C4::Context->userenv->{"branch"}:"insecure"),
293 LoginBranchname => C4::Context->userenv?C4::Context->userenv->{"branchname"}:"",
294 LoginFirstname => (C4::Context->userenv?C4::Context->userenv->{"firstname"}:"Bel"),
295 LoginSurname => C4::Context->userenv?C4::Context->userenv->{"surname"}:"Inconnu",
296 OpacPasswordChange => C4::Context->preference("OpacPasswordChange"),
297 opacreadinghistory => C4::Context->preference("opacreadinghistory"),
298 opacuserjs => C4::Context->preference("opacuserjs"),
299 OpacCloud => C4::Context->preference("OpacCloud"),
300 OpacTopissue => C4::Context->preference("OpacTopissue"),
301 OpacAuthorities => C4::Context->preference("OpacAuthorities"),
302 OpacBrowser => C4::Context->preference("OpacBrowser"),
303 RequestOnOpac => C4::Context->preference("RequestOnOpac"),
304 OPACItemHolds => C4::Context->preference("OPACItemHolds"),
305 reviewson => C4::Context->preference("reviewson"),
306 hide_marc => C4::Context->preference("hide_marc"),
307 patronimages => C4::Context->preference("patronimages"),
308 hidelostitems => C4::Context->preference("hidelostitems"),
309 mylibraryfirst => C4::Context->preference("SearchMyLibraryFirst"),
310 "BiblioDefaultView".C4::Context->preference("BiblioDefaultView") => 1,
311 OPACFRBRizeEditions => C4::Context->preference("OPACFRBRizeEditions"),
312 'item-level_itypes' => C4::Context->preference('item-level_itypes'),
313 'Version' => C4::Context->preference('Version'),
314 singleBranchMode => C4::Context->preference("singleBranchMode"),
315 XSLTResultsDisplay => C4::Context->preference("XSLTResultsDisplay"),
316 XSLTDetailsDisplay => C4::Context->preference("XSLTDetailsDisplay"),
317 TagsEnabled => C4::Context->preference("TagsEnabled"),
318 GoogleJackets => C4::Context->preference("GoogleJackets"),
319 AuthorisedValueImages => C4::Context->preference("AuthorisedValueImages"),
320 OPACBaseURL=> C4::Context->preference("OPACBaseURL"),
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 opacheader => C4::Context->preference("opacheader"),
730 OPACUserCSS => C4::Context->preference("OPACUserCSS"),
731 intranetcolorstylesheet =>
732 C4::Context->preference("intranetcolorstylesheet"),
733 intranetstylesheet => C4::Context->preference("intranetstylesheet"),
734 IntranetNav => C4::Context->preference("IntranetNav"),
735 intranetuserjs => C4::Context->preference("intranetuserjs"),
736 TemplateEncoding => C4::Context->preference("TemplateEncoding"),
737 IndependantBranches=> C4::Context->preference("IndependantBranches"),
738 AutoLocation => C4::Context->preference("AutoLocation"),
739 yuipath => C4::Context->preference("yuipath"),
740 wrongip => $info{'wrongip'}
743 $template->param( loginprompt => 1 ) unless $info{'nopermission'};
745 my $self_url = $query->url( -absolute => 1 );
748 LibraryName => C4::Context->preference("LibraryName"),
750 $template->param( \%info );
751 # $cookie = $query->cookie(CGISESSID => $session->id
753 print $query->header(
754 -type => 'text/html',
764 ($status, $cookie, $sessionId) = check_api_auth($query, $userflags);
766 Given a CGI query containing the parameters 'userid' and 'password' and/or a session
767 cookie, determine if the user has the privileges specified by C<$userflags>.
769 C<check_api_auth> is is meant for authenticating users of web services, and
770 consequently will always return and will not attempt to redirect the user
773 If a valid session cookie is already present, check_api_auth will return a status
774 of "ok", the cookie, and the Koha session ID.
776 If no session cookie is present, check_api_auth will check the 'userid' and 'password
777 parameters and create a session cookie and Koha session if the supplied credentials
780 Possible return values in C<$status> are:
784 =item "ok" -- user authenticated; C<$cookie> and C<$sessionid> have valid values.
786 =item "failed" -- credentials are not correct; C<$cookie> and C<$sessionid> are undef
788 =item "maintenance" -- DB is in maintenance mode; no login possible at the moment
790 =item "expired -- session cookie has expired; API user should resubmit userid and password
798 my $flagsrequired = shift;
800 my $dbh = C4::Context->dbh;
801 my $timeout = C4::Context->preference('timeout');
802 $timeout = 600 unless $timeout;
804 unless (C4::Context->preference('Version')) {
805 # database has not been installed yet
806 return ("maintenance", undef, undef);
808 my $kohaversion=C4::Context::KOHAVERSION;
809 $kohaversion =~ s/(.*\..*)\.(.*)\.(.*)/$1$2$3/;
810 if (C4::Context->preference('Version') < $kohaversion) {
811 # database in need of version update; assume that
812 # no API should be called while databsae is in
814 return ("maintenance", undef, undef);
817 # FIXME -- most of what follows is a copy-and-paste
818 # of code from checkauth. There is an obvious need
819 # for refactoring to separate the various parts of
820 # the authentication code, but as of 2007-11-19 this
821 # is deferred so as to not introduce bugs into the
822 # regular authentication code for Koha 3.0.
824 # see if we have a valid session cookie already
825 # however, if a userid parameter is present (i.e., from
826 # a form submission, assume that any current cookie
828 my $sessionID = undef;
829 unless ($query->param('userid')) {
830 $sessionID = $query->cookie("CGISESSID");
833 my $session = get_session($sessionID);
834 C4::Context->_new_userenv($sessionID);
836 C4::Context::set_userenv(
837 $session->param('number'), $session->param('id'),
838 $session->param('cardnumber'), $session->param('firstname'),
839 $session->param('surname'), $session->param('branch'),
840 $session->param('branchname'), $session->param('flags'),
841 $session->param('emailaddress'), $session->param('branchprinter')
844 my $ip = $session->param('ip');
845 my $lasttime = $session->param('lasttime');
846 my $userid = $session->param('id');
847 if ( $lasttime < time() - $timeout ) {
850 C4::Context->_unset_userenv($sessionID);
853 return ("expired", undef, undef);
854 } elsif ( $ip ne $ENV{'REMOTE_ADDR'} ) {
857 C4::Context->_unset_userenv($sessionID);
860 return ("expired", undef, undef);
862 my $cookie = $query->cookie( CGISESSID => $session->id );
863 $session->param('lasttime',time());
864 my $flags = haspermission( $dbh, $userid, $flagsrequired );
866 return ("ok", $cookie, $sessionID);
869 C4::Context->_unset_userenv($sessionID);
872 return ("failed", undef, undef);
876 return ("expired", undef, undef);
880 my $userid = $query->param('userid');
881 my $password = $query->param('password');
882 unless ($userid and $password) {
883 # caller did something wrong, fail the authenticateion
884 return ("failed", undef, undef);
886 my ( $return, $cardnumber ) = checkpw( $dbh, $userid, $password );
887 if ($return and haspermission( $dbh, $userid, $flagsrequired)) {
888 my $session = get_session("");
889 return ("failed", undef, undef) unless $session;
891 my $sessionID = $session->id;
892 C4::Context->_new_userenv($sessionID);
893 my $cookie = $query->cookie(CGISESSID => $sessionID);
894 if ( $return == 1 ) {
896 $borrowernumber, $firstname, $surname,
897 $userflags, $branchcode, $branchname,
898 $branchprinter, $emailaddress
902 "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=?"
904 $sth->execute($userid);
906 $borrowernumber, $firstname, $surname,
907 $userflags, $branchcode, $branchname,
908 $branchprinter, $emailaddress
909 ) = $sth->fetchrow if ( $sth->rows );
911 unless ($sth->rows ) {
912 my $sth = $dbh->prepare(
913 "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=?"
915 $sth->execute($cardnumber);
917 $borrowernumber, $firstname, $surname,
918 $userflags, $branchcode, $branchname,
919 $branchprinter, $emailaddress
920 ) = $sth->fetchrow if ( $sth->rows );
922 unless ( $sth->rows ) {
923 $sth->execute($userid);
925 $borrowernumber, $firstname, $surname, $userflags,
926 $branchcode, $branchname, $branchprinter, $emailaddress
927 ) = $sth->fetchrow if ( $sth->rows );
931 my $ip = $ENV{'REMOTE_ADDR'};
932 # if they specify at login, use that
933 if ($query->param('branch')) {
934 $branchcode = $query->param('branch');
935 $branchname = GetBranchName($branchcode);
937 my $branches = GetBranches();
939 foreach my $br ( keys %$branches ) {
940 # now we work with the treatment of ip
941 my $domain = $branches->{$br}->{'branchip'};
942 if ( $domain && $ip =~ /^$domain/ ) {
943 $branchcode = $branches->{$br}->{'branchcode'};
945 # new op dev : add the branchprinter and branchname in the cookie
946 $branchprinter = $branches->{$br}->{'branchprinter'};
947 $branchname = $branches->{$br}->{'branchname'};
950 $session->param('number',$borrowernumber);
951 $session->param('id',$userid);
952 $session->param('cardnumber',$cardnumber);
953 $session->param('firstname',$firstname);
954 $session->param('surname',$surname);
955 $session->param('branch',$branchcode);
956 $session->param('branchname',$branchname);
957 $session->param('flags',$userflags);
958 $session->param('emailaddress',$emailaddress);
959 $session->param('ip',$session->remote_addr());
960 $session->param('lasttime',time());
961 } elsif ( $return == 2 ) {
962 #We suppose the user is the superlibrarian
963 $session->param('number',0);
964 $session->param('id',C4::Context->config('user'));
965 $session->param('cardnumber',C4::Context->config('user'));
966 $session->param('firstname',C4::Context->config('user'));
967 $session->param('surname',C4::Context->config('user'));
968 $session->param('branch','NO_LIBRARY_SET');
969 $session->param('branchname','NO_LIBRARY_SET');
970 $session->param('flags',1);
971 $session->param('emailaddress', C4::Context->preference('KohaAdminEmailAddress'));
972 $session->param('ip',$session->remote_addr());
973 $session->param('lasttime',time());
975 C4::Context::set_userenv(
976 $session->param('number'), $session->param('id'),
977 $session->param('cardnumber'), $session->param('firstname'),
978 $session->param('surname'), $session->param('branch'),
979 $session->param('branchname'), $session->param('flags'),
980 $session->param('emailaddress'), $session->param('branchprinter')
982 return ("ok", $cookie, $sessionID);
984 return ("failed", undef, undef);
989 =item check_cookie_auth
991 ($status, $sessionId) = check_api_auth($cookie, $userflags);
993 Given a CGISESSID cookie set during a previous login to Koha, determine
994 if the user has the privileges specified by C<$userflags>.
996 C<check_cookie_auth> is meant for authenticating special services
997 such as tools/upload-file.pl that are invoked by other pages that
998 have been authenticated in the usual way.
1000 Possible return values in C<$status> are:
1004 =item "ok" -- user authenticated; C<$sessionID> have valid values.
1006 =item "failed" -- credentials are not correct; C<$sessionid> are undef
1008 =item "maintenance" -- DB is in maintenance mode; no login possible at the moment
1010 =item "expired -- session cookie has expired; API user should resubmit userid and password
1016 sub check_cookie_auth {
1018 my $flagsrequired = shift;
1020 my $dbh = C4::Context->dbh;
1021 my $timeout = C4::Context->preference('timeout');
1022 $timeout = 600 unless $timeout;
1024 unless (C4::Context->preference('Version')) {
1025 # database has not been installed yet
1026 return ("maintenance", undef);
1028 my $kohaversion=C4::Context::KOHAVERSION;
1029 $kohaversion =~ s/(.*\..*)\.(.*)\.(.*)/$1$2$3/;
1030 if (C4::Context->preference('Version') < $kohaversion) {
1031 # database in need of version update; assume that
1032 # no API should be called while databsae is in
1034 return ("maintenance", undef);
1037 # FIXME -- most of what follows is a copy-and-paste
1038 # of code from checkauth. There is an obvious need
1039 # for refactoring to separate the various parts of
1040 # the authentication code, but as of 2007-11-23 this
1041 # is deferred so as to not introduce bugs into the
1042 # regular authentication code for Koha 3.0.
1044 # see if we have a valid session cookie already
1045 # however, if a userid parameter is present (i.e., from
1046 # a form submission, assume that any current cookie
1048 unless (defined $cookie and $cookie) {
1049 return ("failed", undef);
1051 my $sessionID = $cookie;
1052 my $session = get_session($sessionID);
1053 C4::Context->_new_userenv($sessionID);
1055 C4::Context::set_userenv(
1056 $session->param('number'), $session->param('id'),
1057 $session->param('cardnumber'), $session->param('firstname'),
1058 $session->param('surname'), $session->param('branch'),
1059 $session->param('branchname'), $session->param('flags'),
1060 $session->param('emailaddress'), $session->param('branchprinter')
1063 my $ip = $session->param('ip');
1064 my $lasttime = $session->param('lasttime');
1065 my $userid = $session->param('id');
1066 if ( $lasttime < time() - $timeout ) {
1069 C4::Context->_unset_userenv($sessionID);
1072 return ("expired", undef);
1073 } elsif ( $ip ne $ENV{'REMOTE_ADDR'} ) {
1074 # IP address changed
1076 C4::Context->_unset_userenv($sessionID);
1079 return ("expired", undef);
1081 $session->param('lasttime',time());
1082 my $flags = haspermission( $dbh, $userid, $flagsrequired );
1084 return ("ok", $sessionID);
1087 C4::Context->_unset_userenv($sessionID);
1090 return ("failed", undef);
1094 return ("expired", undef);
1101 my $session = get_session($sessionID);
1103 Given a session ID, retrieve the CGI::Session object used to store
1104 the session's state. The session object can be used to store
1105 data that needs to be accessed by different scripts during a
1108 If the C<$sessionID> parameter is an empty string, a new session
1114 my $sessionID = shift;
1115 my $storage_method = C4::Context->preference('SessionStorage');
1116 my $dbh = C4::Context->dbh;
1118 if ($storage_method eq 'mysql'){
1119 $session = new CGI::Session("driver:MySQL;serializer:yaml", $sessionID, {Handle=>$dbh});
1121 elsif ($storage_method eq 'Pg') {
1122 $session = new CGI::Session("driver:PostgreSQL;serializer:yaml", $sessionID, {Handle=>$dbh});
1125 # catch all defaults to tmp should work on all systems
1126 $session = new CGI::Session("driver:File;serializer:yaml", $sessionID, {Directory=>'/tmp'});
1133 my ( $dbh, $userid, $password ) = @_;
1135 $debug and print "## checkpw - checking LDAP\n";
1136 my ($retval,$retcard) = checkpw_ldap(@_); # EXTERNAL AUTH
1137 ($retval) and return ($retval,$retcard);
1143 "select password,cardnumber,borrowernumber,userid,firstname,surname,branchcode,flags from borrowers where userid=?"
1145 $sth->execute($userid);
1147 my ( $md5password, $cardnumber, $borrowernumber, $userid, $firstname,
1148 $surname, $branchcode, $flags )
1150 if ( md5_base64($password) eq $md5password ) {
1152 C4::Context->set_userenv( "$borrowernumber", $userid, $cardnumber,
1153 $firstname, $surname, $branchcode, $flags );
1154 return 1, $cardnumber;
1159 "select password,cardnumber,borrowernumber,userid, firstname,surname,branchcode,flags from borrowers where cardnumber=?"
1161 $sth->execute($userid);
1163 my ( $md5password, $cardnumber, $borrowernumber, $userid, $firstname,
1164 $surname, $branchcode, $flags )
1166 if ( md5_base64($password) eq $md5password ) {
1168 C4::Context->set_userenv( $borrowernumber, $userid, $cardnumber,
1169 $firstname, $surname, $branchcode, $flags );
1173 if ( $userid && $userid eq C4::Context->config('user')
1174 && "$password" eq C4::Context->config('pass') )
1177 # Koha superuser account
1178 # C4::Context->set_userenv(0,0,C4::Context->config('user'),C4::Context->config('user'),C4::Context->config('user'),"",1);
1181 if ( $userid && $userid eq 'demo'
1182 && "$password" eq 'demo'
1183 && C4::Context->config('demo') )
1186 # DEMO => the demo user is allowed to do everything (if demo set to 1 in koha.conf
1187 # some features won't be effective : modify systempref, modify MARC structure,
1195 $authflags = getuserflags($flags,$dbh);
1196 Translates integer flags into permissions strings hash.
1198 C<$flags> is the integer userflags value ( borrowers.userflags )
1199 C<$authflags> is a hashref of permissions
1208 $flags = 0 unless $flags;
1209 my $sth = $dbh->prepare("SELECT bit, flag, defaulton FROM userflags");
1212 while ( my ( $bit, $flag, $defaulton ) = $sth->fetchrow ) {
1213 if ( ( $flags & ( 2**$bit ) ) || $defaulton ) {
1214 $userflags->{$flag} = 1;
1217 $userflags->{$flag} = 0;
1221 # get subpermissions and merge with top-level permissions
1222 my $user_subperms = get_user_subpermissions($userid);
1223 foreach my $module (keys %$user_subperms) {
1224 next if $userflags->{$module} == 1; # user already has permission for everything in this module
1225 $userflags->{$module} = $user_subperms->{$module};
1231 =item get_user_subpermissions
1235 my $user_perm_hashref = get_user_subpermissions($userid);
1239 Given the userid (note, not the borrowernumber) of a staff user,
1240 return a hashref of hashrefs of the specific subpermissions
1241 accorded to the user. An example return is
1245 export_catalog => 1,
1246 import_patrons => 1,
1250 The top-level hash-key is a module or function code from
1251 userflags.flag, while the second-level key is a code
1254 The results of this function do not give a complete picture
1255 of the functions that a staff user can access; it is also
1256 necessary to check borrowers.flags.
1260 sub get_user_subpermissions {
1263 my $dbh = C4::Context->dbh;
1264 my $sth = $dbh->prepare("SELECT flag, code
1265 FROM user_permissions
1266 JOIN permissions USING (module_bit, code)
1267 JOIN userflags ON (module_bit = bit)
1268 JOIN borrowers USING (borrowernumber)
1270 $sth->execute($userid);
1272 my $user_perms = {};
1273 while (my $perm = $sth->fetchrow_hashref) {
1274 $user_perms->{$perm->{'flag'}}->{$perm->{'code'}} = 1;
1279 =item get_all_subpermissions
1283 my $perm_hashref = get_all_subpermissions();
1287 Returns a hashref of hashrefs defining all specific
1288 permissions currently defined. The return value
1289 has the same structure as that of C<get_user_subpermissions>,
1290 except that the innermost hash value is the description
1291 of the subpermission.
1295 sub get_all_subpermissions {
1296 my $dbh = C4::Context->dbh;
1297 my $sth = $dbh->prepare("SELECT flag, code, description
1299 JOIN userflags ON (module_bit = bit)");
1303 while (my $perm = $sth->fetchrow_hashref) {
1304 $all_perms->{$perm->{'flag'}}->{$perm->{'code'}} = $perm->{'description'};
1311 $flags = ($dbh,$member,$flagsrequired);
1313 C<$member> may be either userid or overloaded with $borrower hashref from GetMemberDetails.
1314 C<$flags> is a hashref of required flags like C<$borrower-<{authflags}>
1316 Returns member's flags or 0 if a permission is not met.
1321 my ( $dbh, $userid, $flagsrequired ) = @_;
1322 my ($flags,$intflags);
1323 $dbh=C4::Context->dbh unless($dbh);
1325 $intflags = $userid->{'flags'};
1327 my $sth = $dbh->prepare("SELECT flags FROM borrowers WHERE userid=?");
1328 $sth->execute($userid);
1329 my ($intflags) = $sth->fetchrow;
1330 $flags = getuserflags( $intflags, $userid, $dbh );
1332 if ( $userid eq C4::Context->config('user') ) {
1333 # Super User Account from /etc/koha.conf
1334 $flags->{'superlibrarian'} = 1;
1336 if ( $userid eq 'demo' && C4::Context->config('demo') ) {
1337 # Demo user that can do "anything" (demo=1 in /etc/koha.conf)
1338 $flags->{'superlibrarian'} = 1;
1340 return $flags if $flags->{superlibrarian};
1341 foreach my $module ( keys %$flagsrequired ) {
1342 if (C4::Context->preference('GranularPermissions')) {
1343 my $subperm = $flagsrequired->{$module};
1344 if ($subperm eq '*') {
1345 return 0 unless ( $flags->{$module} == 1 or ref($flags->{$module}) );
1347 return 0 unless ( $flags->{$module} == 1 or
1348 ( ref($flags->{$module}) and
1349 exists $flags->{$module}->{$subperm} and
1350 $flags->{$module}->{$subperm} == 1
1355 return 0 unless ( $flags->{$module} );
1359 #FIXME - This fcn should return the failed permission so a suitable error msg can be delivered.
1363 sub getborrowernumber {
1365 my $dbh = C4::Context->dbh;
1366 for my $field ( 'userid', 'cardnumber' ) {
1368 $dbh->prepare("select borrowernumber from borrowers where $field=?");
1369 $sth->execute($userid);
1371 my ($bnumber) = $sth->fetchrow;
1378 END { } # module clean-up code here (global destructor)