setting $dbh->{AutoCommit} = 0, and adding a new --commit arg.
[koha.git] / C4 / Auth.pm
blob495ae9a4319d8804902b81df4a8e128956549775
2 # -*- tab-width: 8 -*-
3 # NOTE: This file uses 8-character tabs; do not change the tab size!
5 package C4::Auth;
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
14 # version.
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
24 use strict;
25 use Digest::MD5 qw(md5_base64);
26 use CGI::Session;
28 require Exporter;
29 use C4::Context;
30 use C4::Output; # to get the template
31 use C4::Members;
32 use C4::Koha;
33 use C4::Branch; # GetBranches
34 use C4::VirtualShelves 3.02 qw(GetShelvesSummary);
36 # use utf8;
37 use vars qw($VERSION @ISA @EXPORT @EXPORT_OK %EXPORT_TAGS $debug $ldap);
39 BEGIN {
40 $VERSION = 3.02; # set version for version checking
41 $debug = $ENV{DEBUG} || 0 ;
42 @ISA = qw(Exporter);
43 @EXPORT = qw(&checkauth &get_template_and_user);
44 @EXPORT_OK = qw(&check_api_auth &get_session &check_cookie_auth &checkpw);
45 $ldap = C4::Context->config('useldapserver') || 0;
46 if ($ldap) {
47 require C4::Auth_with_ldap; # no import
48 import C4::Auth_with_ldap qw(checkpw_ldap);
52 =head1 NAME
54 C4::Auth - Authenticates Koha users
56 =head1 SYNOPSIS
58 use CGI;
59 use C4::Auth;
61 my $query = new CGI;
63 my ($template, $borrowernumber, $cookie)
64 = get_template_and_user(
66 template_name => "opac-main.tmpl",
67 query => $query,
68 type => "opac",
69 authnotrequired => 1,
70 flagsrequired => {borrow => 1},
74 print $query->header(
75 -type => 'utf-8',
76 -cookie => $cookie
77 ), $template->output;
80 =head1 DESCRIPTION
82 The main function of this module is to provide
83 authentification. However the get_template_and_user function has
84 been provided so that a users login information is passed along
85 automatically. This gets loaded into the template.
87 =head1 FUNCTIONS
89 =over 2
91 =item get_template_and_user
93 my ($template, $borrowernumber, $cookie)
94 = get_template_and_user(
96 template_name => "opac-main.tmpl",
97 query => $query,
98 type => "opac",
99 authnotrequired => 1,
100 flagsrequired => {borrow => 1},
104 This call passes the C<query>, C<flagsrequired> and C<authnotrequired>
105 to C<&checkauth> (in this module) to perform authentification.
106 See C<&checkauth> for an explanation of these parameters.
108 The C<template_name> is then used to find the correct template for
109 the page. The authenticated users details are loaded onto the
110 template in the HTML::Template LOOP variable C<USER_INFO>. Also the
111 C<sessionID> is passed to the template. This can be used in templates
112 if cookies are disabled. It needs to be put as and input to every
113 authenticated page.
115 More information on the C<gettemplate> sub can be found in the
116 Output.pm module.
118 =cut
120 sub get_template_and_user {
121 my $in = shift;
122 my $template =
123 gettemplate( $in->{'template_name'}, $in->{'type'}, $in->{'query'} );
124 my ( $user, $cookie, $sessionID, $flags ) = checkauth(
125 $in->{'query'},
126 $in->{'authnotrequired'},
127 $in->{'flagsrequired'},
128 $in->{'type'}
129 ) unless ($in->{'template_name'}=~/maintenance/);
131 my $borrowernumber;
132 my $insecure = C4::Context->preference('insecure');
133 if ($user or $insecure) {
135 # load the template variables for stylesheets and JavaScript
136 $template->param( css_libs => $in->{'css_libs'} );
137 $template->param( css_module => $in->{'css_module'} );
138 $template->param( css_page => $in->{'css_page'} );
139 $template->param( css_widgets => $in->{'css_widgets'} );
141 $template->param( js_libs => $in->{'js_libs'} );
142 $template->param( js_module => $in->{'js_module'} );
143 $template->param( js_page => $in->{'js_page'} );
144 $template->param( js_widgets => $in->{'js_widgets'} );
146 # user info
147 $template->param( loggedinusername => $user );
148 $template->param( sessionID => $sessionID );
149 my $shelves;
150 if ($shelves = C4::Context->get_shelves_userenv()) {
151 $template->param( barshelves => scalar (@$shelves));
152 $template->param( barshelvesloop => $shelves);
155 $borrowernumber = getborrowernumber($user);
156 my ( $borr, $alternativeflags ) =
157 GetMemberDetails( $borrowernumber );
158 my @bordat;
159 $bordat[0] = $borr;
160 $template->param( "USER_INFO" => \@bordat );
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_permission => 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 );
186 if ( $flags && $flags->{circulate} == 1 ) {
187 $template->param( CAN_user_circulate => 1 );
190 if ( $flags && $flags->{catalogue} == 1 ) {
191 $template->param( CAN_user_catalogue => 1 );
194 if ( $flags && $flags->{parameters} == 1 ) {
195 $template->param( CAN_user_parameters => 1 );
196 $template->param( CAN_user_management => 1 );
199 if ( $flags && $flags->{borrowers} == 1 ) {
200 $template->param( CAN_user_borrowers => 1 );
203 if ( $flags && $flags->{permissions} == 1 ) {
204 $template->param( CAN_user_permission => 1 );
207 if ( $flags && $flags->{reserveforothers} == 1 ) {
208 $template->param( CAN_user_reserveforothers => 1 );
211 if ( $flags && $flags->{borrow} == 1 ) {
212 $template->param( CAN_user_borrow => 1 );
215 if ( $flags && $flags->{editcatalogue} == 1 ) {
216 $template->param( CAN_user_editcatalogue => 1 );
219 if ( $flags && $flags->{updatecharges} == 1 ) {
220 $template->param( CAN_user_updatecharges => 1 );
223 if ( $flags && $flags->{acquisition} == 1 ) {
224 $template->param( CAN_user_acquisition => 1 );
227 if ( $flags && $flags->{tools} == 1 ) {
228 $template->param( CAN_user_tools => 1 );
231 if ( $flags && $flags->{editauthorities} == 1 ) {
232 $template->param( CAN_user_editauthorities => 1 );
235 if ( $flags && $flags->{serials} == 1 ) {
236 $template->param( CAN_user_serials => 1 );
239 if ( $flags && $flags->{reports} == 1 ) {
240 $template->param( CAN_user_reports => 1 );
242 if ( $flags && $flags->{staffaccess} == 1 ) {
243 $template->param( CAN_user_staffaccess => 1 );
246 if ( $in->{'type'} eq "intranet" ) {
247 $template->param(
248 intranetcolorstylesheet => C4::Context->preference("intranetcolorstylesheet"),
249 intranetstylesheet => C4::Context->preference("intranetstylesheet"),
250 IntranetNav => C4::Context->preference("IntranetNav"),
251 intranetuserjs => C4::Context->preference("intranetuserjs"),
252 TemplateEncoding => C4::Context->preference("TemplateEncoding"),
253 AmazonContent => C4::Context->preference("AmazonContent"),
254 LibraryName => C4::Context->preference("LibraryName"),
255 LoginBranchcode => (C4::Context->userenv?C4::Context->userenv->{"branch"}:"insecure"),
256 LoginBranchname => (C4::Context->userenv?C4::Context->userenv->{"branchname"}:"insecure"),
257 LoginFirstname => (C4::Context->userenv?C4::Context->userenv->{"firstname"}:"Bel"),
258 LoginSurname => C4::Context->userenv?C4::Context->userenv->{"surname"}:"Inconnu",
259 AutoLocation => C4::Context->preference("AutoLocation"),
260 hide_marc => C4::Context->preference("hide_marc"),
261 patronimages => C4::Context->preference("patronimages"),
262 "BiblioDefaultView".C4::Context->preference("IntranetBiblioDefaultView") => 1,
263 advancedMARCEditor => C4::Context->preference("advancedMARCEditor"),
264 suggestion => C4::Context->preference("suggestion"),
265 virtualshelves => C4::Context->preference("virtualshelves"),
266 LibraryName => C4::Context->preference("LibraryName"),
267 KohaAdminEmailAddress => "" . C4::Context->preference("KohaAdminEmailAddress"),
268 IntranetmainUserblock => C4::Context->preference("IntranetmainUserblock"),
269 IndependantBranches => C4::Context->preference("IndependantBranches"),
270 CircAutocompl => C4::Context->preference("CircAutocompl"),
271 yuipath => C4::Context->preference("yuipath"),
272 FRBRizeEditions => C4::Context->preference("FRBRizeEditions"),
273 AmazonSimilarItems => C4::Context->preference("AmazonSimilarItems"),
274 'item-level_itypes' => C4::Context->preference('item-level_itypes'),
275 canreservefromotherbranches => C4::Context->preference('canreservefromotherbranches'),
276 intranetreadinghistory => C4::Context->preference("intranetreadinghistory"),
277 noItemTypeImages => C4::Context->preference("noItemTypeImages"),
280 else {
281 warn "template type should be OPAC, here it is=[" . $in->{'type'} . "]" unless ( $in->{'type'} eq 'opac' );
282 my $LibraryNameTitle = C4::Context->preference("LibraryName");
283 $LibraryNameTitle =~ s/<(?:\/?)(?:br|p)\s*(?:\/?)>/ /sgi;
284 $LibraryNameTitle =~ s/<(?:[^<>'"]|'(?:[^']*)'|"(?:[^"]*)")*>//sg;
285 $template->param(
286 KohaAdminEmailAddress => "" . C4::Context->preference("KohaAdminEmailAddress"),
287 AnonSuggestions => "" . C4::Context->preference("AnonSuggestions"),
288 suggestion => "" . C4::Context->preference("suggestion"),
289 virtualshelves => "" . C4::Context->preference("virtualshelves"),
290 OpacNav => "" . C4::Context->preference("OpacNav"),
291 opacheader => "" . C4::Context->preference("opacheader"),
292 opaccredits => "" . C4::Context->preference("opaccredits"),
293 opacsmallimage => "" . C4::Context->preference("opacsmallimage"),
294 opaclargeimage => "" . C4::Context->preference("opaclargeimage"),
295 opaclayoutstylesheet => "". C4::Context->preference("opaclayoutstylesheet"),
296 opaccolorstylesheet => "". C4::Context->preference("opaccolorstylesheet"),
297 opaclanguagesdisplay => "". C4::Context->preference("opaclanguagesdisplay"),
298 opacuserlogin => "" . C4::Context->preference("opacuserlogin"),
299 opacbookbag => "" . C4::Context->preference("opacbookbag"),
300 TemplateEncoding => "". C4::Context->preference("TemplateEncoding"),
301 AmazonContent => "" . C4::Context->preference("AmazonContent"),
302 LibraryName => "" . C4::Context->preference("LibraryName"),
303 LibraryNameTitle => "" . $LibraryNameTitle,
304 LoginBranchcode => (C4::Context->userenv?C4::Context->userenv->{"branch"}:"insecure"),
305 LoginBranchname => C4::Context->userenv?C4::Context->userenv->{"branchname"}:"",
306 LoginFirstname => (C4::Context->userenv?C4::Context->userenv->{"firstname"}:"Bel"),
307 LoginSurname => C4::Context->userenv?C4::Context->userenv->{"surname"}:"Inconnu",
308 OpacPasswordChange => C4::Context->preference("OpacPasswordChange"),
309 opacreadinghistory => C4::Context->preference("opacreadinghistory"),
310 opacuserjs => C4::Context->preference("opacuserjs"),
311 OpacCloud => C4::Context->preference("OpacCloud"),
312 OpacTopissue => C4::Context->preference("OpacTopissue"),
313 OpacAuthorities => C4::Context->preference("OpacAuthorities"),
314 OpacBrowser => C4::Context->preference("OpacBrowser"),
315 RequestOnOpac => C4::Context->preference("RequestOnOpac"),
316 reviewson => C4::Context->preference("reviewson"),
317 hide_marc => C4::Context->preference("hide_marc"),
318 patronimages => C4::Context->preference("patronimages"),
319 mylibraryfirst => C4::Context->preference("SearchMyLibraryFirst"),
320 "BiblioDefaultView".C4::Context->preference("BiblioDefaultView") => 1,
321 OPACFRBRizeEditions => C4::Context->preference("OPACFRBRizeEditions"),
322 'item-level_itypes' => C4::Context->preference('item-level_itypes'),
323 yuipath => C4::Context->preference("yuipath"),
326 $template->param(listloop=>[{shelfname=>"Freelist", shelfnumber=>110}]);
327 return ( $template, $borrowernumber, $cookie, $flags);
330 =item checkauth
332 ($userid, $cookie, $sessionID) = &checkauth($query, $noauth, $flagsrequired, $type);
334 Verifies that the user is authorized to run this script. If
335 the user is authorized, a (userid, cookie, session-id, flags)
336 quadruple is returned. If the user is not authorized but does
337 not have the required privilege (see $flagsrequired below), it
338 displays an error page and exits. Otherwise, it displays the
339 login page and exits.
341 Note that C<&checkauth> will return if and only if the user
342 is authorized, so it should be called early on, before any
343 unfinished operations (e.g., if you've opened a file, then
344 C<&checkauth> won't close it for you).
346 C<$query> is the CGI object for the script calling C<&checkauth>.
348 The C<$noauth> argument is optional. If it is set, then no
349 authorization is required for the script.
351 C<&checkauth> fetches user and session information from C<$query> and
352 ensures that the user is authorized to run scripts that require
353 authorization.
355 The C<$flagsrequired> argument specifies the required privileges
356 the user must have if the username and password are correct.
357 It should be specified as a reference-to-hash; keys in the hash
358 should be the "flags" for the user, as specified in the Members
359 intranet module. Any key specified must correspond to a "flag"
360 in the userflags table. E.g., { circulate => 1 } would specify
361 that the user must have the "circulate" privilege in order to
362 proceed. To make sure that access control is correct, the
363 C<$flagsrequired> parameter must be specified correctly.
365 The C<$type> argument specifies whether the template should be
366 retrieved from the opac or intranet directory tree. "opac" is
367 assumed if it is not specified; however, if C<$type> is specified,
368 "intranet" is assumed if it is not "opac".
370 If C<$query> does not have a valid session ID associated with it
371 (i.e., the user has not logged in) or if the session has expired,
372 C<&checkauth> presents the user with a login page (from the point of
373 view of the original script, C<&checkauth> does not return). Once the
374 user has authenticated, C<&checkauth> restarts the original script
375 (this time, C<&checkauth> returns).
377 The login page is provided using a HTML::Template, which is set in the
378 systempreferences table or at the top of this file. The variable C<$type>
379 selects which template to use, either the opac or the intranet
380 authentification template.
382 C<&checkauth> returns a user ID, a cookie, and a session ID. The
383 cookie should be sent back to the browser; it verifies that the user
384 has authenticated.
386 =cut
388 sub _version_check ($$) {
389 my $type = shift;
390 my $query = shift;
391 my $version;
392 # If Version syspref is unavailable, it means Koha is beeing installed,
393 # and so we must redirect to OPAC maintenance page or to the WebInstaller
394 #warn "about to check version";
395 unless ($version = C4::Context->preference('Version')) { # assignment, not comparison
396 if ($type ne 'opac') {
397 warn "Install required, redirecting to Installer";
398 print $query->redirect("/cgi-bin/koha/installer/install.pl");
400 else {
401 warn "OPAC Install required, redirecting to maintenance";
402 print $query->redirect("/cgi-bin/koha/maintenance.pl");
404 exit;
407 # check that database and koha version are the same
408 # there is no DB version, it's a fresh install,
409 # go to web installer
410 # there is a DB version, compare it to the code version
411 my $kohaversion=C4::Context::KOHAVERSION;
412 # remove the 3 last . to have a Perl number
413 $kohaversion =~ s/(.*\..*)\.(.*)\.(.*)/$1$2$3/;
414 $debug and print STDERR "kohaversion : $kohaversion\n";
415 if ($version < $kohaversion){
416 my $warning = "Database update needed, redirecting to %s. Database is $version and Koha is "
417 . C4::Context->config("kohaversion");
418 if ($type ne 'opac'){
419 warn sprintf($warning, 'Installer');
420 print $query->redirect("/cgi-bin/koha/installer/install.pl?step=3");
421 } else {
422 warn sprintf("OPAC: " . $warning, 'maintenance');
423 print $query->redirect("/cgi-bin/koha/maintenance.pl");
425 exit;
429 sub _session_log {
430 (@_) or return 0;
431 open L, ">>/tmp/sessionlog" or warn "ERROR: Cannot append to /tmp/sessionlog";
432 printf L join("\n",@_);
433 close L;
436 sub checkauth {
437 my $query = shift;
438 $debug and warn "Checking Auth";
439 # $authnotrequired will be set for scripts which will run without authentication
440 my $authnotrequired = shift;
441 my $flagsrequired = shift;
442 my $type = shift;
443 $type = 'opac' unless $type;
445 my $dbh = C4::Context->dbh;
446 my $timeout = C4::Context->preference('timeout');
447 # days
448 if ($timeout =~ /(\d+)[dD]/) {
449 $timeout = $1 * 86400;
451 $timeout = 600 unless $timeout;
453 _version_check($type,$query);
454 # state variables
455 my $loggedin = 0;
456 my %info;
457 my ( $userid, $cookie, $sessionID, $flags, $shelves );
458 my $logout = $query->param('logout.x');
459 if ( $userid = $ENV{'REMOTE_USER'} ) {
460 # Using Basic Authentication, no cookies required
461 $cookie = $query->cookie(
462 -name => 'CGISESSID',
463 -value => '',
464 -expires => ''
466 $loggedin = 1;
468 elsif ( $sessionID = $query->cookie("CGISESSID")) { # assignment, not comparison
469 my $session = get_session($sessionID);
470 C4::Context->_new_userenv($sessionID);
471 my ($ip, $lasttime);
472 if ($session){
473 C4::Context::set_userenv(
474 $session->param('number'), $session->param('id'),
475 $session->param('cardnumber'), $session->param('firstname'),
476 $session->param('surname'), $session->param('branch'),
477 $session->param('branchname'), $session->param('flags'),
478 $session->param('emailaddress'), $session->param('branchprinter')
480 C4::Context::set_shelves_userenv($session->param('shelves'));
481 $debug and printf STDERR "AUTH_SESSION: (%s)\t%s %s - %s\n", map {$session->param($_)} qw(cardnumber firstname surname branch) ;
482 $ip = $session->param('ip');
483 $lasttime = $session->param('lasttime');
484 $userid = $session->param('id');
487 if ($logout) {
488 # voluntary logout the user
489 $session->flush;
490 $session->delete();
491 C4::Context->_unset_userenv($sessionID);
492 _session_log(sprintf "%20s from %16s logged out at %30s (manually).\n", $userid,$ip,localtime);
493 $sessionID = undef;
494 $userid = undef;
496 elsif ( $lasttime < time() - $timeout ) {
497 # timed logout
498 $info{'timed_out'} = 1;
499 $session->delete();
500 C4::Context->_unset_userenv($sessionID);
501 _session_log(sprintf "%20s from %16s logged out at %30s (inactivity).\n", $userid,$ip,localtime);
502 $userid = undef;
503 $sessionID = undef;
505 elsif ( $ip ne $ENV{'REMOTE_ADDR'} ) {
506 # Different ip than originally logged in from
507 $info{'oldip'} = $ip;
508 $info{'newip'} = $ENV{'REMOTE_ADDR'};
509 $info{'different_ip'} = 1;
510 $session->delete();
511 C4::Context->_unset_userenv($sessionID);
512 _session_log(sprintf "%20s from %16s logged out at %30s (ip changed to %16s).\n", $userid,$ip,localtime, $info{'newip'});
513 $sessionID = undef;
514 $userid = undef;
516 else {
517 $cookie = $query->cookie( CGISESSID => $session->id );
518 $session->param('lasttime',time());
519 $flags = haspermission( $dbh, $userid, $flagsrequired );
520 if ($flags) {
521 $loggedin = 1;
522 } else {
523 $info{'nopermission'} = 1;
527 unless ($userid) {
528 my $session = get_session("") or die "Auth ERROR: Cannot get_session()";
529 my $sessionID = $session->id;
530 $userid = $query->param('userid');
531 my $password = $query->param('password');
532 C4::Context->_new_userenv($sessionID);
533 my ( $return, $cardnumber ) = checkpw( $dbh, $userid, $password );
534 if ($return) {
535 _session_log(sprintf "%20s from %16s logged in at %30s.\n", $userid,$ENV{'REMOTE_ADDR'},localtime);
536 $cookie = $query->cookie(CGISESSID => $sessionID);
537 if ( $flags = haspermission( $dbh, $userid, $flagsrequired ) ) {
538 $loggedin = 1;
540 else {
541 $info{'nopermission'} = 1;
542 C4::Context->_unset_userenv($sessionID);
545 my ($borrowernumber, $firstname, $surname, $userflags,
546 $branchcode, $branchname, $branchprinter, $emailaddress);
548 if ( $return == 1 ) {
549 my $select = "
550 SELECT borrowernumber, firstname, surname, flags, borrowers.branchcode,
551 branches.branchname as branchname,
552 branches.branchprinter as branchprinter,
553 email
554 FROM borrowers
555 LEFT JOIN branches on borrowers.branchcode=branches.branchcode
557 my $sth = $dbh->prepare("$select where userid=?");
558 $sth->execute($userid);
559 unless ($sth->rows) {
560 $debug and print STDERR "AUTH_1: no rows for userid='$userid'\n";
561 $sth = $dbh->prepare("$select where cardnumber=?");
562 $sth->execute($cardnumber);
563 unless ($sth->rows) {
564 $debug and print STDERR "AUTH_2a: no rows for cardnumber='$cardnumber'\n";
565 $sth->execute($userid);
566 unless ($sth->rows) {
567 $debug and print STDERR "AUTH_2b: no rows for userid='$userid' AS cardnumber\n";
571 if ($sth->rows) {
572 ($borrowernumber, $firstname, $surname, $userflags,
573 $branchcode, $branchname, $branchprinter, $emailaddress) = $sth->fetchrow;
574 $debug and print STDERR "AUTH_3 results: " .
575 "$cardnumber,$borrowernumber,$userid,$firstname,$surname,$userflags,$branchcode,$emailaddress\n";
576 } else {
577 print STDERR "AUTH_3: no results for userid='$userid', cardnumber='$cardnumber'.\n";
580 # launch a sequence to check if we have a ip for the branch, i
581 # if we have one we replace the branchcode of the userenv by the branch bound in the ip.
583 my $ip = $ENV{'REMOTE_ADDR'};
584 # if they specify at login, use that
585 if ($query->param('branch')) {
586 $branchcode = $query->param('branch');
587 $branchname = GetBranchName($branchcode);
589 my $branches = GetBranches();
590 if (C4::Context->boolean_preference('IndependantBranches') && C4::Context->boolean_preference('Autolocation')){
591 # we have to check they are coming from the right ip range
592 my $domain = $branches->{$branchcode}->{'branchip'};
593 if ($ip !~ /^$domain/){
594 $loggedin=0;
595 $info{'wrongip'} = 1;
599 my @branchesloop;
600 foreach my $br ( keys %$branches ) {
601 # now we work with the treatment of ip
602 my $domain = $branches->{$br}->{'branchip'};
603 if ( $domain && $ip =~ /^$domain/ ) {
604 $branchcode = $branches->{$br}->{'branchcode'};
606 # new op dev : add the branchprinter and branchname in the cookie
607 $branchprinter = $branches->{$br}->{'branchprinter'};
608 $branchname = $branches->{$br}->{'branchname'};
611 $session->param('number',$borrowernumber);
612 $session->param('id',$userid);
613 $session->param('cardnumber',$cardnumber);
614 $session->param('firstname',$firstname);
615 $session->param('surname',$surname);
616 $session->param('branch',$branchcode);
617 $session->param('branchname',$branchname);
618 $session->param('flags',$userflags);
619 $session->param('emailaddress',$emailaddress);
620 $session->param('ip',$session->remote_addr());
621 $session->param('lasttime',time());
622 $debug and printf STDERR "AUTH_4: (%s)\t%s %s - %s\n", map {$session->param($_)} qw(cardnumber firstname surname branch) ;
624 elsif ( $return == 2 ) {
625 #We suppose the user is the superlibrarian
626 $borrowernumber = 0;
627 $session->param('number',0);
628 $session->param('id',C4::Context->config('user'));
629 $session->param('cardnumber',C4::Context->config('user'));
630 $session->param('firstname',C4::Context->config('user'));
631 $session->param('surname',C4::Context->config('user'));
632 $session->param('branch','NO_LIBRARY_SET');
633 $session->param('branchname','NO_LIBRARY_SET');
634 $session->param('flags',1);
635 $session->param('emailaddress', C4::Context->preference('KohaAdminEmailAddress'));
636 $session->param('ip',$session->remote_addr());
637 $session->param('lasttime',time());
639 C4::Context::set_userenv(
640 $session->param('number'), $session->param('id'),
641 $session->param('cardnumber'), $session->param('firstname'),
642 $session->param('surname'), $session->param('branch'),
643 $session->param('branchname'), $session->param('flags'),
644 $session->param('emailaddress'), $session->param('branchprinter')
646 $shelves = GetShelvesSummary($borrowernumber,2,10);
647 $session->param('shelves', $shelves);
648 C4::Context::set_shelves_userenv($shelves);
650 else {
651 if ($userid) {
652 $info{'invalid_username_or_password'} = 1;
653 C4::Context->_unset_userenv($sessionID);
657 } # END unless ($userid)
658 my $insecure = C4::Context->boolean_preference('insecure');
660 # finished authentification, now respond
661 if ( $loggedin || $authnotrequired || ( defined($insecure) && $insecure ) )
663 # successful login
664 unless ($cookie) {
665 $cookie = $query->cookie( CGISESSID => '' );
667 return ( $userid, $cookie, $sessionID, $flags );
672 # AUTH rejected, show the login/password template, after checking the DB.
676 # get the inputs from the incoming query
677 my @inputs = ();
678 foreach my $name ( param $query) {
679 (next) if ( $name eq 'userid' || $name eq 'password' );
680 my $value = $query->param($name);
681 push @inputs, { name => $name, value => $value };
683 # get the branchloop, which we need for authentication
684 my $branches = GetBranches();
685 my @branch_loop;
686 for my $branch_hash (sort keys %$branches) {
687 push @branch_loop, {branchcode => "$branch_hash", branchname => $branches->{$branch_hash}->{'branchname'}, };
690 my $template_name = ( $type eq 'opac' ) ? 'opac-auth.tmpl' : 'auth.tmpl';
691 my $template = gettemplate( $template_name, $type, $query );
692 $template->param(branchloop => \@branch_loop,);
693 $template->param(
694 login => 1,
695 INPUTS => \@inputs,
696 suggestion => C4::Context->preference("suggestion"),
697 virtualshelves => C4::Context->preference("virtualshelves"),
698 opaclargeimage => C4::Context->preference("opaclargeimage"),
699 LibraryName => C4::Context->preference("LibraryName"),
700 opacuserlogin => C4::Context->preference("opacuserlogin"),
701 OpacNav => C4::Context->preference("OpacNav"),
702 opaccredits => C4::Context->preference("opaccredits"),
703 opacreadinghistory => C4::Context->preference("opacreadinghistory"),
704 opacsmallimage => C4::Context->preference("opacsmallimage"),
705 opaclayoutstylesheet => C4::Context->preference("opaclayoutstylesheet"),
706 opaccolorstylesheet => C4::Context->preference("opaccolorstylesheet"),
707 opaclanguagesdisplay => C4::Context->preference("opaclanguagesdisplay"),
708 opacuserjs => C4::Context->preference("opacuserjs"),
709 intranetcolorstylesheet =>
710 C4::Context->preference("intranetcolorstylesheet"),
711 intranetstylesheet => C4::Context->preference("intranetstylesheet"),
712 IntranetNav => C4::Context->preference("IntranetNav"),
713 intranetuserjs => C4::Context->preference("intranetuserjs"),
714 TemplateEncoding => C4::Context->preference("TemplateEncoding"),
715 IndependantBranches=> C4::Context->preference("IndependantBranches"),
716 AutoLocation => C4::Context->preference("AutoLocation"),
717 yuipath => C4::Context->preference("yuipath"),
718 wrongip => $info{'wrongip'}
721 $template->param( loginprompt => 1 ) unless $info{'nopermission'};
723 my $self_url = $query->url( -absolute => 1 );
724 $template->param(
725 url => $self_url,
726 LibraryName => C4::Context->preference("LibraryName"),
728 $template->param( \%info );
729 # $cookie = $query->cookie(CGISESSID => $session->id
730 # );
731 print $query->header(
732 -type => 'text/html',
733 -charset => 'utf-8',
734 -cookie => $cookie
736 $template->output;
737 exit;
740 =item check_api_auth
742 ($status, $cookie, $sessionId) = check_api_auth($query, $userflags);
744 Given a CGI query containing the parameters 'userid' and 'password' and/or a session
745 cookie, determine if the user has the privileges specified by C<$userflags>.
747 C<check_api_auth> is is meant for authenticating users of web services, and
748 consequently will always return and will not attempt to redirect the user
749 agent.
751 If a valid session cookie is already present, check_api_auth will return a status
752 of "ok", the cookie, and the Koha session ID.
754 If no session cookie is present, check_api_auth will check the 'userid' and 'password
755 parameters and create a session cookie and Koha session if the supplied credentials
756 are OK.
758 Possible return values in C<$status> are:
760 =over 4
762 =item "ok" -- user authenticated; C<$cookie> and C<$sessionid> have valid values.
764 =item "failed" -- credentials are not correct; C<$cookie> and C<$sessionid> are undef
766 =item "maintenance" -- DB is in maintenance mode; no login possible at the moment
768 =item "expired -- session cookie has expired; API user should resubmit userid and password
770 =back
772 =cut
774 sub check_api_auth {
775 my $query = shift;
776 my $flagsrequired = shift;
778 my $dbh = C4::Context->dbh;
779 my $timeout = C4::Context->preference('timeout');
780 $timeout = 600 unless $timeout;
782 unless (C4::Context->preference('Version')) {
783 # database has not been installed yet
784 return ("maintenance", undef, undef);
786 my $kohaversion=C4::Context::KOHAVERSION;
787 $kohaversion =~ s/(.*\..*)\.(.*)\.(.*)/$1$2$3/;
788 if (C4::Context->preference('Version') < $kohaversion) {
789 # database in need of version update; assume that
790 # no API should be called while databsae is in
791 # this condition.
792 return ("maintenance", undef, undef);
795 # FIXME -- most of what follows is a copy-and-paste
796 # of code from checkauth. There is an obvious need
797 # for refactoring to separate the various parts of
798 # the authentication code, but as of 2007-11-19 this
799 # is deferred so as to not introduce bugs into the
800 # regular authentication code for Koha 3.0.
802 # see if we have a valid session cookie already
803 # however, if a userid parameter is present (i.e., from
804 # a form submission, assume that any current cookie
805 # is to be ignored
806 my $sessionID = undef;
807 unless ($query->param('userid')) {
808 $sessionID = $query->cookie("CGISESSID");
810 if ($sessionID) {
811 my $session = get_session($sessionID);
812 C4::Context->_new_userenv($sessionID);
813 if ($session) {
814 C4::Context::set_userenv(
815 $session->param('number'), $session->param('id'),
816 $session->param('cardnumber'), $session->param('firstname'),
817 $session->param('surname'), $session->param('branch'),
818 $session->param('branchname'), $session->param('flags'),
819 $session->param('emailaddress'), $session->param('branchprinter')
822 my $ip = $session->param('ip');
823 my $lasttime = $session->param('lasttime');
824 my $userid = $session->param('id');
825 if ( $lasttime < time() - $timeout ) {
826 # time out
827 $session->delete();
828 C4::Context->_unset_userenv($sessionID);
829 $userid = undef;
830 $sessionID = undef;
831 return ("expired", undef, undef);
832 } elsif ( $ip ne $ENV{'REMOTE_ADDR'} ) {
833 # IP address changed
834 $session->delete();
835 C4::Context->_unset_userenv($sessionID);
836 $userid = undef;
837 $sessionID = undef;
838 return ("expired", undef, undef);
839 } else {
840 my $cookie = $query->cookie( CGISESSID => $session->id );
841 $session->param('lasttime',time());
842 my $flags = haspermission( $dbh, $userid, $flagsrequired );
843 if ($flags) {
844 return ("ok", $cookie, $sessionID);
845 } else {
846 $session->delete();
847 C4::Context->_unset_userenv($sessionID);
848 $userid = undef;
849 $sessionID = undef;
850 return ("failed", undef, undef);
853 } else {
854 return ("expired", undef, undef);
856 } else {
857 # new login
858 my $userid = $query->param('userid');
859 my $password = $query->param('password');
860 unless ($userid and $password) {
861 # caller did something wrong, fail the authenticateion
862 return ("failed", undef, undef);
864 my ( $return, $cardnumber ) = checkpw( $dbh, $userid, $password );
865 if ($return and haspermission( $dbh, $userid, $flagsrequired)) {
866 my $session = get_session("");
867 return ("failed", undef, undef) unless $session;
869 my $sessionID = $session->id;
870 C4::Context->_new_userenv($sessionID);
871 my $cookie = $query->cookie(CGISESSID => $sessionID);
872 if ( $return == 1 ) {
873 my (
874 $borrowernumber, $firstname, $surname,
875 $userflags, $branchcode, $branchname,
876 $branchprinter, $emailaddress
878 my $sth =
879 $dbh->prepare(
880 "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=?"
882 $sth->execute($userid);
884 $borrowernumber, $firstname, $surname,
885 $userflags, $branchcode, $branchname,
886 $branchprinter, $emailaddress
887 ) = $sth->fetchrow if ( $sth->rows );
889 unless ($sth->rows ) {
890 my $sth = $dbh->prepare(
891 "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=?"
893 $sth->execute($cardnumber);
895 $borrowernumber, $firstname, $surname,
896 $userflags, $branchcode, $branchname,
897 $branchprinter, $emailaddress
898 ) = $sth->fetchrow if ( $sth->rows );
900 unless ( $sth->rows ) {
901 $sth->execute($userid);
903 $borrowernumber, $firstname, $surname, $userflags,
904 $branchcode, $branchname, $branchprinter, $emailaddress
905 ) = $sth->fetchrow if ( $sth->rows );
909 my $ip = $ENV{'REMOTE_ADDR'};
910 # if they specify at login, use that
911 if ($query->param('branch')) {
912 $branchcode = $query->param('branch');
913 $branchname = GetBranchName($branchcode);
915 my $branches = GetBranches();
916 my @branchesloop;
917 foreach my $br ( keys %$branches ) {
918 # now we work with the treatment of ip
919 my $domain = $branches->{$br}->{'branchip'};
920 if ( $domain && $ip =~ /^$domain/ ) {
921 $branchcode = $branches->{$br}->{'branchcode'};
923 # new op dev : add the branchprinter and branchname in the cookie
924 $branchprinter = $branches->{$br}->{'branchprinter'};
925 $branchname = $branches->{$br}->{'branchname'};
928 $session->param('number',$borrowernumber);
929 $session->param('id',$userid);
930 $session->param('cardnumber',$cardnumber);
931 $session->param('firstname',$firstname);
932 $session->param('surname',$surname);
933 $session->param('branch',$branchcode);
934 $session->param('branchname',$branchname);
935 $session->param('flags',$userflags);
936 $session->param('emailaddress',$emailaddress);
937 $session->param('ip',$session->remote_addr());
938 $session->param('lasttime',time());
939 } elsif ( $return == 2 ) {
940 #We suppose the user is the superlibrarian
941 $session->param('number',0);
942 $session->param('id',C4::Context->config('user'));
943 $session->param('cardnumber',C4::Context->config('user'));
944 $session->param('firstname',C4::Context->config('user'));
945 $session->param('surname',C4::Context->config('user'));
946 $session->param('branch','NO_LIBRARY_SET');
947 $session->param('branchname','NO_LIBRARY_SET');
948 $session->param('flags',1);
949 $session->param('emailaddress', C4::Context->preference('KohaAdminEmailAddress'));
950 $session->param('ip',$session->remote_addr());
951 $session->param('lasttime',time());
953 C4::Context::set_userenv(
954 $session->param('number'), $session->param('id'),
955 $session->param('cardnumber'), $session->param('firstname'),
956 $session->param('surname'), $session->param('branch'),
957 $session->param('branchname'), $session->param('flags'),
958 $session->param('emailaddress'), $session->param('branchprinter')
960 return ("ok", $cookie, $sessionID);
961 } else {
962 return ("failed", undef, undef);
967 =item check_cookie_auth
969 ($status, $sessionId) = check_api_auth($cookie, $userflags);
971 Given a CGISESSID cookie set during a previous login to Koha, determine
972 if the user has the privileges specified by C<$userflags>.
974 C<check_cookie_auth> is meant for authenticating special services
975 such as tools/upload-file.pl that are invoked by other pages that
976 have been authenticated in the usual way.
978 Possible return values in C<$status> are:
980 =over 4
982 =item "ok" -- user authenticated; C<$sessionID> have valid values.
984 =item "failed" -- credentials are not correct; C<$sessionid> are undef
986 =item "maintenance" -- DB is in maintenance mode; no login possible at the moment
988 =item "expired -- session cookie has expired; API user should resubmit userid and password
990 =back
992 =cut
994 sub check_cookie_auth {
995 my $cookie = shift;
996 my $flagsrequired = shift;
998 my $dbh = C4::Context->dbh;
999 my $timeout = C4::Context->preference('timeout');
1000 $timeout = 600 unless $timeout;
1002 unless (C4::Context->preference('Version')) {
1003 # database has not been installed yet
1004 return ("maintenance", undef);
1006 my $kohaversion=C4::Context::KOHAVERSION;
1007 $kohaversion =~ s/(.*\..*)\.(.*)\.(.*)/$1$2$3/;
1008 if (C4::Context->preference('Version') < $kohaversion) {
1009 # database in need of version update; assume that
1010 # no API should be called while databsae is in
1011 # this condition.
1012 return ("maintenance", undef);
1015 # FIXME -- most of what follows is a copy-and-paste
1016 # of code from checkauth. There is an obvious need
1017 # for refactoring to separate the various parts of
1018 # the authentication code, but as of 2007-11-23 this
1019 # is deferred so as to not introduce bugs into the
1020 # regular authentication code for Koha 3.0.
1022 # see if we have a valid session cookie already
1023 # however, if a userid parameter is present (i.e., from
1024 # a form submission, assume that any current cookie
1025 # is to be ignored
1026 unless (defined $cookie and $cookie) {
1027 return ("failed", undef);
1029 my $sessionID = $cookie;
1030 my $session = get_session($sessionID);
1031 C4::Context->_new_userenv($sessionID);
1032 if ($session) {
1033 C4::Context::set_userenv(
1034 $session->param('number'), $session->param('id'),
1035 $session->param('cardnumber'), $session->param('firstname'),
1036 $session->param('surname'), $session->param('branch'),
1037 $session->param('branchname'), $session->param('flags'),
1038 $session->param('emailaddress'), $session->param('branchprinter')
1041 my $ip = $session->param('ip');
1042 my $lasttime = $session->param('lasttime');
1043 my $userid = $session->param('id');
1044 if ( $lasttime < time() - $timeout ) {
1045 # time out
1046 $session->delete();
1047 C4::Context->_unset_userenv($sessionID);
1048 $userid = undef;
1049 $sessionID = undef;
1050 return ("expired", undef);
1051 } elsif ( $ip ne $ENV{'REMOTE_ADDR'} ) {
1052 # IP address changed
1053 $session->delete();
1054 C4::Context->_unset_userenv($sessionID);
1055 $userid = undef;
1056 $sessionID = undef;
1057 return ("expired", undef);
1058 } else {
1059 $session->param('lasttime',time());
1060 my $flags = haspermission( $dbh, $userid, $flagsrequired );
1061 if ($flags) {
1062 return ("ok", $sessionID);
1063 } else {
1064 $session->delete();
1065 C4::Context->_unset_userenv($sessionID);
1066 $userid = undef;
1067 $sessionID = undef;
1068 return ("failed", undef);
1071 } else {
1072 return ("expired", undef);
1076 =item get_session
1078 use CGI::Session;
1079 my $session = get_session($sessionID);
1081 Given a session ID, retrieve the CGI::Session object used to store
1082 the session's state. The session object can be used to store
1083 data that needs to be accessed by different scripts during a
1084 user's session.
1086 If the C<$sessionID> parameter is an empty string, a new session
1087 will be created.
1089 =cut
1091 sub get_session {
1092 my $sessionID = shift;
1093 my $storage_method = C4::Context->preference('SessionStorage');
1094 my $dbh = C4::Context->dbh;
1095 my $session;
1096 if ($storage_method eq 'mysql'){
1097 $session = new CGI::Session("driver:MySQL;serializer:yaml", $sessionID, {Handle=>$dbh});
1099 elsif ($storage_method eq 'Pg') {
1100 $session = new CGI::Session("driver:PostgreSQL;serializer:yaml", $sessionID, {Handle=>$dbh});
1102 else {
1103 # catch all defaults to tmp should work on all systems
1104 $session = new CGI::Session("driver:File;serializer:yaml", $sessionID, {Directory=>'/tmp'});
1106 return $session;
1109 sub checkpw {
1111 my ( $dbh, $userid, $password ) = @_;
1112 if ($ldap) {
1113 $debug and print "## checkpw - checking LDAP\n";
1114 my ($retval,$retcard) = checkpw_ldap(@_); # EXTERNAL AUTH
1115 ($retval) and return ($retval,$retcard);
1118 # INTERNAL AUTH
1119 my $sth =
1120 $dbh->prepare(
1121 "select password,cardnumber,borrowernumber,userid,firstname,surname,branchcode,flags from borrowers where userid=?"
1123 $sth->execute($userid);
1124 if ( $sth->rows ) {
1125 my ( $md5password, $cardnumber, $borrowernumber, $userid, $firstname,
1126 $surname, $branchcode, $flags )
1127 = $sth->fetchrow;
1128 if ( md5_base64($password) eq $md5password ) {
1130 C4::Context->set_userenv( "$borrowernumber", $userid, $cardnumber,
1131 $firstname, $surname, $branchcode, $flags );
1132 return 1, $cardnumber;
1135 $sth =
1136 $dbh->prepare(
1137 "select password,cardnumber,borrowernumber,userid, firstname,surname,branchcode,flags from borrowers where cardnumber=?"
1139 $sth->execute($userid);
1140 if ( $sth->rows ) {
1141 my ( $md5password, $cardnumber, $borrowernumber, $userid, $firstname,
1142 $surname, $branchcode, $flags )
1143 = $sth->fetchrow;
1144 if ( md5_base64($password) eq $md5password ) {
1146 C4::Context->set_userenv( $borrowernumber, $userid, $cardnumber,
1147 $firstname, $surname, $branchcode, $flags );
1148 return 1, $userid;
1151 if ( $userid && $userid eq C4::Context->config('user')
1152 && "$password" eq C4::Context->config('pass') )
1155 # Koha superuser account
1156 # C4::Context->set_userenv(0,0,C4::Context->config('user'),C4::Context->config('user'),C4::Context->config('user'),"",1);
1157 return 2;
1159 if ( $userid && $userid eq 'demo'
1160 && "$password" eq 'demo'
1161 && C4::Context->config('demo') )
1164 # DEMO => the demo user is allowed to do everything (if demo set to 1 in koha.conf
1165 # some features won't be effective : modify systempref, modify MARC structure,
1166 return 2;
1168 return 0;
1171 =item getuserflags
1173 $authflags = getuserflags($flags,$dbh);
1174 Translates integer flags into permissions strings hash.
1176 C<$flags> is the integer userflags value ( borrowers.userflags )
1177 C<$authflags> is a hashref of permissions
1179 =cut
1181 sub getuserflags {
1182 my $flags = shift;
1183 my $dbh = shift;
1184 my $userflags;
1185 $flags = 0 unless $flags;
1186 my $sth = $dbh->prepare("SELECT bit, flag, defaulton FROM userflags");
1187 $sth->execute;
1189 while ( my ( $bit, $flag, $defaulton ) = $sth->fetchrow ) {
1190 if ( ( $flags & ( 2**$bit ) ) || $defaulton ) {
1191 $userflags->{$flag} = 1;
1193 else {
1194 $userflags->{$flag} = 0;
1197 return $userflags;
1200 =item haspermission
1202 $flags = ($dbh,$member,$flagsrequired);
1204 C<$member> may be either userid or overloaded with $borrower hashref from GetMemberDetails.
1205 C<$flags> is a hashref of required flags lik C<$borrower-&lt;{authflags}>
1207 Returns member's flags or 0 if a permission is not met.
1209 =cut
1211 sub haspermission {
1212 my ( $dbh, $userid, $flagsrequired ) = @_;
1213 my ($flags,$intflags);
1214 $dbh=C4::Context->dbh unless($dbh);
1215 if(ref($userid)) {
1216 $intflags = $userid->{'flags'};
1217 } else {
1218 my $sth = $dbh->prepare("SELECT flags FROM borrowers WHERE userid=?");
1219 $sth->execute($userid);
1220 my ($intflags) = $sth->fetchrow;
1221 $flags = getuserflags( $intflags, $dbh );
1223 if ( $userid eq C4::Context->config('user') ) {
1224 # Super User Account from /etc/koha.conf
1225 $flags->{'superlibrarian'} = 1;
1227 if ( $userid eq 'demo' && C4::Context->config('demo') ) {
1228 # Demo user that can do "anything" (demo=1 in /etc/koha.conf)
1229 $flags->{'superlibrarian'} = 1;
1231 return $flags if $flags->{superlibrarian};
1232 foreach ( keys %$flagsrequired ) {
1233 return 0 unless( $flags->{$_} );
1235 return $flags;
1236 #FIXME - This fcn should return the failed permission so a suitable error msg can be delivered.
1240 sub getborrowernumber {
1241 my ($userid) = @_;
1242 my $dbh = C4::Context->dbh;
1243 for my $field ( 'userid', 'cardnumber' ) {
1244 my $sth =
1245 $dbh->prepare("select borrowernumber from borrowers where $field=?");
1246 $sth->execute($userid);
1247 if ( $sth->rows ) {
1248 my ($bnumber) = $sth->fetchrow;
1249 return $bnumber;
1252 return 0;
1255 END { } # module clean-up code here (global destructor)
1257 __END__
1259 =back
1261 =head1 SEE ALSO
1263 CGI(3)
1265 C4::Output(3)
1267 Digest::MD5(3)
1269 =cut