oups, sorry, fixing mistake in previous patch
[koha.git] / C4 / Auth.pm
blob4c4fcd661a00570bc5b404d67a47280e4ce01044
1 package C4::Auth;
3 # Copyright 2000-2002 Katipo Communications
5 # This file is part of Koha.
7 # Koha is free software; you can redistribute it and/or modify it under the
8 # terms of the GNU General Public License as published by the Free Software
9 # Foundation; either version 2 of the License, or (at your option) any later
10 # version.
12 # Koha is distributed in the hope that it will be useful, but WITHOUT ANY
13 # WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
14 # A PARTICULAR PURPOSE. See the GNU General Public License for more details.
16 # You should have received a copy of the GNU General Public License along with
17 # Koha; if not, write to the Free Software Foundation, Inc., 59 Temple Place,
18 # Suite 330, Boston, MA 02111-1307 USA
20 use strict;
21 use Digest::MD5 qw(md5_base64);
22 use Storable qw(thaw freeze);
23 use URI::Escape;
24 use CGI::Session;
26 require Exporter;
27 use C4::Context;
28 use C4::Output; # to get the template
29 use C4::Members;
30 use C4::Koha;
31 use C4::Branch; # GetBranches
32 use C4::VirtualShelves;
33 use POSIX qw/strftime/;
35 # use utf8;
36 use vars qw($VERSION @ISA @EXPORT @EXPORT_OK %EXPORT_TAGS $debug $ldap);
38 BEGIN {
39 $VERSION = 3.02; # set version for version checking
40 $debug = $ENV{DEBUG} || 0 ;
41 @ISA = qw(Exporter);
42 @EXPORT = qw(&checkauth &get_template_and_user &haspermission &get_user_subpermissions);
43 @EXPORT_OK = qw(&check_api_auth &get_session &check_cookie_auth &checkpw &get_all_subpermissions &get_user_subpermissions);
44 %EXPORT_TAGS = (EditPermissions => [qw(get_all_subpermissions get_user_subpermissions)]);
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;
60 use C4::Output;
62 my $query = new CGI;
64 my ($template, $borrowernumber, $cookie)
65 = get_template_and_user(
67 template_name => "opac-main.tmpl",
68 query => $query,
69 type => "opac",
70 authnotrequired => 1,
71 flagsrequired => {borrow => 1, catalogue => '*', tools => 'import_patrons' },
75 output_html_with_http_headers $query, $cookie, $template->output;
77 =head1 DESCRIPTION
79 The main function of this module is to provide
80 authentification. However the get_template_and_user function has
81 been provided so that a users login information is passed along
82 automatically. This gets loaded into the template.
84 =head1 FUNCTIONS
86 =over 2
88 =item get_template_and_user
90 my ($template, $borrowernumber, $cookie)
91 = get_template_and_user(
93 template_name => "opac-main.tmpl",
94 query => $query,
95 type => "opac",
96 authnotrequired => 1,
97 flagsrequired => {borrow => 1, catalogue => '*', tools => 'import_patrons' },
101 This call passes the C<query>, C<flagsrequired> and C<authnotrequired>
102 to C<&checkauth> (in this module) to perform authentification.
103 See C<&checkauth> for an explanation of these parameters.
105 The C<template_name> is then used to find the correct template for
106 the page. The authenticated users details are loaded onto the
107 template in the HTML::Template LOOP variable C<USER_INFO>. Also the
108 C<sessionID> is passed to the template. This can be used in templates
109 if cookies are disabled. It needs to be put as and input to every
110 authenticated page.
112 More information on the C<gettemplate> sub can be found in the
113 Output.pm module.
115 =cut
117 sub get_template_and_user {
118 my $in = shift;
119 my $template =
120 gettemplate( $in->{'template_name'}, $in->{'type'}, $in->{'query'} );
121 my ( $user, $cookie, $sessionID, $flags ) = checkauth(
122 $in->{'query'},
123 $in->{'authnotrequired'},
124 $in->{'flagsrequired'},
125 $in->{'type'}
126 ) unless ($in->{'template_name'}=~/maintenance/);
128 my $borrowernumber;
129 my $insecure = C4::Context->preference('insecure');
130 if ($user or $insecure) {
132 # load the template variables for stylesheets and JavaScript
133 $template->param( css_libs => $in->{'css_libs'} );
134 $template->param( css_module => $in->{'css_module'} );
135 $template->param( css_page => $in->{'css_page'} );
136 $template->param( css_widgets => $in->{'css_widgets'} );
138 $template->param( js_libs => $in->{'js_libs'} );
139 $template->param( js_module => $in->{'js_module'} );
140 $template->param( js_page => $in->{'js_page'} );
141 $template->param( js_widgets => $in->{'js_widgets'} );
143 # user info
144 $template->param( loggedinusername => $user );
145 $template->param( sessionID => $sessionID );
147 my ($total, $pubshelves, $barshelves) = C4::Context->get_shelves_userenv();
148 if (defined($pubshelves)) {
149 $template->param( pubshelves => scalar (@$pubshelves),
150 pubshelvesloop => $pubshelves,
152 $template->param( pubtotal => $total->{'pubtotal'}, ) if ($total->{'pubtotal'} > scalar (@$pubshelves));
154 if (defined($barshelves)) {
155 $template->param( barshelves => scalar (@$barshelves),
156 barshelvesloop => $barshelves,
158 $template->param( bartotal => $total->{'bartotal'}, ) if ($total->{'bartotal'} > scalar (@$barshelves));
161 $borrowernumber = getborrowernumber($user);
162 my ( $borr ) = GetMemberDetails( $borrowernumber );
163 my @bordat;
164 $bordat[0] = $borr;
165 $template->param( "USER_INFO" => \@bordat );
167 my $all_perms = get_all_subpermissions();
169 my @flagroots = qw(circulate catalogue parameters borrowers permissions reserveforothers borrow
170 editcatalogue updatecharges management tools editauthorities serials reports acquisition);
171 # We are going to use the $flags returned by checkauth
172 # to create the template's parameters that will indicate
173 # which menus the user can access.
174 if (( $flags && $flags->{superlibrarian}==1) or $insecure==1) {
175 $template->param( CAN_user_circulate => 1 );
176 $template->param( CAN_user_catalogue => 1 );
177 $template->param( CAN_user_parameters => 1 );
178 $template->param( CAN_user_borrowers => 1 );
179 $template->param( CAN_user_permissions => 1 );
180 $template->param( CAN_user_reserveforothers => 1 );
181 $template->param( CAN_user_borrow => 1 );
182 $template->param( CAN_user_editcatalogue => 1 );
183 $template->param( CAN_user_updatecharges => 1 );
184 $template->param( CAN_user_acquisition => 1 );
185 $template->param( CAN_user_management => 1 );
186 $template->param( CAN_user_tools => 1 );
187 $template->param( CAN_user_editauthorities => 1 );
188 $template->param( CAN_user_serials => 1 );
189 $template->param( CAN_user_reports => 1 );
190 $template->param( CAN_user_staffaccess => 1 );
191 foreach my $module (keys %$all_perms) {
192 foreach my $subperm (keys %{ $all_perms->{$module} }) {
193 $template->param( "CAN_user_${module}_${subperm}" => 1 );
198 if (C4::Context->preference('GranularPermissions')) {
199 if ( $flags ) {
200 foreach my $module (keys %$all_perms) {
201 if ( $flags->{$module} == 1) {
202 foreach my $subperm (keys %{ $all_perms->{$module} }) {
203 $template->param( "CAN_user_${module}_${subperm}" => 1 );
205 } elsif ( ref($flags->{$module}) ) {
206 foreach my $subperm (keys %{ $flags->{$module} } ) {
207 $template->param( "CAN_user_${module}_${subperm}" => 1 );
212 } else {
213 foreach my $module (keys %$all_perms) {
214 foreach my $subperm (keys %{ $all_perms->{$module} }) {
215 $template->param( "CAN_user_${module}_${subperm}" => 1 );
220 if ($flags) {
221 foreach my $module (keys %$flags) {
222 if ( $flags->{$module} == 1 or ref($flags->{$module}) ) {
223 $template->param( "CAN_user_$module" => 1 );
224 if ($module eq "parameters") {
225 $template->param( CAN_user_management => 1 );
230 # Logged-in opac search history
231 # If the requested template is an opac one and opac search history is enabled
232 if ($in->{'type'} == "opac" && C4::Context->preference('EnableOpacSearchHistory')) {
233 my $dbh = C4::Context->dbh;
234 my $query = "SELECT COUNT(*) FROM search_history WHERE userid=?";
235 my $sth = $dbh->prepare($query);
236 $sth->execute($borrowernumber);
238 # If at least one search has already been performed
239 if ($sth->fetchrow_array > 0) {
240 # We show the link in opac
241 $template->param(ShowOpacRecentSearchLink => 1);
244 # And if there's a cookie with searches performed when the user was not logged in,
245 # we add them to the logged-in search history
246 my @recentSearches;
247 my $searchcookie = $in->{'query'}->cookie('KohaOpacRecentSearches');
248 if ($searchcookie){
249 $searchcookie = uri_unescape($searchcookie);
250 if (thaw($searchcookie)) {
251 @recentSearches = @{thaw($searchcookie)};
254 if (@recentSearches > 0) {
255 my $query = "INSERT INTO search_history(userid, sessionid, query_desc, query_cgi, total, time) VALUES";
256 my $icount = 1;
257 foreach my $asearch (@recentSearches) {
258 $query .= "(";
259 $query .= $borrowernumber . ", ";
260 $query .= '"' . $in->{'query'}->cookie("CGISESSID") . "\", ";
261 $query .= '"' . $asearch->{'query_desc'} . "\", ";
262 $query .= '"' . $asearch->{'query_cgi'} . "\", ";
263 $query .= $asearch->{'total'} . ", ";
264 $query .= 'FROM_UNIXTIME(' . $asearch->{'time'} . "))";
265 if ($icount < @recentSearches) { $query .= ", ";}
266 $icount++;
269 my $sth = $dbh->prepare($query);
270 $sth->execute;
272 # And then, delete the cookie's content
273 my $newsearchcookie = $in->{'query'}->cookie(
274 -name => 'KohaOpacRecentSearches',
275 -value => freeze([]),
276 -expires => ''
278 $cookie = [$cookie, $newsearchcookie];
283 else { # if this is an anonymous session, setup to display public lists...
285 # load the template variables for stylesheets and JavaScript
286 $template->param( css_libs => $in->{'css_libs'} );
287 $template->param( css_module => $in->{'css_module'} );
288 $template->param( css_page => $in->{'css_page'} );
289 $template->param( css_widgets => $in->{'css_widgets'} );
291 $template->param( js_libs => $in->{'js_libs'} );
292 $template->param( js_module => $in->{'js_module'} );
293 $template->param( js_page => $in->{'js_page'} );
294 $template->param( js_widgets => $in->{'js_widgets'} );
296 $template->param( sessionID => $sessionID );
298 my ($total, $pubshelves) = C4::Context->get_shelves_userenv(); # an anonymous user has no 'barshelves'...
299 if (defined(($pubshelves))) {
300 $template->param( pubshelves => scalar (@$pubshelves),
301 pubshelvesloop => $pubshelves,
303 $template->param( pubtotal => $total->{'pubtotal'}, ) if ($total->{'pubtotal'} > scalar (@$pubshelves));
307 # Anonymous opac search history
308 # If opac search history is enabled and at least one search has already been performed
309 if (C4::Context->preference('EnableOpacSearchHistory') && $in->{'query'}->cookie('KohaOpacRecentSearches')) {
310 # We show the link in opac
311 if (thaw(uri_unescape($in->{'query'}->cookie('KohaOpacRecentSearches')))) {
312 my @recentSearches = @{thaw(uri_unescape($in->{'query'}->cookie('KohaOpacRecentSearches')))};
313 if (@recentSearches > 0) {
314 $template->param(ShowOpacRecentSearchLink => 1);
319 # these template parameters are set the same regardless of $in->{'type'}
320 $template->param(
321 "BiblioDefaultView".C4::Context->preference("BiblioDefaultView") => 1,
322 EnhancedMessagingPreferences => C4::Context->preference('EnhancedMessagingPreferences'),
323 GoogleJackets => C4::Context->preference("GoogleJackets"),
324 KohaAdminEmailAddress => "" . C4::Context->preference("KohaAdminEmailAddress"),
325 LoginBranchcode => (C4::Context->userenv?C4::Context->userenv->{"branch"}:"insecure"),
326 LoginFirstname => (C4::Context->userenv?C4::Context->userenv->{"firstname"}:"Bel"),
327 LoginSurname => C4::Context->userenv?C4::Context->userenv->{"surname"}:"Inconnu",
328 TagsEnabled => C4::Context->preference("TagsEnabled"),
329 hide_marc => C4::Context->preference("hide_marc"),
330 'item-level_itypes' => C4::Context->preference('item-level_itypes'),
331 patronimages => C4::Context->preference("patronimages"),
332 singleBranchMode => C4::Context->preference("singleBranchMode"),
333 XSLTDetailsDisplay => C4::Context->preference("XSLTDetailsDisplay"),
334 XSLTResultsDisplay => C4::Context->preference("XSLTResultsDisplay"),
337 if ( $in->{'type'} eq "intranet" ) {
338 $template->param(
339 AmazonContent => C4::Context->preference("AmazonContent"),
340 AmazonSimilarItems => C4::Context->preference("AmazonSimilarItems"),
341 AutoLocation => C4::Context->preference("AutoLocation"),
342 "BiblioDefaultView".C4::Context->preference("IntranetBiblioDefaultView") => 1,
343 CircAutocompl => C4::Context->preference("CircAutocompl"),
344 FRBRizeEditions => C4::Context->preference("FRBRizeEditions"),
345 IndependantBranches => C4::Context->preference("IndependantBranches"),
346 IntranetNav => C4::Context->preference("IntranetNav"),
347 IntranetmainUserblock => C4::Context->preference("IntranetmainUserblock"),
348 LibraryName => C4::Context->preference("LibraryName"),
349 LoginBranchname => (C4::Context->userenv?C4::Context->userenv->{"branchname"}:"insecure"),
350 TemplateEncoding => C4::Context->preference("TemplateEncoding"),
351 advancedMARCEditor => C4::Context->preference("advancedMARCEditor"),
352 canreservefromotherbranches => C4::Context->preference('canreservefromotherbranches'),
353 intranetcolorstylesheet => C4::Context->preference("intranetcolorstylesheet"),
354 intranetreadinghistory => C4::Context->preference("intranetreadinghistory"),
355 intranetstylesheet => C4::Context->preference("intranetstylesheet"),
356 intranetuserjs => C4::Context->preference("intranetuserjs"),
357 intranetbookbag => C4::Context->preference("intranetbookbag"),
358 noItemTypeImages => C4::Context->preference("noItemTypeImages"),
359 suggestion => C4::Context->preference("suggestion"),
360 virtualshelves => C4::Context->preference("virtualshelves"),
361 StaffSerialIssueDisplayCount => C4::Context->preference("StaffSerialIssueDisplayCount"),
362 NoZebra => C4::Context->preference('NoZebra'),
365 else {
366 warn "template type should be OPAC, here it is=[" . $in->{'type'} . "]" unless ( $in->{'type'} eq 'opac' );
367 #TODO : replace LibraryName syspref with 'system name', and remove this html processing
368 my $LibraryNameTitle = C4::Context->preference("LibraryName");
369 $LibraryNameTitle =~ s/<(?:\/?)(?:br|p)\s*(?:\/?)>/ /sgi;
370 $LibraryNameTitle =~ s/<(?:[^<>'"]|'(?:[^']*)'|"(?:[^"]*)")*>//sg;
371 # variables passed from CGI: opac_css_override and opac_search_limits.
372 my $opac_search_limit = $ENV{'OPAC_SEARCH_LIMIT'};
373 my $opac_limit_override = $ENV{'OPAC_LIMIT_OVERRIDE'};
374 my $mylibraryfirst = C4::Context->preference("SearchMyLibraryFirst");
375 my $opac_name;
376 if($opac_limit_override && ($opac_search_limit =~ /branch:(\w+)/) ){
377 $opac_name = C4::Branch::GetBranchName($1) # opac_search_limit is a branch, so we use it.
378 } elsif($mylibraryfirst){
379 $opac_name = C4::Branch::GetBranchName($mylibraryfirst);
381 $template->param(
382 AmazonContent => "" . C4::Context->preference("AmazonContent"),
383 AnonSuggestions => "" . C4::Context->preference("AnonSuggestions"),
384 AuthorisedValueImages => C4::Context->preference("AuthorisedValueImages"),
385 LibraryName => "" . C4::Context->preference("LibraryName"),
386 LibraryNameTitle => "" . $LibraryNameTitle,
387 LoginBranchname => C4::Context->userenv?C4::Context->userenv->{"branchname"}:"",
388 OPACAmazonSimilarItems => "" . C4::Context->preference("OPACAmazonSimilarItems"),
389 OPACFRBRizeEditions => C4::Context->preference("OPACFRBRizeEditions"),
390 OPACItemHolds => C4::Context->preference("OPACItemHolds"),
391 OPACShelfBrowser => "". C4::Context->preference("OPACShelfBrowser"),
392 OPACURLOpenInNewWindow => "" . C4::Context->preference("OPACURLOpenInNewWindow"),
393 OPACUserCSS => "". C4::Context->preference("OPACUserCSS"),
394 OPACViewOthersSuggestions => "" . C4::Context->preference("OPACViewOthersSuggestions"),
395 OpacAuthorities => C4::Context->preference("OpacAuthorities"),
396 OPACBaseURL => ($in->{'query'}->https() ? "https://" : "http://") . $ENV{'SERVER_NAME'} .
397 ($ENV{'SERVER_PORT'} eq ($in->{'query'}->https() ? "443" : "80") ? '' : ":$ENV{'SERVER_PORT'}"),
398 opac_name => $opac_name,
399 opac_css_override => $ENV{'OPAC_CSS_OVERRIDE'},
400 opac_search_limit => $opac_search_limit,
401 opac_limit_override => $opac_limit_override,
402 OpacBrowser => C4::Context->preference("OpacBrowser"),
403 OpacCloud => C4::Context->preference("OpacCloud"),
404 OpacMainUserBlock => "" . C4::Context->preference("OpacMainUserBlock"),
405 OpacNav => "" . C4::Context->preference("OpacNav"),
406 OpacPasswordChange => C4::Context->preference("OpacPasswordChange"),
407 OPACPatronDetails => C4::Context->preference("OPACPatronDetails"),
408 OPACFinesTab => C4::Context->preference("OPACFinesTab"),
409 OpacTopissue => C4::Context->preference("OpacTopissue"),
410 RequestOnOpac => C4::Context->preference("RequestOnOpac"),
411 TemplateEncoding => "". C4::Context->preference("TemplateEncoding"),
412 'Version' => C4::Context->preference('Version'),
413 hidelostitems => C4::Context->preference("hidelostitems"),
414 mylibraryfirst => (C4::Context->preference("SearchMyLibraryFirst") && C4::Context->userenv) ? C4::Context->userenv->{'branch'} : '',
415 opaclayoutstylesheet => "" . C4::Context->preference("opaclayoutstylesheet"),
416 opaccolorstylesheet => "" . C4::Context->preference("opaccolorstylesheet"),
417 opacstylesheet => "" . C4::Context->preference("opacstylesheet"),
418 opacbookbag => "" . C4::Context->preference("opacbookbag"),
419 opaccredits => "" . C4::Context->preference("opaccredits"),
420 opacheader => "" . C4::Context->preference("opacheader"),
421 opaclanguagesdisplay => "" . C4::Context->preference("opaclanguagesdisplay"),
422 opacreadinghistory => C4::Context->preference("opacreadinghistory"),
423 opacsmallimage => "" . C4::Context->preference("opacsmallimage"),
424 opacuserjs => C4::Context->preference("opacuserjs"),
425 opacuserlogin => "" . C4::Context->preference("opacuserlogin"),
426 reviewson => C4::Context->preference("reviewson"),
427 suggestion => "" . C4::Context->preference("suggestion"),
428 virtualshelves => "" . C4::Context->preference("virtualshelves"),
429 OPACSerialIssueDisplayCount => C4::Context->preference("OPACSerialIssueDisplayCount"),
432 $template->param(listloop=>[{shelfname=>"Freelist", shelfnumber=>110}]);
433 return ( $template, $borrowernumber, $cookie, $flags);
436 =item checkauth
438 ($userid, $cookie, $sessionID) = &checkauth($query, $noauth, $flagsrequired, $type);
440 Verifies that the user is authorized to run this script. If
441 the user is authorized, a (userid, cookie, session-id, flags)
442 quadruple is returned. If the user is not authorized but does
443 not have the required privilege (see $flagsrequired below), it
444 displays an error page and exits. Otherwise, it displays the
445 login page and exits.
447 Note that C<&checkauth> will return if and only if the user
448 is authorized, so it should be called early on, before any
449 unfinished operations (e.g., if you've opened a file, then
450 C<&checkauth> won't close it for you).
452 C<$query> is the CGI object for the script calling C<&checkauth>.
454 The C<$noauth> argument is optional. If it is set, then no
455 authorization is required for the script.
457 C<&checkauth> fetches user and session information from C<$query> and
458 ensures that the user is authorized to run scripts that require
459 authorization.
461 The C<$flagsrequired> argument specifies the required privileges
462 the user must have if the username and password are correct.
463 It should be specified as a reference-to-hash; keys in the hash
464 should be the "flags" for the user, as specified in the Members
465 intranet module. Any key specified must correspond to a "flag"
466 in the userflags table. E.g., { circulate => 1 } would specify
467 that the user must have the "circulate" privilege in order to
468 proceed. To make sure that access control is correct, the
469 C<$flagsrequired> parameter must be specified correctly.
471 If the GranularPermissions system preference is ON, the
472 value of each key in the C<flagsrequired> hash takes on an additional
473 meaning, e.g.,
475 =item 1
477 The user must have access to all subfunctions of the module
478 specified by the hash key.
480 =item *
482 The user must have access to at least one subfunction of the module
483 specified by the hash key.
485 =item specific permission, e.g., 'export_catalog'
487 The user must have access to the specific subfunction list, which
488 must correspond to a row in the permissions table.
490 The C<$type> argument specifies whether the template should be
491 retrieved from the opac or intranet directory tree. "opac" is
492 assumed if it is not specified; however, if C<$type> is specified,
493 "intranet" is assumed if it is not "opac".
495 If C<$query> does not have a valid session ID associated with it
496 (i.e., the user has not logged in) or if the session has expired,
497 C<&checkauth> presents the user with a login page (from the point of
498 view of the original script, C<&checkauth> does not return). Once the
499 user has authenticated, C<&checkauth> restarts the original script
500 (this time, C<&checkauth> returns).
502 The login page is provided using a HTML::Template, which is set in the
503 systempreferences table or at the top of this file. The variable C<$type>
504 selects which template to use, either the opac or the intranet
505 authentification template.
507 C<&checkauth> returns a user ID, a cookie, and a session ID. The
508 cookie should be sent back to the browser; it verifies that the user
509 has authenticated.
511 =cut
513 sub _version_check ($$) {
514 my $type = shift;
515 my $query = shift;
516 my $version;
517 # If Version syspref is unavailable, it means Koha is beeing installed,
518 # and so we must redirect to OPAC maintenance page or to the WebInstaller
519 # also, if OpacMaintenance is ON, OPAC should redirect to maintenance
520 if (C4::Context->preference('OpacMaintenance') && $type eq 'opac') {
521 warn "OPAC Install required, redirecting to maintenance";
522 print $query->redirect("/cgi-bin/koha/maintenance.pl");
524 unless ($version = C4::Context->preference('Version')) { # assignment, not comparison
525 if ($type ne 'opac') {
526 warn "Install required, redirecting to Installer";
527 print $query->redirect("/cgi-bin/koha/installer/install.pl");
529 else {
530 warn "OPAC Install required, redirecting to maintenance";
531 print $query->redirect("/cgi-bin/koha/maintenance.pl");
533 exit;
536 # check that database and koha version are the same
537 # there is no DB version, it's a fresh install,
538 # go to web installer
539 # there is a DB version, compare it to the code version
540 my $kohaversion=C4::Context::KOHAVERSION;
541 # remove the 3 last . to have a Perl number
542 $kohaversion =~ s/(.*\..*)\.(.*)\.(.*)/$1$2$3/;
543 $debug and print STDERR "kohaversion : $kohaversion\n";
544 if ($version < $kohaversion){
545 my $warning = "Database update needed, redirecting to %s. Database is $version and Koha is $kohaversion";
546 if ($type ne 'opac'){
547 warn sprintf($warning, 'Installer');
548 print $query->redirect("/cgi-bin/koha/installer/install.pl?step=3");
549 } else {
550 warn sprintf("OPAC: " . $warning, 'maintenance');
551 print $query->redirect("/cgi-bin/koha/maintenance.pl");
553 exit;
557 sub _session_log {
558 (@_) or return 0;
559 open L, ">>/tmp/sessionlog" or warn "ERROR: Cannot append to /tmp/sessionlog";
560 printf L join("\n",@_);
561 close L;
564 sub checkauth {
565 my $query = shift;
566 $debug and warn "Checking Auth";
567 # $authnotrequired will be set for scripts which will run without authentication
568 my $authnotrequired = shift;
569 my $flagsrequired = shift;
570 my $type = shift;
571 $type = 'opac' unless $type;
573 my $dbh = C4::Context->dbh;
574 my $timeout = C4::Context->preference('timeout');
575 # days
576 if ($timeout =~ /(\d+)[dD]/) {
577 $timeout = $1 * 86400;
579 $timeout = 600 unless $timeout;
581 _version_check($type,$query);
582 # state variables
583 my $loggedin = 0;
584 my %info;
585 my ( $userid, $cookie, $sessionID, $flags, $barshelves, $pubshelves );
586 my $logout = $query->param('logout.x');
588 if ( $userid = $ENV{'REMOTE_USER'} ) {
589 # Using Basic Authentication, no cookies required
590 $cookie = $query->cookie(
591 -name => 'CGISESSID',
592 -value => '',
593 -expires => ''
595 $loggedin = 1;
597 elsif ( $sessionID = $query->cookie("CGISESSID")) { # assignment, not comparison
598 my $session = get_session($sessionID);
599 C4::Context->_new_userenv($sessionID);
600 my ($ip, $lasttime, $sessiontype);
601 if ($session){
602 C4::Context::set_userenv(
603 $session->param('number'), $session->param('id'),
604 $session->param('cardnumber'), $session->param('firstname'),
605 $session->param('surname'), $session->param('branch'),
606 $session->param('branchname'), $session->param('flags'),
607 $session->param('emailaddress'), $session->param('branchprinter')
609 C4::Context::set_shelves_userenv('bar',$session->param('barshelves'));
610 C4::Context::set_shelves_userenv('pub',$session->param('pubshelves'));
611 C4::Context::set_shelves_userenv('tot',$session->param('totshelves'));
612 $debug and printf STDERR "AUTH_SESSION: (%s)\t%s %s - %s\n", map {$session->param($_)} qw(cardnumber firstname surname branch) ;
613 $ip = $session->param('ip');
614 $lasttime = $session->param('lasttime');
615 $userid = $session->param('id');
616 $sessiontype = $session->param('sessiontype');
619 if ( ($query->param('koha_login_context')) && ($query->param('userid') ne $session->param('id')) ) {
620 #if a user enters an id ne to the id in the current session, we need to log them in...
621 #first we need to clear the anonymous session...
622 $debug and warn "query id = " . $query->param('userid') . " but session id = " . $session->param('id');
623 $session->flush;
624 $session->delete();
625 C4::Context->_unset_userenv($sessionID);
626 $sessionID = undef;
627 $userid = undef;
629 elsif ($logout) {
630 # voluntary logout the user
631 $session->flush;
632 $session->delete();
633 C4::Context->_unset_userenv($sessionID);
634 _session_log(sprintf "%20s from %16s logged out at %30s (manually).\n", $userid,$ip,(strftime "%c",localtime));
635 $sessionID = undef;
636 $userid = undef;
638 elsif ( $lasttime < time() - $timeout ) {
639 # timed logout
640 $info{'timed_out'} = 1;
641 $session->delete();
642 C4::Context->_unset_userenv($sessionID);
643 _session_log(sprintf "%20s from %16s logged out at %30s (inactivity).\n", $userid,$ip,(strftime "%c",localtime));
644 $userid = undef;
645 $sessionID = undef;
647 elsif ( $ip ne $ENV{'REMOTE_ADDR'} ) {
648 # Different ip than originally logged in from
649 $info{'oldip'} = $ip;
650 $info{'newip'} = $ENV{'REMOTE_ADDR'};
651 $info{'different_ip'} = 1;
652 $session->delete();
653 C4::Context->_unset_userenv($sessionID);
654 _session_log(sprintf "%20s from %16s logged out at %30s (ip changed to %16s).\n", $userid,$ip,(strftime "%c",localtime), $info{'newip'});
655 $sessionID = undef;
656 $userid = undef;
658 else {
659 $cookie = $query->cookie( CGISESSID => $session->id );
660 $session->param('lasttime',time());
661 unless ( $sessiontype eq 'anon' ) { #if this is an anonymous session, we want to update the session, but not behave as if they are logged in...
662 $flags = haspermission($userid, $flagsrequired);
663 if ($flags) {
664 $loggedin = 1;
665 } else {
666 $info{'nopermission'} = 1;
671 unless ($userid || $sessionID) {
672 #we initiate a session prior to checking for a username to allow for anonymous sessions...
673 my $session = get_session("") or die "Auth ERROR: Cannot get_session()";
674 my $sessionID = $session->id;
675 C4::Context->_new_userenv($sessionID);
676 $cookie = $query->cookie(CGISESSID => $sessionID);
677 if ( $userid = $query->param('userid') ) {
678 my $password = $query->param('password');
679 my ( $return, $cardnumber ) = checkpw( $dbh, $userid, $password );
680 if ($return) {
681 _session_log(sprintf "%20s from %16s logged in at %30s.\n", $userid,$ENV{'REMOTE_ADDR'},(strftime "%c",localtime));
682 if ( $flags = haspermission($userid, $flagsrequired) ) {
683 $loggedin = 1;
685 else {
686 $info{'nopermission'} = 1;
687 C4::Context->_unset_userenv($sessionID);
690 my ($borrowernumber, $firstname, $surname, $userflags,
691 $branchcode, $branchname, $branchprinter, $emailaddress);
693 if ( $return == 1 ) {
694 my $select = "
695 SELECT borrowernumber, firstname, surname, flags, borrowers.branchcode,
696 branches.branchname as branchname,
697 branches.branchprinter as branchprinter,
698 email
699 FROM borrowers
700 LEFT JOIN branches on borrowers.branchcode=branches.branchcode
702 my $sth = $dbh->prepare("$select where userid=?");
703 $sth->execute($userid);
704 unless ($sth->rows) {
705 $debug and print STDERR "AUTH_1: no rows for userid='$userid'\n";
706 $sth = $dbh->prepare("$select where cardnumber=?");
707 $sth->execute($cardnumber);
708 unless ($sth->rows) {
709 $debug and print STDERR "AUTH_2a: no rows for cardnumber='$cardnumber'\n";
710 $sth->execute($userid);
711 unless ($sth->rows) {
712 $debug and print STDERR "AUTH_2b: no rows for userid='$userid' AS cardnumber\n";
716 if ($sth->rows) {
717 ($borrowernumber, $firstname, $surname, $userflags,
718 $branchcode, $branchname, $branchprinter, $emailaddress) = $sth->fetchrow;
719 $debug and print STDERR "AUTH_3 results: " .
720 "$cardnumber,$borrowernumber,$userid,$firstname,$surname,$userflags,$branchcode,$emailaddress\n";
721 } else {
722 print STDERR "AUTH_3: no results for userid='$userid', cardnumber='$cardnumber'.\n";
725 # launch a sequence to check if we have a ip for the branch, i
726 # if we have one we replace the branchcode of the userenv by the branch bound in the ip.
728 my $ip = $ENV{'REMOTE_ADDR'};
729 # if they specify at login, use that
730 if ($query->param('branch')) {
731 $branchcode = $query->param('branch');
732 $branchname = GetBranchName($branchcode);
734 my $branches = GetBranches();
735 if (C4::Context->boolean_preference('IndependantBranches') && C4::Context->boolean_preference('Autolocation')){
736 # we have to check they are coming from the right ip range
737 my $domain = $branches->{$branchcode}->{'branchip'};
738 if ($ip !~ /^$domain/){
739 $loggedin=0;
740 $info{'wrongip'} = 1;
744 my @branchesloop;
745 foreach my $br ( keys %$branches ) {
746 # now we work with the treatment of ip
747 my $domain = $branches->{$br}->{'branchip'};
748 if ( $domain && $ip =~ /^$domain/ ) {
749 $branchcode = $branches->{$br}->{'branchcode'};
751 # new op dev : add the branchprinter and branchname in the cookie
752 $branchprinter = $branches->{$br}->{'branchprinter'};
753 $branchname = $branches->{$br}->{'branchname'};
756 $session->param('number',$borrowernumber);
757 $session->param('id',$userid);
758 $session->param('cardnumber',$cardnumber);
759 $session->param('firstname',$firstname);
760 $session->param('surname',$surname);
761 $session->param('branch',$branchcode);
762 $session->param('branchname',$branchname);
763 $session->param('flags',$userflags);
764 $session->param('emailaddress',$emailaddress);
765 $session->param('ip',$session->remote_addr());
766 $session->param('lasttime',time());
767 $debug and printf STDERR "AUTH_4: (%s)\t%s %s - %s\n", map {$session->param($_)} qw(cardnumber firstname surname branch) ;
769 elsif ( $return == 2 ) {
770 #We suppose the user is the superlibrarian
771 $borrowernumber = 0;
772 $session->param('number',0);
773 $session->param('id',C4::Context->config('user'));
774 $session->param('cardnumber',C4::Context->config('user'));
775 $session->param('firstname',C4::Context->config('user'));
776 $session->param('surname',C4::Context->config('user'));
777 $session->param('branch','NO_LIBRARY_SET');
778 $session->param('branchname','NO_LIBRARY_SET');
779 $session->param('flags',1);
780 $session->param('emailaddress', C4::Context->preference('KohaAdminEmailAddress'));
781 $session->param('ip',$session->remote_addr());
782 $session->param('lasttime',time());
784 C4::Context::set_userenv(
785 $session->param('number'), $session->param('id'),
786 $session->param('cardnumber'), $session->param('firstname'),
787 $session->param('surname'), $session->param('branch'),
788 $session->param('branchname'), $session->param('flags'),
789 $session->param('emailaddress'), $session->param('branchprinter')
792 # Grab borrower's shelves and public shelves and add them to the session
793 # $row_count determines how many records are returned from the db query
794 # and the number of lists to be displayed of each type in the 'Lists' button drop down
795 my $row_count = 10; # FIXME:This probably should be a syspref
796 my ($total, $totshelves, $barshelves, $pubshelves);
797 ($barshelves, $totshelves) = C4::VirtualShelves::GetRecentShelves(1, $row_count, $borrowernumber);
798 $total->{'bartotal'} = $totshelves;
799 ($pubshelves, $totshelves) = C4::VirtualShelves::GetRecentShelves(2, $row_count, undef);
800 $total->{'pubtotal'} = $totshelves;
801 $session->param('barshelves', $barshelves->[0]);
802 $session->param('pubshelves', $pubshelves->[0]);
803 $session->param('totshelves', $total);
805 C4::Context::set_shelves_userenv('bar',$barshelves->[0]);
806 C4::Context::set_shelves_userenv('pub',$pubshelves->[0]);
807 C4::Context::set_shelves_userenv('tot',$total);
809 else {
810 if ($userid) {
811 $info{'invalid_username_or_password'} = 1;
812 C4::Context->_unset_userenv($sessionID);
815 } # END if ( $userid = $query->param('userid') )
816 elsif ($type eq "opac") {
817 # if we are here this is an anonymous session; add public lists to it and a few other items...
818 # anonymous sessions are created only for the OPAC
819 $debug and warn "Initiating an anonymous session...";
821 # Grab the public shelves and add to the session...
822 my $row_count = 20; # FIXME:This probably should be a syspref
823 my ($total, $totshelves, $pubshelves);
824 ($pubshelves, $totshelves) = C4::VirtualShelves::GetRecentShelves(2, $row_count, undef);
825 $total->{'pubtotal'} = $totshelves;
826 $session->param('pubshelves', $pubshelves->[0]);
827 $session->param('totshelves', $total);
828 C4::Context::set_shelves_userenv('pub',$pubshelves->[0]);
829 C4::Context::set_shelves_userenv('tot',$total);
831 # setting a couple of other session vars...
832 $session->param('ip',$session->remote_addr());
833 $session->param('lasttime',time());
834 $session->param('sessiontype','anon');
836 } # END unless ($userid)
837 my $insecure = C4::Context->boolean_preference('insecure');
839 # finished authentification, now respond
840 if ( $loggedin || $authnotrequired || ( defined($insecure) && $insecure ) )
842 # successful login
843 unless ($cookie) {
844 $cookie = $query->cookie( CGISESSID => '' );
846 return ( $userid, $cookie, $sessionID, $flags );
851 # AUTH rejected, show the login/password template, after checking the DB.
855 # get the inputs from the incoming query
856 my @inputs = ();
857 foreach my $name ( param $query) {
858 (next) if ( $name eq 'userid' || $name eq 'password' );
859 my $value = $query->param($name);
860 push @inputs, { name => $name, value => $value };
862 # get the branchloop, which we need for authentication
863 my $branches = GetBranches();
864 my @branch_loop;
865 for my $branch_hash (sort keys %$branches) {
866 push @branch_loop, {branchcode => "$branch_hash", branchname => $branches->{$branch_hash}->{'branchname'}, };
869 my $template_name = ( $type eq 'opac' ) ? 'opac-auth.tmpl' : 'auth.tmpl';
870 my $template = gettemplate( $template_name, $type, $query );
871 $template->param(branchloop => \@branch_loop,);
872 $template->param(
873 login => 1,
874 INPUTS => \@inputs,
875 suggestion => C4::Context->preference("suggestion"),
876 virtualshelves => C4::Context->preference("virtualshelves"),
877 LibraryName => C4::Context->preference("LibraryName"),
878 opacuserlogin => C4::Context->preference("opacuserlogin"),
879 OpacNav => C4::Context->preference("OpacNav"),
880 opaccredits => C4::Context->preference("opaccredits"),
881 opacreadinghistory => C4::Context->preference("opacreadinghistory"),
882 opacsmallimage => C4::Context->preference("opacsmallimage"),
883 opaclayoutstylesheet => C4::Context->preference("opaclayoutstylesheet"),
884 opaccolorstylesheet => C4::Context->preference("opaccolorstylesheet"),
885 opaclanguagesdisplay => C4::Context->preference("opaclanguagesdisplay"),
886 opacuserjs => C4::Context->preference("opacuserjs"),
887 opacbookbag => "" . C4::Context->preference("opacbookbag"),
888 OpacCloud => C4::Context->preference("OpacCloud"),
889 OpacTopissue => C4::Context->preference("OpacTopissue"),
890 OpacAuthorities => C4::Context->preference("OpacAuthorities"),
891 OpacBrowser => C4::Context->preference("OpacBrowser"),
892 opacheader => C4::Context->preference("opacheader"),
893 TagsEnabled => C4::Context->preference("TagsEnabled"),
894 OPACUserCSS => C4::Context->preference("OPACUserCSS"),
895 intranetcolorstylesheet =>
896 C4::Context->preference("intranetcolorstylesheet"),
897 intranetstylesheet => C4::Context->preference("intranetstylesheet"),
898 intranetbookbag => C4::Context->preference("intranetbookbag"),
899 IntranetNav => C4::Context->preference("IntranetNav"),
900 intranetuserjs => C4::Context->preference("intranetuserjs"),
901 TemplateEncoding => C4::Context->preference("TemplateEncoding"),
902 IndependantBranches=> C4::Context->preference("IndependantBranches"),
903 AutoLocation => C4::Context->preference("AutoLocation"),
904 wrongip => $info{'wrongip'}
906 $template->param( loginprompt => 1 ) unless $info{'nopermission'};
908 my $self_url = $query->url( -absolute => 1 );
909 $template->param(
910 url => $self_url,
911 LibraryName => C4::Context->preference("LibraryName"),
913 $template->param( \%info );
914 # $cookie = $query->cookie(CGISESSID => $session->id
915 # );
916 print $query->header(
917 -type => 'text/html',
918 -charset => 'utf-8',
919 -cookie => $cookie
921 $template->output;
922 exit;
925 =item check_api_auth
927 ($status, $cookie, $sessionId) = check_api_auth($query, $userflags);
929 Given a CGI query containing the parameters 'userid' and 'password' and/or a session
930 cookie, determine if the user has the privileges specified by C<$userflags>.
932 C<check_api_auth> is is meant for authenticating users of web services, and
933 consequently will always return and will not attempt to redirect the user
934 agent.
936 If a valid session cookie is already present, check_api_auth will return a status
937 of "ok", the cookie, and the Koha session ID.
939 If no session cookie is present, check_api_auth will check the 'userid' and 'password
940 parameters and create a session cookie and Koha session if the supplied credentials
941 are OK.
943 Possible return values in C<$status> are:
945 =over 4
947 =item "ok" -- user authenticated; C<$cookie> and C<$sessionid> have valid values.
949 =item "failed" -- credentials are not correct; C<$cookie> and C<$sessionid> are undef
951 =item "maintenance" -- DB is in maintenance mode; no login possible at the moment
953 =item "expired -- session cookie has expired; API user should resubmit userid and password
955 =back
957 =cut
959 sub check_api_auth {
960 my $query = shift;
961 my $flagsrequired = shift;
963 my $dbh = C4::Context->dbh;
964 my $timeout = C4::Context->preference('timeout');
965 $timeout = 600 unless $timeout;
967 unless (C4::Context->preference('Version')) {
968 # database has not been installed yet
969 return ("maintenance", undef, undef);
971 my $kohaversion=C4::Context::KOHAVERSION;
972 $kohaversion =~ s/(.*\..*)\.(.*)\.(.*)/$1$2$3/;
973 if (C4::Context->preference('Version') < $kohaversion) {
974 # database in need of version update; assume that
975 # no API should be called while databsae is in
976 # this condition.
977 return ("maintenance", undef, undef);
980 # FIXME -- most of what follows is a copy-and-paste
981 # of code from checkauth. There is an obvious need
982 # for refactoring to separate the various parts of
983 # the authentication code, but as of 2007-11-19 this
984 # is deferred so as to not introduce bugs into the
985 # regular authentication code for Koha 3.0.
987 # see if we have a valid session cookie already
988 # however, if a userid parameter is present (i.e., from
989 # a form submission, assume that any current cookie
990 # is to be ignored
991 my $sessionID = undef;
992 unless ($query->param('userid')) {
993 $sessionID = $query->cookie("CGISESSID");
995 if ($sessionID) {
996 my $session = get_session($sessionID);
997 C4::Context->_new_userenv($sessionID);
998 if ($session) {
999 C4::Context::set_userenv(
1000 $session->param('number'), $session->param('id'),
1001 $session->param('cardnumber'), $session->param('firstname'),
1002 $session->param('surname'), $session->param('branch'),
1003 $session->param('branchname'), $session->param('flags'),
1004 $session->param('emailaddress'), $session->param('branchprinter')
1007 my $ip = $session->param('ip');
1008 my $lasttime = $session->param('lasttime');
1009 my $userid = $session->param('id');
1010 if ( $lasttime < time() - $timeout ) {
1011 # time out
1012 $session->delete();
1013 C4::Context->_unset_userenv($sessionID);
1014 $userid = undef;
1015 $sessionID = undef;
1016 return ("expired", undef, undef);
1017 } elsif ( $ip ne $ENV{'REMOTE_ADDR'} ) {
1018 # IP address changed
1019 $session->delete();
1020 C4::Context->_unset_userenv($sessionID);
1021 $userid = undef;
1022 $sessionID = undef;
1023 return ("expired", undef, undef);
1024 } else {
1025 my $cookie = $query->cookie( CGISESSID => $session->id );
1026 $session->param('lasttime',time());
1027 my $flags = haspermission($userid, $flagsrequired);
1028 if ($flags) {
1029 return ("ok", $cookie, $sessionID);
1030 } else {
1031 $session->delete();
1032 C4::Context->_unset_userenv($sessionID);
1033 $userid = undef;
1034 $sessionID = undef;
1035 return ("failed", undef, undef);
1038 } else {
1039 return ("expired", undef, undef);
1041 } else {
1042 # new login
1043 my $userid = $query->param('userid');
1044 my $password = $query->param('password');
1045 unless ($userid and $password) {
1046 # caller did something wrong, fail the authenticateion
1047 return ("failed", undef, undef);
1049 my ( $return, $cardnumber ) = checkpw( $dbh, $userid, $password );
1050 if ($return and haspermission($userid, $flagsrequired)) {
1051 my $session = get_session("");
1052 return ("failed", undef, undef) unless $session;
1054 my $sessionID = $session->id;
1055 C4::Context->_new_userenv($sessionID);
1056 my $cookie = $query->cookie(CGISESSID => $sessionID);
1057 if ( $return == 1 ) {
1058 my (
1059 $borrowernumber, $firstname, $surname,
1060 $userflags, $branchcode, $branchname,
1061 $branchprinter, $emailaddress
1063 my $sth =
1064 $dbh->prepare(
1065 "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=?"
1067 $sth->execute($userid);
1069 $borrowernumber, $firstname, $surname,
1070 $userflags, $branchcode, $branchname,
1071 $branchprinter, $emailaddress
1072 ) = $sth->fetchrow if ( $sth->rows );
1074 unless ($sth->rows ) {
1075 my $sth = $dbh->prepare(
1076 "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=?"
1078 $sth->execute($cardnumber);
1080 $borrowernumber, $firstname, $surname,
1081 $userflags, $branchcode, $branchname,
1082 $branchprinter, $emailaddress
1083 ) = $sth->fetchrow if ( $sth->rows );
1085 unless ( $sth->rows ) {
1086 $sth->execute($userid);
1088 $borrowernumber, $firstname, $surname, $userflags,
1089 $branchcode, $branchname, $branchprinter, $emailaddress
1090 ) = $sth->fetchrow if ( $sth->rows );
1094 my $ip = $ENV{'REMOTE_ADDR'};
1095 # if they specify at login, use that
1096 if ($query->param('branch')) {
1097 $branchcode = $query->param('branch');
1098 $branchname = GetBranchName($branchcode);
1100 my $branches = GetBranches();
1101 my @branchesloop;
1102 foreach my $br ( keys %$branches ) {
1103 # now we work with the treatment of ip
1104 my $domain = $branches->{$br}->{'branchip'};
1105 if ( $domain && $ip =~ /^$domain/ ) {
1106 $branchcode = $branches->{$br}->{'branchcode'};
1108 # new op dev : add the branchprinter and branchname in the cookie
1109 $branchprinter = $branches->{$br}->{'branchprinter'};
1110 $branchname = $branches->{$br}->{'branchname'};
1113 $session->param('number',$borrowernumber);
1114 $session->param('id',$userid);
1115 $session->param('cardnumber',$cardnumber);
1116 $session->param('firstname',$firstname);
1117 $session->param('surname',$surname);
1118 $session->param('branch',$branchcode);
1119 $session->param('branchname',$branchname);
1120 $session->param('flags',$userflags);
1121 $session->param('emailaddress',$emailaddress);
1122 $session->param('ip',$session->remote_addr());
1123 $session->param('lasttime',time());
1124 } elsif ( $return == 2 ) {
1125 #We suppose the user is the superlibrarian
1126 $session->param('number',0);
1127 $session->param('id',C4::Context->config('user'));
1128 $session->param('cardnumber',C4::Context->config('user'));
1129 $session->param('firstname',C4::Context->config('user'));
1130 $session->param('surname',C4::Context->config('user'));
1131 $session->param('branch','NO_LIBRARY_SET');
1132 $session->param('branchname','NO_LIBRARY_SET');
1133 $session->param('flags',1);
1134 $session->param('emailaddress', C4::Context->preference('KohaAdminEmailAddress'));
1135 $session->param('ip',$session->remote_addr());
1136 $session->param('lasttime',time());
1138 C4::Context::set_userenv(
1139 $session->param('number'), $session->param('id'),
1140 $session->param('cardnumber'), $session->param('firstname'),
1141 $session->param('surname'), $session->param('branch'),
1142 $session->param('branchname'), $session->param('flags'),
1143 $session->param('emailaddress'), $session->param('branchprinter')
1145 return ("ok", $cookie, $sessionID);
1146 } else {
1147 return ("failed", undef, undef);
1152 =item check_cookie_auth
1154 ($status, $sessionId) = check_api_auth($cookie, $userflags);
1156 Given a CGISESSID cookie set during a previous login to Koha, determine
1157 if the user has the privileges specified by C<$userflags>.
1159 C<check_cookie_auth> is meant for authenticating special services
1160 such as tools/upload-file.pl that are invoked by other pages that
1161 have been authenticated in the usual way.
1163 Possible return values in C<$status> are:
1165 =over 4
1167 =item "ok" -- user authenticated; C<$sessionID> have valid values.
1169 =item "failed" -- credentials are not correct; C<$sessionid> are undef
1171 =item "maintenance" -- DB is in maintenance mode; no login possible at the moment
1173 =item "expired -- session cookie has expired; API user should resubmit userid and password
1175 =back
1177 =cut
1179 sub check_cookie_auth {
1180 my $cookie = shift;
1181 my $flagsrequired = shift;
1183 my $dbh = C4::Context->dbh;
1184 my $timeout = C4::Context->preference('timeout');
1185 $timeout = 600 unless $timeout;
1187 unless (C4::Context->preference('Version')) {
1188 # database has not been installed yet
1189 return ("maintenance", undef);
1191 my $kohaversion=C4::Context::KOHAVERSION;
1192 $kohaversion =~ s/(.*\..*)\.(.*)\.(.*)/$1$2$3/;
1193 if (C4::Context->preference('Version') < $kohaversion) {
1194 # database in need of version update; assume that
1195 # no API should be called while databsae is in
1196 # this condition.
1197 return ("maintenance", undef);
1200 # FIXME -- most of what follows is a copy-and-paste
1201 # of code from checkauth. There is an obvious need
1202 # for refactoring to separate the various parts of
1203 # the authentication code, but as of 2007-11-23 this
1204 # is deferred so as to not introduce bugs into the
1205 # regular authentication code for Koha 3.0.
1207 # see if we have a valid session cookie already
1208 # however, if a userid parameter is present (i.e., from
1209 # a form submission, assume that any current cookie
1210 # is to be ignored
1211 unless (defined $cookie and $cookie) {
1212 return ("failed", undef);
1214 my $sessionID = $cookie;
1215 my $session = get_session($sessionID);
1216 C4::Context->_new_userenv($sessionID);
1217 if ($session) {
1218 C4::Context::set_userenv(
1219 $session->param('number'), $session->param('id'),
1220 $session->param('cardnumber'), $session->param('firstname'),
1221 $session->param('surname'), $session->param('branch'),
1222 $session->param('branchname'), $session->param('flags'),
1223 $session->param('emailaddress'), $session->param('branchprinter')
1226 my $ip = $session->param('ip');
1227 my $lasttime = $session->param('lasttime');
1228 my $userid = $session->param('id');
1229 if ( $lasttime < time() - $timeout ) {
1230 # time out
1231 $session->delete();
1232 C4::Context->_unset_userenv($sessionID);
1233 $userid = undef;
1234 $sessionID = undef;
1235 return ("expired", undef);
1236 } elsif ( $ip ne $ENV{'REMOTE_ADDR'} ) {
1237 # IP address changed
1238 $session->delete();
1239 C4::Context->_unset_userenv($sessionID);
1240 $userid = undef;
1241 $sessionID = undef;
1242 return ("expired", undef);
1243 } else {
1244 $session->param('lasttime',time());
1245 my $flags = haspermission($userid, $flagsrequired);
1246 if ($flags) {
1247 return ("ok", $sessionID);
1248 } else {
1249 $session->delete();
1250 C4::Context->_unset_userenv($sessionID);
1251 $userid = undef;
1252 $sessionID = undef;
1253 return ("failed", undef);
1256 } else {
1257 return ("expired", undef);
1261 =item get_session
1263 use CGI::Session;
1264 my $session = get_session($sessionID);
1266 Given a session ID, retrieve the CGI::Session object used to store
1267 the session's state. The session object can be used to store
1268 data that needs to be accessed by different scripts during a
1269 user's session.
1271 If the C<$sessionID> parameter is an empty string, a new session
1272 will be created.
1274 =cut
1276 sub get_session {
1277 my $sessionID = shift;
1278 my $storage_method = C4::Context->preference('SessionStorage');
1279 my $dbh = C4::Context->dbh;
1280 my $session;
1281 if ($storage_method eq 'mysql'){
1282 $session = new CGI::Session("driver:MySQL;serializer:yaml;id:md5", $sessionID, {Handle=>$dbh});
1284 elsif ($storage_method eq 'Pg') {
1285 $session = new CGI::Session("driver:PostgreSQL;serializer:yaml;id:md5", $sessionID, {Handle=>$dbh});
1287 else {
1288 # catch all defaults to tmp should work on all systems
1289 $session = new CGI::Session("driver:File;serializer:yaml;id:md5", $sessionID, {Directory=>'/tmp'});
1291 return $session;
1294 sub checkpw {
1296 my ( $dbh, $userid, $password ) = @_;
1297 if ($ldap) {
1298 $debug and print "## checkpw - checking LDAP\n";
1299 my ($retval,$retcard) = checkpw_ldap(@_); # EXTERNAL AUTH
1300 ($retval) and return ($retval,$retcard);
1303 # INTERNAL AUTH
1304 my $sth =
1305 $dbh->prepare(
1306 "select password,cardnumber,borrowernumber,userid,firstname,surname,branchcode,flags from borrowers where userid=?"
1308 $sth->execute($userid);
1309 if ( $sth->rows ) {
1310 my ( $md5password, $cardnumber, $borrowernumber, $userid, $firstname,
1311 $surname, $branchcode, $flags )
1312 = $sth->fetchrow;
1313 if ( md5_base64($password) eq $md5password and $md5password ne "!") {
1315 C4::Context->set_userenv( "$borrowernumber", $userid, $cardnumber,
1316 $firstname, $surname, $branchcode, $flags );
1317 return 1, $cardnumber;
1320 $sth =
1321 $dbh->prepare(
1322 "select password,cardnumber,borrowernumber,userid, firstname,surname,branchcode,flags from borrowers where cardnumber=?"
1324 $sth->execute($userid);
1325 if ( $sth->rows ) {
1326 my ( $md5password, $cardnumber, $borrowernumber, $userid, $firstname,
1327 $surname, $branchcode, $flags )
1328 = $sth->fetchrow;
1329 if ( md5_base64($password) eq $md5password ) {
1331 C4::Context->set_userenv( $borrowernumber, $userid, $cardnumber,
1332 $firstname, $surname, $branchcode, $flags );
1333 return 1, $userid;
1336 if ( $userid && $userid eq C4::Context->config('user')
1337 && "$password" eq C4::Context->config('pass') )
1340 # Koha superuser account
1341 # C4::Context->set_userenv(0,0,C4::Context->config('user'),C4::Context->config('user'),C4::Context->config('user'),"",1);
1342 return 2;
1344 if ( $userid && $userid eq 'demo'
1345 && "$password" eq 'demo'
1346 && C4::Context->config('demo') )
1349 # DEMO => the demo user is allowed to do everything (if demo set to 1 in koha.conf
1350 # some features won't be effective : modify systempref, modify MARC structure,
1351 return 2;
1353 return 0;
1356 =item getuserflags
1358 my $authflags = getuserflags($flags, $userid, [$dbh]);
1360 Translates integer flags into permissions strings hash.
1362 C<$flags> is the integer userflags value ( borrowers.userflags )
1363 C<$userid> is the members.userid, used for building subpermissions
1364 C<$authflags> is a hashref of permissions
1366 =cut
1368 sub getuserflags {
1369 my $flags = shift;
1370 my $userid = shift;
1371 my $dbh = @_ ? shift : C4::Context->dbh;
1372 my $userflags;
1373 $flags = 0 unless $flags;
1374 my $sth = $dbh->prepare("SELECT bit, flag, defaulton FROM userflags");
1375 $sth->execute;
1377 while ( my ( $bit, $flag, $defaulton ) = $sth->fetchrow ) {
1378 if ( ( $flags & ( 2**$bit ) ) || $defaulton ) {
1379 $userflags->{$flag} = 1;
1381 else {
1382 $userflags->{$flag} = 0;
1386 # get subpermissions and merge with top-level permissions
1387 my $user_subperms = get_user_subpermissions($userid);
1388 foreach my $module (keys %$user_subperms) {
1389 next if $userflags->{$module} == 1; # user already has permission for everything in this module
1390 $userflags->{$module} = $user_subperms->{$module};
1393 return $userflags;
1396 =item get_user_subpermissions
1398 =over 4
1400 my $user_perm_hashref = get_user_subpermissions($userid);
1402 =back
1404 Given the userid (note, not the borrowernumber) of a staff user,
1405 return a hashref of hashrefs of the specific subpermissions
1406 accorded to the user. An example return is
1409 tools => {
1410 export_catalog => 1,
1411 import_patrons => 1,
1415 The top-level hash-key is a module or function code from
1416 userflags.flag, while the second-level key is a code
1417 from permissions.
1419 The results of this function do not give a complete picture
1420 of the functions that a staff user can access; it is also
1421 necessary to check borrowers.flags.
1423 =cut
1425 sub get_user_subpermissions {
1426 my $userid = shift;
1428 my $dbh = C4::Context->dbh;
1429 my $sth = $dbh->prepare("SELECT flag, user_permissions.code
1430 FROM user_permissions
1431 JOIN permissions USING (module_bit, code)
1432 JOIN userflags ON (module_bit = bit)
1433 JOIN borrowers USING (borrowernumber)
1434 WHERE userid = ?");
1435 $sth->execute($userid);
1437 my $user_perms = {};
1438 while (my $perm = $sth->fetchrow_hashref) {
1439 $user_perms->{$perm->{'flag'}}->{$perm->{'code'}} = 1;
1441 return $user_perms;
1444 =item get_all_subpermissions
1446 =over 4
1448 my $perm_hashref = get_all_subpermissions();
1450 =back
1452 Returns a hashref of hashrefs defining all specific
1453 permissions currently defined. The return value
1454 has the same structure as that of C<get_user_subpermissions>,
1455 except that the innermost hash value is the description
1456 of the subpermission.
1458 =cut
1460 sub get_all_subpermissions {
1461 my $dbh = C4::Context->dbh;
1462 my $sth = $dbh->prepare("SELECT flag, code, description
1463 FROM permissions
1464 JOIN userflags ON (module_bit = bit)");
1465 $sth->execute();
1467 my $all_perms = {};
1468 while (my $perm = $sth->fetchrow_hashref) {
1469 $all_perms->{$perm->{'flag'}}->{$perm->{'code'}} = $perm->{'description'};
1471 return $all_perms;
1474 =item haspermission
1476 $flags = ($userid, $flagsrequired);
1478 C<$userid> the userid of the member
1479 C<$flags> is a hashref of required flags like C<$borrower-&lt;{authflags}>
1481 Returns member's flags or 0 if a permission is not met.
1483 =cut
1485 sub haspermission {
1486 my ($userid, $flagsrequired) = @_;
1487 my $sth = C4::Context->dbh->prepare("SELECT flags FROM borrowers WHERE userid=?");
1488 $sth->execute($userid);
1489 my $flags = getuserflags( $sth->fetchrow(), $userid );
1490 if ( $userid eq C4::Context->config('user') ) {
1491 # Super User Account from /etc/koha.conf
1492 $flags->{'superlibrarian'} = 1;
1494 elsif ( $userid eq 'demo' && C4::Context->config('demo') ) {
1495 # Demo user that can do "anything" (demo=1 in /etc/koha.conf)
1496 $flags->{'superlibrarian'} = 1;
1498 return $flags if $flags->{superlibrarian};
1499 foreach my $module ( keys %$flagsrequired ) {
1500 if (C4::Context->preference('GranularPermissions')) {
1501 my $subperm = $flagsrequired->{$module};
1502 if ($subperm eq '*') {
1503 return 0 unless ( $flags->{$module} == 1 or ref($flags->{$module}) );
1504 } else {
1505 return 0 unless ( $flags->{$module} == 1 or
1506 ( ref($flags->{$module}) and
1507 exists $flags->{$module}->{$subperm} and
1508 $flags->{$module}->{$subperm} == 1
1512 } else {
1513 return 0 unless ( $flags->{$module} );
1516 return $flags;
1517 #FIXME - This fcn should return the failed permission so a suitable error msg can be delivered.
1521 sub getborrowernumber {
1522 my ($userid) = @_;
1523 my $userenv = C4::Context->userenv;
1524 if ( defined( $userenv ) && ref( $userenv ) eq 'HASH' && $userenv->{number} ) {
1525 return $userenv->{number};
1527 my $dbh = C4::Context->dbh;
1528 for my $field ( 'userid', 'cardnumber' ) {
1529 my $sth =
1530 $dbh->prepare("select borrowernumber from borrowers where $field=?");
1531 $sth->execute($userid);
1532 if ( $sth->rows ) {
1533 my ($bnumber) = $sth->fetchrow;
1534 return $bnumber;
1537 return 0;
1540 END { } # module clean-up code here (global destructor)
1542 __END__
1544 =back
1546 =head1 SEE ALSO
1548 CGI(3)
1550 C4::Output(3)
1552 Digest::MD5(3)
1554 =cut