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
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
17 # with Koha; if not, write to the Free Software Foundation, Inc.,
18 # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
21 #use warnings; FIXME - Bug 2505
22 use Digest::MD5 qw(md5_base64);
23 use Storable qw(thaw freeze);
29 use C4::Templates; # to get the template
32 use C4::Branch; # GetBranches
33 use C4::VirtualShelves;
34 use POSIX qw/strftime/;
35 use List::MoreUtils qw/ any /;
38 use vars qw($VERSION @ISA @EXPORT @EXPORT_OK %EXPORT_TAGS $debug $ldap $cas $caslogout $servers $memcached);
41 sub psgi_env { any { /^psgi\./ } keys %ENV }
43 if ( psgi_env ) { die 'psgi:exit' }
47 $VERSION = 3.02; # set version for version checking
50 @EXPORT = qw(&checkauth &get_template_and_user &haspermission &get_user_subpermissions);
51 @EXPORT_OK = qw(&check_api_auth &get_session &check_cookie_auth &checkpw &get_all_subpermissions &get_user_subpermissions);
52 %EXPORT_TAGS = ( EditPermissions => [qw(get_all_subpermissions get_user_subpermissions)] );
53 $ldap = C4::Context->config('useldapserver') || 0;
54 $cas = C4::Context->preference('casAuthentication');
55 $caslogout = C4::Context->preference('casLogout');
56 require C4::Auth_with_cas; # no import
58 require C4::Auth_with_ldap;
59 import C4::Auth_with_ldap qw(checkpw_ldap);
62 import C4::Auth_with_cas qw(check_api_auth_cas checkpw_cas login_cas logout_cas login_cas_url);
64 $servers = C4::Context->config('memcached_servers');
66 require Cache::Memcached;
67 $memcached = Cache::Memcached->new({
68 servers => [ $servers ],
70 compress_threshold => 10_000,
71 namespace => C4::Context->config('memcached_namespace') || 'koha',
78 C4::Auth - Authenticates Koha users
88 my ($template, $borrowernumber, $cookie)
89 = get_template_and_user(
91 template_name => "opac-main.tmpl",
95 flagsrequired => {borrow => 1, catalogue => '*', tools => 'import_patrons' },
99 output_html_with_http_headers $query, $cookie, $template->output;
103 The main function of this module is to provide
104 authentification. However the get_template_and_user function has
105 been provided so that a users login information is passed along
106 automatically. This gets loaded into the template.
110 =head2 get_template_and_user
112 my ($template, $borrowernumber, $cookie)
113 = get_template_and_user(
115 template_name => "opac-main.tmpl",
118 authnotrequired => 1,
119 flagsrequired => {borrow => 1, catalogue => '*', tools => 'import_patrons' },
123 This call passes the C<query>, C<flagsrequired> and C<authnotrequired>
124 to C<&checkauth> (in this module) to perform authentification.
125 See C<&checkauth> for an explanation of these parameters.
127 The C<template_name> is then used to find the correct template for
128 the page. The authenticated users details are loaded onto the
129 template in the HTML::Template LOOP variable C<USER_INFO>. Also the
130 C<sessionID> is passed to the template. This can be used in templates
131 if cookies are disabled. It needs to be put as and input to every
134 More information on the C<gettemplate> sub can be found in the
139 my $SEARCH_HISTORY_INSERT_SQL =<<EOQ;
140 INSERT INTO search_history(userid, sessionid, query_desc, query_cgi, total, time )
141 VALUES ( ?, ?, ?, ?, ?, FROM_UNIXTIME(?))
143 sub get_template_and_user {
146 C4::Templates::gettemplate( $in->{'template_name'}, $in->{'type'}, $in->{'query'} );
147 my ( $user, $cookie, $sessionID, $flags );
148 if ( $in->{'template_name'} !~m/maintenance/ ) {
149 ( $user, $cookie, $sessionID, $flags ) = checkauth(
151 $in->{'authnotrequired'},
152 $in->{'flagsrequired'},
158 my $insecure = C4::Context->preference('insecure');
159 if ($user or $insecure) {
161 # load the template variables for stylesheets and JavaScript
162 $template->param( css_libs => $in->{'css_libs'} );
163 $template->param( css_module => $in->{'css_module'} );
164 $template->param( css_page => $in->{'css_page'} );
165 $template->param( css_widgets => $in->{'css_widgets'} );
167 $template->param( js_libs => $in->{'js_libs'} );
168 $template->param( js_module => $in->{'js_module'} );
169 $template->param( js_page => $in->{'js_page'} );
170 $template->param( js_widgets => $in->{'js_widgets'} );
173 $template->param( loggedinusername => $user );
174 $template->param( sessionID => $sessionID );
176 my ($total, $pubshelves, $barshelves) = C4::Context->get_shelves_userenv();
177 if (defined($pubshelves)) {
178 $template->param( pubshelves => scalar @{$pubshelves},
179 pubshelvesloop => $pubshelves,
181 $template->param( pubtotal => $total->{'pubtotal'}, ) if ($total->{'pubtotal'} > scalar @{$pubshelves});
183 if (defined($barshelves)) {
184 $template->param( barshelves => scalar @{$barshelves},
185 barshelvesloop => $barshelves,
187 $template->param( bartotal => $total->{'bartotal'}, ) if ($total->{'bartotal'} > scalar @{$barshelves});
190 $borrowernumber = getborrowernumber($user) if defined($user);
192 my ( $borr ) = GetMemberDetails( $borrowernumber );
195 $template->param( "USER_INFO" => \@bordat );
197 my $all_perms = get_all_subpermissions();
199 my @flagroots = qw(circulate catalogue parameters borrowers permissions reserveforothers borrow
200 editcatalogue updatecharges management tools editauthorities serials reports acquisition);
201 # We are going to use the $flags returned by checkauth
202 # to create the template's parameters that will indicate
203 # which menus the user can access.
204 if (( $flags && $flags->{superlibrarian}==1) or $insecure==1) {
205 $template->param( CAN_user_circulate => 1 );
206 $template->param( CAN_user_catalogue => 1 );
207 $template->param( CAN_user_parameters => 1 );
208 $template->param( CAN_user_borrowers => 1 );
209 $template->param( CAN_user_permissions => 1 );
210 $template->param( CAN_user_reserveforothers => 1 );
211 $template->param( CAN_user_borrow => 1 );
212 $template->param( CAN_user_editcatalogue => 1 );
213 $template->param( CAN_user_updatecharges => 1 );
214 $template->param( CAN_user_acquisition => 1 );
215 $template->param( CAN_user_management => 1 );
216 $template->param( CAN_user_tools => 1 );
217 $template->param( CAN_user_editauthorities => 1 );
218 $template->param( CAN_user_serials => 1 );
219 $template->param( CAN_user_reports => 1 );
220 $template->param( CAN_user_staffaccess => 1 );
221 foreach my $module (keys %$all_perms) {
222 foreach my $subperm (keys %{ $all_perms->{$module} }) {
223 $template->param( "CAN_user_${module}_${subperm}" => 1 );
229 foreach my $module (keys %$all_perms) {
230 if ( $flags->{$module} == 1) {
231 foreach my $subperm (keys %{ $all_perms->{$module} }) {
232 $template->param( "CAN_user_${module}_${subperm}" => 1 );
234 } elsif ( ref($flags->{$module}) ) {
235 foreach my $subperm (keys %{ $flags->{$module} } ) {
236 $template->param( "CAN_user_${module}_${subperm}" => 1 );
243 foreach my $module (keys %$flags) {
244 if ( $flags->{$module} == 1 or ref($flags->{$module}) ) {
245 $template->param( "CAN_user_$module" => 1 );
246 if ($module eq "parameters") {
247 $template->param( CAN_user_management => 1 );
252 # Logged-in opac search history
253 # If the requested template is an opac one and opac search history is enabled
254 if ($in->{type} eq 'opac' && C4::Context->preference('EnableOpacSearchHistory')) {
255 my $dbh = C4::Context->dbh;
256 my $query = "SELECT COUNT(*) FROM search_history WHERE userid=?";
257 my $sth = $dbh->prepare($query);
258 $sth->execute($borrowernumber);
260 # If at least one search has already been performed
261 if ($sth->fetchrow_array > 0) {
262 # We show the link in opac
263 $template->param(ShowOpacRecentSearchLink => 1);
266 # And if there's a cookie with searches performed when the user was not logged in,
267 # we add them to the logged-in search history
268 my $searchcookie = $in->{'query'}->cookie('KohaOpacRecentSearches');
270 $searchcookie = uri_unescape($searchcookie);
271 my @recentSearches = @{thaw($searchcookie) || []};
272 if (@recentSearches) {
273 my $sth = $dbh->prepare($SEARCH_HISTORY_INSERT_SQL);
274 $sth->execute( $borrowernumber,
275 $in->{'query'}->cookie("CGISESSID"),
280 ) foreach @recentSearches;
282 # And then, delete the cookie's content
283 my $newsearchcookie = $in->{'query'}->cookie(
284 -name => 'KohaOpacRecentSearches',
285 -value => freeze([]),
288 $cookie = [$cookie, $newsearchcookie];
293 else { # if this is an anonymous session, setup to display public lists...
295 # load the template variables for stylesheets and JavaScript
296 $template->param( css_libs => $in->{'css_libs'} );
297 $template->param( css_module => $in->{'css_module'} );
298 $template->param( css_page => $in->{'css_page'} );
299 $template->param( css_widgets => $in->{'css_widgets'} );
301 $template->param( js_libs => $in->{'js_libs'} );
302 $template->param( js_module => $in->{'js_module'} );
303 $template->param( js_page => $in->{'js_page'} );
304 $template->param( js_widgets => $in->{'js_widgets'} );
306 $template->param( sessionID => $sessionID );
308 my ($total, $pubshelves) = C4::Context->get_shelves_userenv(); # an anonymous user has no 'barshelves'...
309 if (defined $pubshelves) {
310 $template->param( pubshelves => scalar @{$pubshelves},
311 pubshelvesloop => $pubshelves,
313 $template->param( pubtotal => $total->{'pubtotal'}, ) if ($total->{'pubtotal'} > scalar @{$pubshelves});
317 # Anonymous opac search history
318 # If opac search history is enabled and at least one search has already been performed
319 if (C4::Context->preference('EnableOpacSearchHistory')) {
320 my $searchcookie = $in->{'query'}->cookie('KohaOpacRecentSearches');
322 $searchcookie = uri_unescape($searchcookie);
323 my @recentSearches = @{thaw($searchcookie) || []};
324 # We show the link in opac
325 if (@recentSearches) {
326 $template->param(ShowOpacRecentSearchLink => 1);
331 if(C4::Context->preference('dateformat')){
332 if(C4::Context->preference('dateformat') eq "metric"){
333 $template->param(dateformat_metric => 1);
334 } elsif(C4::Context->preference('dateformat') eq "us"){
335 $template->param(dateformat_us => 1);
337 $template->param(dateformat_iso => 1);
340 $template->param(dateformat_iso => 1);
343 # these template parameters are set the same regardless of $in->{'type'}
345 "BiblioDefaultView".C4::Context->preference("BiblioDefaultView") => 1,
346 EnhancedMessagingPreferences => C4::Context->preference('EnhancedMessagingPreferences'),
347 GoogleJackets => C4::Context->preference("GoogleJackets"),
348 OpenLibraryCovers => C4::Context->preference("OpenLibraryCovers"),
349 KohaAdminEmailAddress => "" . C4::Context->preference("KohaAdminEmailAddress"),
350 LoginBranchcode => (C4::Context->userenv?C4::Context->userenv->{"branch"}:"insecure"),
351 LoginFirstname => (C4::Context->userenv?C4::Context->userenv->{"firstname"}:"Bel"),
352 LoginSurname => C4::Context->userenv?C4::Context->userenv->{"surname"}:"Inconnu",
353 TagsEnabled => C4::Context->preference("TagsEnabled"),
354 hide_marc => C4::Context->preference("hide_marc"),
355 item_level_itypes => C4::Context->preference('item-level_itypes'),
356 patronimages => C4::Context->preference("patronimages"),
357 singleBranchMode => C4::Context->preference("singleBranchMode"),
358 XSLTDetailsDisplay => C4::Context->preference("XSLTDetailsDisplay"),
359 XSLTResultsDisplay => C4::Context->preference("XSLTResultsDisplay"),
360 using_https => $in->{'query'}->https() ? 1 : 0,
361 noItemTypeImages => C4::Context->preference("noItemTypeImages"),
364 if ( $in->{'type'} eq "intranet" ) {
366 AmazonContent => C4::Context->preference("AmazonContent"),
367 AmazonCoverImages => C4::Context->preference("AmazonCoverImages"),
368 AmazonEnabled => C4::Context->preference("AmazonEnabled"),
369 AmazonSimilarItems => C4::Context->preference("AmazonSimilarItems"),
370 AutoLocation => C4::Context->preference("AutoLocation"),
371 "BiblioDefaultView".C4::Context->preference("IntranetBiblioDefaultView") => 1,
372 CircAutocompl => C4::Context->preference("CircAutocompl"),
373 FRBRizeEditions => C4::Context->preference("FRBRizeEditions"),
374 IndependantBranches => C4::Context->preference("IndependantBranches"),
375 IntranetNav => C4::Context->preference("IntranetNav"),
376 IntranetmainUserblock => C4::Context->preference("IntranetmainUserblock"),
377 LibraryName => C4::Context->preference("LibraryName"),
378 LoginBranchname => (C4::Context->userenv?C4::Context->userenv->{"branchname"}:"insecure"),
379 advancedMARCEditor => C4::Context->preference("advancedMARCEditor"),
380 canreservefromotherbranches => C4::Context->preference('canreservefromotherbranches'),
381 intranetcolorstylesheet => C4::Context->preference("intranetcolorstylesheet"),
382 IntranetFavicon => C4::Context->preference("IntranetFavicon"),
383 intranetreadinghistory => C4::Context->preference("intranetreadinghistory"),
384 intranetstylesheet => C4::Context->preference("intranetstylesheet"),
385 IntranetUserCSS => C4::Context->preference("IntranetUserCSS"),
386 intranetuserjs => C4::Context->preference("intranetuserjs"),
387 intranetbookbag => C4::Context->preference("intranetbookbag"),
388 suggestion => C4::Context->preference("suggestion"),
389 virtualshelves => C4::Context->preference("virtualshelves"),
390 StaffSerialIssueDisplayCount => C4::Context->preference("StaffSerialIssueDisplayCount"),
391 NoZebra => C4::Context->preference('NoZebra'),
392 EasyAnalyticalRecords => C4::Context->preference('EasyAnalyticalRecords'),
396 warn "template type should be OPAC, here it is=[" . $in->{'type'} . "]" unless ( $in->{'type'} eq 'opac' );
397 #TODO : replace LibraryName syspref with 'system name', and remove this html processing
398 my $LibraryNameTitle = C4::Context->preference("LibraryName");
399 $LibraryNameTitle =~ s/<(?:\/?)(?:br|p)\s*(?:\/?)>/ /sgi;
400 $LibraryNameTitle =~ s/<(?:[^<>'"]|'(?:[^']*)'|"(?:[^"]*)")*>//sg;
401 # variables passed from CGI: opac_css_override and opac_search_limits.
402 my $opac_search_limit = $ENV{'OPAC_SEARCH_LIMIT'};
403 my $opac_limit_override = $ENV{'OPAC_LIMIT_OVERRIDE'};
405 if (($opac_search_limit =~ /branch:(\w+)/ && $opac_limit_override) || $in->{'query'}->param('limit') =~ /branch:(\w+)/){
406 $opac_name = $1; # opac_search_limit is a branch, so we use it.
407 } elsif (C4::Context->preference("SearchMyLibraryFirst") && C4::Context->userenv && C4::Context->userenv->{'branch'}) {
408 $opac_name = C4::Context->userenv->{'branch'};
410 my $checkstyle = C4::Context->preference("opaccolorstylesheet");
411 if ($checkstyle =~ /http/)
413 $template->param( opacexternalsheet => $checkstyle);
416 my $opaccolorstylesheet = C4::Context->preference("opaccolorstylesheet");
417 $template->param( opaccolorstylesheet => $opaccolorstylesheet);
420 AmazonContent => "" . C4::Context->preference("AmazonContent"),
421 AnonSuggestions => "" . C4::Context->preference("AnonSuggestions"),
422 AuthorisedValueImages => C4::Context->preference("AuthorisedValueImages"),
423 BranchesLoop => GetBranchesLoop($opac_name),
424 LibraryName => "" . C4::Context->preference("LibraryName"),
425 LibraryNameTitle => "" . $LibraryNameTitle,
426 LoginBranchname => C4::Context->userenv?C4::Context->userenv->{"branchname"}:"",
427 OPACAmazonEnabled => C4::Context->preference("OPACAmazonEnabled"),
428 OPACAmazonSimilarItems => C4::Context->preference("OPACAmazonSimilarItems"),
429 OPACAmazonCoverImages => C4::Context->preference("OPACAmazonCoverImages"),
430 OPACAmazonReviews => C4::Context->preference("OPACAmazonReviews"),
431 OPACFRBRizeEditions => C4::Context->preference("OPACFRBRizeEditions"),
432 OpacHighlightedWords => C4::Context->preference("OpacHighlightedWords"),
433 OPACItemHolds => C4::Context->preference("OPACItemHolds"),
434 OPACShelfBrowser => "". C4::Context->preference("OPACShelfBrowser"),
435 OpacShowRecentComments => C4::Context->preference("OpacShowRecentComments"),
436 OPACURLOpenInNewWindow => "" . C4::Context->preference("OPACURLOpenInNewWindow"),
437 OPACUserCSS => "". C4::Context->preference("OPACUserCSS"),
438 OPACViewOthersSuggestions => "" . C4::Context->preference("OPACViewOthersSuggestions"),
439 OpacAuthorities => C4::Context->preference("OpacAuthorities"),
440 OPACBaseURL => ($in->{'query'}->https() ? "https://" : "http://") . $ENV{'SERVER_NAME'} .
441 ($ENV{'SERVER_PORT'} eq ($in->{'query'}->https() ? "443" : "80") ? '' : ":$ENV{'SERVER_PORT'}"),
442 opac_css_override => $ENV{'OPAC_CSS_OVERRIDE'},
443 opac_search_limit => $opac_search_limit,
444 opac_limit_override => $opac_limit_override,
445 OpacBrowser => C4::Context->preference("OpacBrowser"),
446 OpacCloud => C4::Context->preference("OpacCloud"),
447 OpacKohaUrl => C4::Context->preference("OpacKohaUrl"),
448 OpacMainUserBlock => "" . C4::Context->preference("OpacMainUserBlock"),
449 OpacNav => "" . C4::Context->preference("OpacNav"),
450 OpacNavBottom => "" . C4::Context->preference("OpacNavBottom"),
451 OpacPasswordChange => C4::Context->preference("OpacPasswordChange"),
452 OPACPatronDetails => C4::Context->preference("OPACPatronDetails"),
453 OPACPrivacy => C4::Context->preference("OPACPrivacy"),
454 OPACFinesTab => C4::Context->preference("OPACFinesTab"),
455 OpacTopissue => C4::Context->preference("OpacTopissue"),
456 RequestOnOpac => C4::Context->preference("RequestOnOpac"),
457 'Version' => C4::Context->preference('Version'),
458 hidelostitems => C4::Context->preference("hidelostitems"),
459 mylibraryfirst => (C4::Context->preference("SearchMyLibraryFirst") && C4::Context->userenv) ? C4::Context->userenv->{'branch'} : '',
460 opaclayoutstylesheet => "" . C4::Context->preference("opaclayoutstylesheet"),
461 opacstylesheet => "" . C4::Context->preference("opacstylesheet"),
462 opacbookbag => "" . C4::Context->preference("opacbookbag"),
463 opaccredits => "" . C4::Context->preference("opaccredits"),
464 OpacFavicon => C4::Context->preference("OpacFavicon"),
465 opacheader => "" . C4::Context->preference("opacheader"),
466 opaclanguagesdisplay => "" . C4::Context->preference("opaclanguagesdisplay"),
467 opacreadinghistory => C4::Context->preference("opacreadinghistory"),
468 opacsmallimage => "" . C4::Context->preference("opacsmallimage"),
469 opacuserjs => C4::Context->preference("opacuserjs"),
470 opacuserlogin => "" . C4::Context->preference("opacuserlogin"),
471 reviewson => C4::Context->preference("reviewson"),
472 ShowReviewer => C4::Context->preference("ShowReviewer"),
473 ShowReviewerPhoto => C4::Context->preference("ShowReviewerPhoto"),
474 suggestion => "" . C4::Context->preference("suggestion"),
475 virtualshelves => "" . C4::Context->preference("virtualshelves"),
476 OPACSerialIssueDisplayCount => C4::Context->preference("OPACSerialIssueDisplayCount"),
477 OpacAddMastheadLibraryPulldown => C4::Context->preference("OpacAddMastheadLibraryPulldown"),
478 OPACXSLTDetailsDisplay => C4::Context->preference("OPACXSLTDetailsDisplay"),
479 OPACXSLTResultsDisplay => C4::Context->preference("OPACXSLTResultsDisplay"),
480 SyndeticsClientCode => C4::Context->preference("SyndeticsClientCode"),
481 SyndeticsEnabled => C4::Context->preference("SyndeticsEnabled"),
482 SyndeticsCoverImages => C4::Context->preference("SyndeticsCoverImages"),
483 SyndeticsTOC => C4::Context->preference("SyndeticsTOC"),
484 SyndeticsSummary => C4::Context->preference("SyndeticsSummary"),
485 SyndeticsEditions => C4::Context->preference("SyndeticsEditions"),
486 SyndeticsExcerpt => C4::Context->preference("SyndeticsExcerpt"),
487 SyndeticsReviews => C4::Context->preference("SyndeticsReviews"),
488 SyndeticsAuthorNotes => C4::Context->preference("SyndeticsAuthorNotes"),
489 SyndeticsAwards => C4::Context->preference("SyndeticsAwards"),
490 SyndeticsSeries => C4::Context->preference("SyndeticsSeries"),
491 SyndeticsCoverImageSize => C4::Context->preference("SyndeticsCoverImageSize"),
494 $template->param(OpacPublic => '1') if ($user || C4::Context->preference("OpacPublic"));
496 $template->param(listloop=>[{shelfname=>"Freelist", shelfnumber=>110}]);
497 return ( $template, $borrowernumber, $cookie, $flags);
502 ($userid, $cookie, $sessionID) = &checkauth($query, $noauth, $flagsrequired, $type);
504 Verifies that the user is authorized to run this script. If
505 the user is authorized, a (userid, cookie, session-id, flags)
506 quadruple is returned. If the user is not authorized but does
507 not have the required privilege (see $flagsrequired below), it
508 displays an error page and exits. Otherwise, it displays the
509 login page and exits.
511 Note that C<&checkauth> will return if and only if the user
512 is authorized, so it should be called early on, before any
513 unfinished operations (e.g., if you've opened a file, then
514 C<&checkauth> won't close it for you).
516 C<$query> is the CGI object for the script calling C<&checkauth>.
518 The C<$noauth> argument is optional. If it is set, then no
519 authorization is required for the script.
521 C<&checkauth> fetches user and session information from C<$query> and
522 ensures that the user is authorized to run scripts that require
525 The C<$flagsrequired> argument specifies the required privileges
526 the user must have if the username and password are correct.
527 It should be specified as a reference-to-hash; keys in the hash
528 should be the "flags" for the user, as specified in the Members
529 intranet module. Any key specified must correspond to a "flag"
530 in the userflags table. E.g., { circulate => 1 } would specify
531 that the user must have the "circulate" privilege in order to
532 proceed. To make sure that access control is correct, the
533 C<$flagsrequired> parameter must be specified correctly.
535 Koha also has a concept of sub-permissions, also known as
536 granular permissions. This makes the value of each key
537 in the C<flagsrequired> hash take on an additional
542 The user must have access to all subfunctions of the module
543 specified by the hash key.
547 The user must have access to at least one subfunction of the module
548 specified by the hash key.
550 specific permission, e.g., 'export_catalog'
552 The user must have access to the specific subfunction list, which
553 must correspond to a row in the permissions table.
555 The C<$type> argument specifies whether the template should be
556 retrieved from the opac or intranet directory tree. "opac" is
557 assumed if it is not specified; however, if C<$type> is specified,
558 "intranet" is assumed if it is not "opac".
560 If C<$query> does not have a valid session ID associated with it
561 (i.e., the user has not logged in) or if the session has expired,
562 C<&checkauth> presents the user with a login page (from the point of
563 view of the original script, C<&checkauth> does not return). Once the
564 user has authenticated, C<&checkauth> restarts the original script
565 (this time, C<&checkauth> returns).
567 The login page is provided using a HTML::Template, which is set in the
568 systempreferences table or at the top of this file. The variable C<$type>
569 selects which template to use, either the opac or the intranet
570 authentification template.
572 C<&checkauth> returns a user ID, a cookie, and a session ID. The
573 cookie should be sent back to the browser; it verifies that the user
578 sub _version_check ($$) {
582 # If Version syspref is unavailable, it means Koha is beeing installed,
583 # and so we must redirect to OPAC maintenance page or to the WebInstaller
584 # also, if OpacMaintenance is ON, OPAC should redirect to maintenance
585 if (C4::Context->preference('OpacMaintenance') && $type eq 'opac') {
586 warn "OPAC Install required, redirecting to maintenance";
587 print $query->redirect("/cgi-bin/koha/maintenance.pl");
589 unless ( $version = C4::Context->preference('Version') ) { # assignment, not comparison
590 if ( $type ne 'opac' ) {
591 warn "Install required, redirecting to Installer";
592 print $query->redirect("/cgi-bin/koha/installer/install.pl");
594 warn "OPAC Install required, redirecting to maintenance";
595 print $query->redirect("/cgi-bin/koha/maintenance.pl");
600 # check that database and koha version are the same
601 # there is no DB version, it's a fresh install,
602 # go to web installer
603 # there is a DB version, compare it to the code version
604 my $kohaversion=C4::Context::KOHAVERSION;
605 # remove the 3 last . to have a Perl number
606 $kohaversion =~ s/(.*\..*)\.(.*)\.(.*)/$1$2$3/;
607 $debug and print STDERR "kohaversion : $kohaversion\n";
608 if ($version < $kohaversion){
609 my $warning = "Database update needed, redirecting to %s. Database is $version and Koha is $kohaversion";
610 if ($type ne 'opac'){
611 warn sprintf($warning, 'Installer');
612 print $query->redirect("/cgi-bin/koha/installer/install.pl?step=3");
614 warn sprintf("OPAC: " . $warning, 'maintenance');
615 print $query->redirect("/cgi-bin/koha/maintenance.pl");
623 open L, ">>/tmp/sessionlog" or warn "ERROR: Cannot append to /tmp/sessionlog";
624 printf L join("\n",@_);
630 $debug and warn "Checking Auth";
631 # $authnotrequired will be set for scripts which will run without authentication
632 my $authnotrequired = shift;
633 my $flagsrequired = shift;
635 $type = 'opac' unless $type;
637 my $dbh = C4::Context->dbh;
638 my $timeout = C4::Context->preference('timeout');
640 if ($timeout =~ /(\d+)[dD]/) {
641 $timeout = $1 * 86400;
643 $timeout = 600 unless $timeout;
645 _version_check($type,$query);
649 my ( $userid, $cookie, $sessionID, $flags, $barshelves, $pubshelves );
650 my $logout = $query->param('logout.x');
652 # This parameter is the name of the CAS server we want to authenticate against,
653 # when using authentication against multiple CAS servers, as configured in Auth_cas_servers.yaml
654 my $casparam = $query->param('cas');
656 if ( $userid = $ENV{'REMOTE_USER'} ) {
657 # Using Basic Authentication, no cookies required
658 $cookie = $query->cookie(
659 -name => 'CGISESSID',
665 elsif ( $sessionID = $query->cookie("CGISESSID")) { # assignment, not comparison
666 my $session = get_session($sessionID);
667 C4::Context->_new_userenv($sessionID);
668 my ($ip, $lasttime, $sessiontype);
670 C4::Context::set_userenv(
671 $session->param('number'), $session->param('id'),
672 $session->param('cardnumber'), $session->param('firstname'),
673 $session->param('surname'), $session->param('branch'),
674 $session->param('branchname'), $session->param('flags'),
675 $session->param('emailaddress'), $session->param('branchprinter')
677 C4::Context::set_shelves_userenv('bar',$session->param('barshelves'));
678 C4::Context::set_shelves_userenv('pub',$session->param('pubshelves'));
679 C4::Context::set_shelves_userenv('tot',$session->param('totshelves'));
680 $debug and printf STDERR "AUTH_SESSION: (%s)\t%s %s - %s\n", map {$session->param($_)} qw(cardnumber firstname surname branch) ;
681 $ip = $session->param('ip');
682 $lasttime = $session->param('lasttime');
683 $userid = $session->param('id');
684 $sessiontype = $session->param('sessiontype');
686 if ( ($query->param('koha_login_context')) && ($query->param('userid') ne $session->param('id')) ) {
687 #if a user enters an id ne to the id in the current session, we need to log them in...
688 #first we need to clear the anonymous session...
689 $debug and warn "query id = " . $query->param('userid') . " but session id = " . $session->param('id');
692 C4::Context->_unset_userenv($sessionID);
697 # voluntary logout the user
700 C4::Context->_unset_userenv($sessionID);
701 _session_log(sprintf "%20s from %16s logged out at %30s (manually).\n", $userid,$ip,(strftime "%c",localtime));
705 if ($cas and $caslogout) {
709 elsif ( $lasttime < time() - $timeout ) {
711 $info{'timed_out'} = 1;
713 C4::Context->_unset_userenv($sessionID);
714 _session_log(sprintf "%20s from %16s logged out at %30s (inactivity).\n", $userid,$ip,(strftime "%c",localtime));
718 elsif ( $ip ne $ENV{'REMOTE_ADDR'} ) {
719 # Different ip than originally logged in from
720 $info{'oldip'} = $ip;
721 $info{'newip'} = $ENV{'REMOTE_ADDR'};
722 $info{'different_ip'} = 1;
724 C4::Context->_unset_userenv($sessionID);
725 _session_log(sprintf "%20s from %16s logged out at %30s (ip changed to %16s).\n", $userid,$ip,(strftime "%c",localtime), $info{'newip'});
730 $cookie = $query->cookie( CGISESSID => $session->id );
731 $session->param('lasttime',time());
732 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...
733 $flags = haspermission($userid, $flagsrequired);
737 $info{'nopermission'} = 1;
742 unless ($userid || $sessionID) {
743 #we initiate a session prior to checking for a username to allow for anonymous sessions...
744 my $session = get_session("") or die "Auth ERROR: Cannot get_session()";
745 my $sessionID = $session->id;
746 C4::Context->_new_userenv($sessionID);
747 $cookie = $query->cookie(CGISESSID => $sessionID);
748 $userid = $query->param('userid');
749 if ($cas || $userid) {
750 my $password = $query->param('password');
751 my ($return, $cardnumber);
752 if ($cas && $query->param('ticket')) {
754 ( $return, $cardnumber, $retuserid ) = checkpw( $dbh, $userid, $password, $query );
755 $userid = $retuserid;
756 $info{'invalidCasLogin'} = 1 unless ($return);
759 ( $return, $cardnumber, $retuserid ) = checkpw( $dbh, $userid, $password, $query );
760 $userid = $retuserid if ($retuserid ne '');
763 _session_log(sprintf "%20s from %16s logged in at %30s.\n", $userid,$ENV{'REMOTE_ADDR'},(strftime '%c', localtime));
764 if ( $flags = haspermission( $userid, $flagsrequired ) ) {
768 $info{'nopermission'} = 1;
769 C4::Context->_unset_userenv($sessionID);
772 my ($borrowernumber, $firstname, $surname, $userflags,
773 $branchcode, $branchname, $branchprinter, $emailaddress);
775 if ( $return == 1 ) {
777 SELECT borrowernumber, firstname, surname, flags, borrowers.branchcode,
778 branches.branchname as branchname,
779 branches.branchprinter as branchprinter,
782 LEFT JOIN branches on borrowers.branchcode=branches.branchcode
784 my $sth = $dbh->prepare("$select where userid=?");
785 $sth->execute($userid);
786 unless ($sth->rows) {
787 $debug and print STDERR "AUTH_1: no rows for userid='$userid'\n";
788 $sth = $dbh->prepare("$select where cardnumber=?");
789 $sth->execute($cardnumber);
791 unless ($sth->rows) {
792 $debug and print STDERR "AUTH_2a: no rows for cardnumber='$cardnumber'\n";
793 $sth->execute($userid);
794 unless ($sth->rows) {
795 $debug and print STDERR "AUTH_2b: no rows for userid='$userid' AS cardnumber\n";
800 ($borrowernumber, $firstname, $surname, $userflags,
801 $branchcode, $branchname, $branchprinter, $emailaddress) = $sth->fetchrow;
802 $debug and print STDERR "AUTH_3 results: " .
803 "$cardnumber,$borrowernumber,$userid,$firstname,$surname,$userflags,$branchcode,$emailaddress\n";
805 print STDERR "AUTH_3: no results for userid='$userid', cardnumber='$cardnumber'.\n";
808 # launch a sequence to check if we have a ip for the branch, i
809 # if we have one we replace the branchcode of the userenv by the branch bound in the ip.
811 my $ip = $ENV{'REMOTE_ADDR'};
812 # if they specify at login, use that
813 if ($query->param('branch')) {
814 $branchcode = $query->param('branch');
815 $branchname = GetBranchName($branchcode);
817 my $branches = GetBranches();
818 if (C4::Context->boolean_preference('IndependantBranches') && C4::Context->boolean_preference('Autolocation')){
819 # we have to check they are coming from the right ip range
820 my $domain = $branches->{$branchcode}->{'branchip'};
821 if ($ip !~ /^$domain/){
823 $info{'wrongip'} = 1;
828 foreach my $br ( keys %$branches ) {
829 # now we work with the treatment of ip
830 my $domain = $branches->{$br}->{'branchip'};
831 if ( $domain && $ip =~ /^$domain/ ) {
832 $branchcode = $branches->{$br}->{'branchcode'};
834 # new op dev : add the branchprinter and branchname in the cookie
835 $branchprinter = $branches->{$br}->{'branchprinter'};
836 $branchname = $branches->{$br}->{'branchname'};
839 $session->param('number',$borrowernumber);
840 $session->param('id',$userid);
841 $session->param('cardnumber',$cardnumber);
842 $session->param('firstname',$firstname);
843 $session->param('surname',$surname);
844 $session->param('branch',$branchcode);
845 $session->param('branchname',$branchname);
846 $session->param('flags',$userflags);
847 $session->param('emailaddress',$emailaddress);
848 $session->param('ip',$session->remote_addr());
849 $session->param('lasttime',time());
850 $debug and printf STDERR "AUTH_4: (%s)\t%s %s - %s\n", map {$session->param($_)} qw(cardnumber firstname surname branch) ;
852 elsif ( $return == 2 ) {
853 #We suppose the user is the superlibrarian
855 $session->param('number',0);
856 $session->param('id',C4::Context->config('user'));
857 $session->param('cardnumber',C4::Context->config('user'));
858 $session->param('firstname',C4::Context->config('user'));
859 $session->param('surname',C4::Context->config('user'));
860 $session->param('branch','NO_LIBRARY_SET');
861 $session->param('branchname','NO_LIBRARY_SET');
862 $session->param('flags',1);
863 $session->param('emailaddress', C4::Context->preference('KohaAdminEmailAddress'));
864 $session->param('ip',$session->remote_addr());
865 $session->param('lasttime',time());
867 C4::Context::set_userenv(
868 $session->param('number'), $session->param('id'),
869 $session->param('cardnumber'), $session->param('firstname'),
870 $session->param('surname'), $session->param('branch'),
871 $session->param('branchname'), $session->param('flags'),
872 $session->param('emailaddress'), $session->param('branchprinter')
875 # Grab borrower's shelves and public shelves and add them to the session
876 # $row_count determines how many records are returned from the db query
877 # and the number of lists to be displayed of each type in the 'Lists' button drop down
878 my $row_count = 10; # FIXME:This probably should be a syspref
879 my ($total, $totshelves, $barshelves, $pubshelves);
880 ($barshelves, $totshelves) = C4::VirtualShelves::GetRecentShelves(1, $row_count, $borrowernumber);
881 $total->{'bartotal'} = $totshelves;
882 ($pubshelves, $totshelves) = C4::VirtualShelves::GetRecentShelves(2, $row_count, undef);
883 $total->{'pubtotal'} = $totshelves;
884 $session->param('barshelves', $barshelves);
885 $session->param('pubshelves', $pubshelves);
886 $session->param('totshelves', $total);
888 C4::Context::set_shelves_userenv('bar',$barshelves);
889 C4::Context::set_shelves_userenv('pub',$pubshelves);
890 C4::Context::set_shelves_userenv('tot',$total);
894 $info{'invalid_username_or_password'} = 1;
895 C4::Context->_unset_userenv($sessionID);
898 } # END if ( $userid = $query->param('userid') )
899 elsif ($type eq "opac") {
900 # if we are here this is an anonymous session; add public lists to it and a few other items...
901 # anonymous sessions are created only for the OPAC
902 $debug and warn "Initiating an anonymous session...";
904 # Grab the public shelves and add to the session...
905 my $row_count = 20; # FIXME:This probably should be a syspref
906 my ($total, $totshelves, $pubshelves);
907 ($pubshelves, $totshelves) = C4::VirtualShelves::GetRecentShelves(2, $row_count, undef);
908 $total->{'pubtotal'} = $totshelves;
909 $session->param('pubshelves', $pubshelves);
910 $session->param('totshelves', $total);
911 C4::Context::set_shelves_userenv('pub',$pubshelves);
912 C4::Context::set_shelves_userenv('tot',$total);
914 # setting a couple of other session vars...
915 $session->param('ip',$session->remote_addr());
916 $session->param('lasttime',time());
917 $session->param('sessiontype','anon');
919 } # END unless ($userid)
920 my $insecure = C4::Context->boolean_preference('insecure');
922 # finished authentification, now respond
923 if ( $loggedin || $authnotrequired || ( defined($insecure) && $insecure ) )
927 $cookie = $query->cookie( CGISESSID => '' );
929 return ( $userid, $cookie, $sessionID, $flags );
934 # AUTH rejected, show the login/password template, after checking the DB.
938 # get the inputs from the incoming query
940 foreach my $name ( param $query) {
941 (next) if ( $name eq 'userid' || $name eq 'password' || $name eq 'ticket' );
942 my $value = $query->param($name);
943 push @inputs, { name => $name, value => $value };
945 # get the branchloop, which we need for authentication
946 my $branches = GetBranches();
948 for my $branch_hash (sort keys %$branches) {
949 push @branch_loop, {branchcode => "$branch_hash", branchname => $branches->{$branch_hash}->{'branchname'}, };
952 my $template_name = ( $type eq 'opac' ) ? 'opac-auth.tmpl' : 'auth.tmpl';
953 my $template = C4::Templates::gettemplate( $template_name, $type, $query );
954 $template->param(branchloop => \@branch_loop,);
955 my $checkstyle = C4::Context->preference("opaccolorstylesheet");
956 if ($checkstyle =~ /\//)
958 $template->param( opacexternalsheet => $checkstyle);
961 my $opaccolorstylesheet = C4::Context->preference("opaccolorstylesheet");
962 $template->param( opaccolorstylesheet => $opaccolorstylesheet);
967 casAuthentication => C4::Context->preference("casAuthentication"),
968 suggestion => C4::Context->preference("suggestion"),
969 virtualshelves => C4::Context->preference("virtualshelves"),
970 LibraryName => C4::Context->preference("LibraryName"),
971 opacuserlogin => C4::Context->preference("opacuserlogin"),
972 OpacNav => C4::Context->preference("OpacNav"),
973 OpacNavBottom => C4::Context->preference("OpacNavBottom"),
974 opaccredits => C4::Context->preference("opaccredits"),
975 OpacFavicon => C4::Context->preference("OpacFavicon"),
976 opacreadinghistory => C4::Context->preference("opacreadinghistory"),
977 opacsmallimage => C4::Context->preference("opacsmallimage"),
978 opaclayoutstylesheet => C4::Context->preference("opaclayoutstylesheet"),
979 opaclanguagesdisplay => C4::Context->preference("opaclanguagesdisplay"),
980 opacuserjs => C4::Context->preference("opacuserjs"),
981 opacbookbag => "" . C4::Context->preference("opacbookbag"),
982 OpacCloud => C4::Context->preference("OpacCloud"),
983 OpacTopissue => C4::Context->preference("OpacTopissue"),
984 OpacAuthorities => C4::Context->preference("OpacAuthorities"),
985 OpacBrowser => C4::Context->preference("OpacBrowser"),
986 opacheader => C4::Context->preference("opacheader"),
987 TagsEnabled => C4::Context->preference("TagsEnabled"),
988 OPACUserCSS => C4::Context->preference("OPACUserCSS"),
989 opacstylesheet => C4::Context->preference("opacstylesheet"),
990 intranetcolorstylesheet =>
991 C4::Context->preference("intranetcolorstylesheet"),
992 intranetstylesheet => C4::Context->preference("intranetstylesheet"),
993 intranetbookbag => C4::Context->preference("intranetbookbag"),
994 IntranetNav => C4::Context->preference("IntranetNav"),
995 intranetuserjs => C4::Context->preference("intranetuserjs"),
996 IndependantBranches=> C4::Context->preference("IndependantBranches"),
997 AutoLocation => C4::Context->preference("AutoLocation"),
998 wrongip => $info{'wrongip'},
1001 $template->param( OpacPublic => C4::Context->preference("OpacPublic"));
1002 $template->param( loginprompt => 1 ) unless $info{'nopermission'};
1006 # Is authentication against multiple CAS servers enabled?
1007 if (C4::Auth_with_cas::multipleAuth && !$casparam) {
1008 my $casservers = C4::Auth_with_cas::getMultipleAuth();
1010 foreach my $key (keys %$casservers) {
1011 push @tmplservers, {name => $key, value => login_cas_url($query, $key) . "?cas=$key" };
1013 #warn Data::Dumper::Dumper(\@tmplservers);
1015 casServersLoop => \@tmplservers
1019 casServerUrl => login_cas_url($query),
1024 invalidCasLogin => $info{'invalidCasLogin'}
1028 my $self_url = $query->url( -absolute => 1 );
1031 LibraryName => C4::Context->preference("LibraryName"),
1033 $template->param( %info );
1034 # $cookie = $query->cookie(CGISESSID => $session->id
1036 print $query->header(
1037 -type => 'text/html',
1038 -charset => 'utf-8',
1045 =head2 check_api_auth
1047 ($status, $cookie, $sessionId) = check_api_auth($query, $userflags);
1049 Given a CGI query containing the parameters 'userid' and 'password' and/or a session
1050 cookie, determine if the user has the privileges specified by C<$userflags>.
1052 C<check_api_auth> is is meant for authenticating users of web services, and
1053 consequently will always return and will not attempt to redirect the user
1056 If a valid session cookie is already present, check_api_auth will return a status
1057 of "ok", the cookie, and the Koha session ID.
1059 If no session cookie is present, check_api_auth will check the 'userid' and 'password
1060 parameters and create a session cookie and Koha session if the supplied credentials
1063 Possible return values in C<$status> are:
1067 =item "ok" -- user authenticated; C<$cookie> and C<$sessionid> have valid values.
1069 =item "failed" -- credentials are not correct; C<$cookie> and C<$sessionid> are undef
1071 =item "maintenance" -- DB is in maintenance mode; no login possible at the moment
1073 =item "expired -- session cookie has expired; API user should resubmit userid and password
1079 sub check_api_auth {
1081 my $flagsrequired = shift;
1083 my $dbh = C4::Context->dbh;
1084 my $timeout = C4::Context->preference('timeout');
1085 $timeout = 600 unless $timeout;
1087 unless (C4::Context->preference('Version')) {
1088 # database has not been installed yet
1089 return ("maintenance", undef, undef);
1091 my $kohaversion=C4::Context::KOHAVERSION;
1092 $kohaversion =~ s/(.*\..*)\.(.*)\.(.*)/$1$2$3/;
1093 if (C4::Context->preference('Version') < $kohaversion) {
1094 # database in need of version update; assume that
1095 # no API should be called while databsae is in
1097 return ("maintenance", undef, undef);
1100 # FIXME -- most of what follows is a copy-and-paste
1101 # of code from checkauth. There is an obvious need
1102 # for refactoring to separate the various parts of
1103 # the authentication code, but as of 2007-11-19 this
1104 # is deferred so as to not introduce bugs into the
1105 # regular authentication code for Koha 3.0.
1107 # see if we have a valid session cookie already
1108 # however, if a userid parameter is present (i.e., from
1109 # a form submission, assume that any current cookie
1111 my $sessionID = undef;
1112 unless ($query->param('userid')) {
1113 $sessionID = $query->cookie("CGISESSID");
1115 if ($sessionID && not $cas) {
1116 my $session = get_session($sessionID);
1117 C4::Context->_new_userenv($sessionID);
1119 C4::Context::set_userenv(
1120 $session->param('number'), $session->param('id'),
1121 $session->param('cardnumber'), $session->param('firstname'),
1122 $session->param('surname'), $session->param('branch'),
1123 $session->param('branchname'), $session->param('flags'),
1124 $session->param('emailaddress'), $session->param('branchprinter')
1127 my $ip = $session->param('ip');
1128 my $lasttime = $session->param('lasttime');
1129 my $userid = $session->param('id');
1130 if ( $lasttime < time() - $timeout ) {
1133 C4::Context->_unset_userenv($sessionID);
1136 return ("expired", undef, undef);
1137 } elsif ( $ip ne $ENV{'REMOTE_ADDR'} ) {
1138 # IP address changed
1140 C4::Context->_unset_userenv($sessionID);
1143 return ("expired", undef, undef);
1145 my $cookie = $query->cookie( CGISESSID => $session->id );
1146 $session->param('lasttime',time());
1147 my $flags = haspermission($userid, $flagsrequired);
1149 return ("ok", $cookie, $sessionID);
1152 C4::Context->_unset_userenv($sessionID);
1155 return ("failed", undef, undef);
1159 return ("expired", undef, undef);
1163 my $userid = $query->param('userid');
1164 my $password = $query->param('password');
1165 my ($return, $cardnumber);
1168 if ($cas && $query->param('PT')) {
1170 $debug and print STDERR "## check_api_auth - checking CAS\n";
1171 # In case of a CAS authentication, we use the ticket instead of the password
1172 my $PT = $query->param('PT');
1173 ($return,$cardnumber,$userid) = check_api_auth_cas($dbh, $PT, $query); # EXTERNAL AUTH
1175 # User / password auth
1176 unless ($userid and $password) {
1177 # caller did something wrong, fail the authenticateion
1178 return ("failed", undef, undef);
1180 ( $return, $cardnumber ) = checkpw( $dbh, $userid, $password, $query );
1183 if ($return and haspermission( $userid, $flagsrequired)) {
1184 my $session = get_session("");
1185 return ("failed", undef, undef) unless $session;
1187 my $sessionID = $session->id;
1188 C4::Context->_new_userenv($sessionID);
1189 my $cookie = $query->cookie(CGISESSID => $sessionID);
1190 if ( $return == 1 ) {
1192 $borrowernumber, $firstname, $surname,
1193 $userflags, $branchcode, $branchname,
1194 $branchprinter, $emailaddress
1198 "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=?"
1200 $sth->execute($userid);
1202 $borrowernumber, $firstname, $surname,
1203 $userflags, $branchcode, $branchname,
1204 $branchprinter, $emailaddress
1205 ) = $sth->fetchrow if ( $sth->rows );
1207 unless ($sth->rows ) {
1208 my $sth = $dbh->prepare(
1209 "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=?"
1211 $sth->execute($cardnumber);
1213 $borrowernumber, $firstname, $surname,
1214 $userflags, $branchcode, $branchname,
1215 $branchprinter, $emailaddress
1216 ) = $sth->fetchrow if ( $sth->rows );
1218 unless ( $sth->rows ) {
1219 $sth->execute($userid);
1221 $borrowernumber, $firstname, $surname, $userflags,
1222 $branchcode, $branchname, $branchprinter, $emailaddress
1223 ) = $sth->fetchrow if ( $sth->rows );
1227 my $ip = $ENV{'REMOTE_ADDR'};
1228 # if they specify at login, use that
1229 if ($query->param('branch')) {
1230 $branchcode = $query->param('branch');
1231 $branchname = GetBranchName($branchcode);
1233 my $branches = GetBranches();
1235 foreach my $br ( keys %$branches ) {
1236 # now we work with the treatment of ip
1237 my $domain = $branches->{$br}->{'branchip'};
1238 if ( $domain && $ip =~ /^$domain/ ) {
1239 $branchcode = $branches->{$br}->{'branchcode'};
1241 # new op dev : add the branchprinter and branchname in the cookie
1242 $branchprinter = $branches->{$br}->{'branchprinter'};
1243 $branchname = $branches->{$br}->{'branchname'};
1246 $session->param('number',$borrowernumber);
1247 $session->param('id',$userid);
1248 $session->param('cardnumber',$cardnumber);
1249 $session->param('firstname',$firstname);
1250 $session->param('surname',$surname);
1251 $session->param('branch',$branchcode);
1252 $session->param('branchname',$branchname);
1253 $session->param('flags',$userflags);
1254 $session->param('emailaddress',$emailaddress);
1255 $session->param('ip',$session->remote_addr());
1256 $session->param('lasttime',time());
1257 } elsif ( $return == 2 ) {
1258 #We suppose the user is the superlibrarian
1259 $session->param('number',0);
1260 $session->param('id',C4::Context->config('user'));
1261 $session->param('cardnumber',C4::Context->config('user'));
1262 $session->param('firstname',C4::Context->config('user'));
1263 $session->param('surname',C4::Context->config('user'));
1264 $session->param('branch','NO_LIBRARY_SET');
1265 $session->param('branchname','NO_LIBRARY_SET');
1266 $session->param('flags',1);
1267 $session->param('emailaddress', C4::Context->preference('KohaAdminEmailAddress'));
1268 $session->param('ip',$session->remote_addr());
1269 $session->param('lasttime',time());
1271 C4::Context::set_userenv(
1272 $session->param('number'), $session->param('id'),
1273 $session->param('cardnumber'), $session->param('firstname'),
1274 $session->param('surname'), $session->param('branch'),
1275 $session->param('branchname'), $session->param('flags'),
1276 $session->param('emailaddress'), $session->param('branchprinter')
1278 return ("ok", $cookie, $sessionID);
1280 return ("failed", undef, undef);
1285 =head2 check_cookie_auth
1287 ($status, $sessionId) = check_api_auth($cookie, $userflags);
1289 Given a CGISESSID cookie set during a previous login to Koha, determine
1290 if the user has the privileges specified by C<$userflags>.
1292 C<check_cookie_auth> is meant for authenticating special services
1293 such as tools/upload-file.pl that are invoked by other pages that
1294 have been authenticated in the usual way.
1296 Possible return values in C<$status> are:
1300 =item "ok" -- user authenticated; C<$sessionID> have valid values.
1302 =item "failed" -- credentials are not correct; C<$sessionid> are undef
1304 =item "maintenance" -- DB is in maintenance mode; no login possible at the moment
1306 =item "expired -- session cookie has expired; API user should resubmit userid and password
1312 sub check_cookie_auth {
1314 my $flagsrequired = shift;
1316 my $dbh = C4::Context->dbh;
1317 my $timeout = C4::Context->preference('timeout');
1318 $timeout = 600 unless $timeout;
1320 unless (C4::Context->preference('Version')) {
1321 # database has not been installed yet
1322 return ("maintenance", undef);
1324 my $kohaversion=C4::Context::KOHAVERSION;
1325 $kohaversion =~ s/(.*\..*)\.(.*)\.(.*)/$1$2$3/;
1326 if (C4::Context->preference('Version') < $kohaversion) {
1327 # database in need of version update; assume that
1328 # no API should be called while databsae is in
1330 return ("maintenance", undef);
1333 # FIXME -- most of what follows is a copy-and-paste
1334 # of code from checkauth. There is an obvious need
1335 # for refactoring to separate the various parts of
1336 # the authentication code, but as of 2007-11-23 this
1337 # is deferred so as to not introduce bugs into the
1338 # regular authentication code for Koha 3.0.
1340 # see if we have a valid session cookie already
1341 # however, if a userid parameter is present (i.e., from
1342 # a form submission, assume that any current cookie
1344 unless (defined $cookie and $cookie) {
1345 return ("failed", undef);
1347 my $sessionID = $cookie;
1348 my $session = get_session($sessionID);
1349 C4::Context->_new_userenv($sessionID);
1351 C4::Context::set_userenv(
1352 $session->param('number'), $session->param('id'),
1353 $session->param('cardnumber'), $session->param('firstname'),
1354 $session->param('surname'), $session->param('branch'),
1355 $session->param('branchname'), $session->param('flags'),
1356 $session->param('emailaddress'), $session->param('branchprinter')
1359 my $ip = $session->param('ip');
1360 my $lasttime = $session->param('lasttime');
1361 my $userid = $session->param('id');
1362 if ( $lasttime < time() - $timeout ) {
1365 C4::Context->_unset_userenv($sessionID);
1368 return ("expired", undef);
1369 } elsif ( $ip ne $ENV{'REMOTE_ADDR'} ) {
1370 # IP address changed
1372 C4::Context->_unset_userenv($sessionID);
1375 return ("expired", undef);
1377 $session->param('lasttime',time());
1378 my $flags = haspermission($userid, $flagsrequired);
1380 return ("ok", $sessionID);
1383 C4::Context->_unset_userenv($sessionID);
1386 return ("failed", undef);
1390 return ("expired", undef);
1397 my $session = get_session($sessionID);
1399 Given a session ID, retrieve the CGI::Session object used to store
1400 the session's state. The session object can be used to store
1401 data that needs to be accessed by different scripts during a
1404 If the C<$sessionID> parameter is an empty string, a new session
1410 my $sessionID = shift;
1411 my $storage_method = C4::Context->preference('SessionStorage');
1412 my $dbh = C4::Context->dbh;
1414 if ($storage_method eq 'mysql'){
1415 $session = new CGI::Session("driver:MySQL;serializer:yaml;id:md5", $sessionID, {Handle=>$dbh});
1417 elsif ($storage_method eq 'Pg') {
1418 $session = new CGI::Session("driver:PostgreSQL;serializer:yaml;id:md5", $sessionID, {Handle=>$dbh});
1420 elsif ($storage_method eq 'memcached' && $servers){
1421 $session = new CGI::Session("driver:memcached;serializer:yaml;id:md5", $sessionID, { Memcached => $memcached } );
1424 # catch all defaults to tmp should work on all systems
1425 $session = new CGI::Session("driver:File;serializer:yaml;id:md5", $sessionID, {Directory=>'/tmp'});
1432 my ( $dbh, $userid, $password, $query ) = @_;
1434 $debug and print "## checkpw - checking LDAP\n";
1435 my ($retval,$retcard,$retuserid) = checkpw_ldap(@_); # EXTERNAL AUTH
1436 ($retval) and return ($retval,$retcard,$retuserid);
1439 if ($cas && $query && $query->param('ticket')) {
1440 $debug and print STDERR "## checkpw - checking CAS\n";
1441 # In case of a CAS authentication, we use the ticket instead of the password
1442 my $ticket = $query->param('ticket');
1443 my ($retval,$retcard,$retuserid) = checkpw_cas($dbh, $ticket, $query); # EXTERNAL AUTH
1444 ($retval) and return ($retval,$retcard,$retuserid);
1451 "select password,cardnumber,borrowernumber,userid,firstname,surname,branchcode,flags from borrowers where userid=?"
1453 $sth->execute($userid);
1455 my ( $md5password, $cardnumber, $borrowernumber, $userid, $firstname,
1456 $surname, $branchcode, $flags )
1458 if ( md5_base64($password) eq $md5password and $md5password ne "!") {
1460 C4::Context->set_userenv( "$borrowernumber", $userid, $cardnumber,
1461 $firstname, $surname, $branchcode, $flags );
1462 return 1, $cardnumber, $userid;
1467 "select password,cardnumber,borrowernumber,userid, firstname,surname,branchcode,flags from borrowers where cardnumber=?"
1469 $sth->execute($userid);
1471 my ( $md5password, $cardnumber, $borrowernumber, $userid, $firstname,
1472 $surname, $branchcode, $flags )
1474 if ( md5_base64($password) eq $md5password ) {
1476 C4::Context->set_userenv( $borrowernumber, $userid, $cardnumber,
1477 $firstname, $surname, $branchcode, $flags );
1478 return 1, $cardnumber, $userid;
1481 if ( $userid && $userid eq C4::Context->config('user')
1482 && "$password" eq C4::Context->config('pass') )
1485 # Koha superuser account
1486 # C4::Context->set_userenv(0,0,C4::Context->config('user'),C4::Context->config('user'),C4::Context->config('user'),"",1);
1489 if ( $userid && $userid eq 'demo'
1490 && "$password" eq 'demo'
1491 && C4::Context->config('demo') )
1494 # DEMO => the demo user is allowed to do everything (if demo set to 1 in koha.conf
1495 # some features won't be effective : modify systempref, modify MARC structure,
1503 my $authflags = getuserflags($flags, $userid, [$dbh]);
1505 Translates integer flags into permissions strings hash.
1507 C<$flags> is the integer userflags value ( borrowers.userflags )
1508 C<$userid> is the members.userid, used for building subpermissions
1509 C<$authflags> is a hashref of permissions
1516 my $dbh = @_ ? shift : C4::Context->dbh;
1518 $flags = 0 unless $flags;
1519 my $sth = $dbh->prepare("SELECT bit, flag, defaulton FROM userflags");
1522 while ( my ( $bit, $flag, $defaulton ) = $sth->fetchrow ) {
1523 if ( ( $flags & ( 2**$bit ) ) || $defaulton ) {
1524 $userflags->{$flag} = 1;
1527 $userflags->{$flag} = 0;
1531 # get subpermissions and merge with top-level permissions
1532 my $user_subperms = get_user_subpermissions($userid);
1533 foreach my $module (keys %$user_subperms) {
1534 next if $userflags->{$module} == 1; # user already has permission for everything in this module
1535 $userflags->{$module} = $user_subperms->{$module};
1541 =head2 get_user_subpermissions
1543 $user_perm_hashref = get_user_subpermissions($userid);
1545 Given the userid (note, not the borrowernumber) of a staff user,
1546 return a hashref of hashrefs of the specific subpermissions
1547 accorded to the user. An example return is
1551 export_catalog => 1,
1552 import_patrons => 1,
1556 The top-level hash-key is a module or function code from
1557 userflags.flag, while the second-level key is a code
1560 The results of this function do not give a complete picture
1561 of the functions that a staff user can access; it is also
1562 necessary to check borrowers.flags.
1566 sub get_user_subpermissions {
1569 my $dbh = C4::Context->dbh;
1570 my $sth = $dbh->prepare("SELECT flag, user_permissions.code
1571 FROM user_permissions
1572 JOIN permissions USING (module_bit, code)
1573 JOIN userflags ON (module_bit = bit)
1574 JOIN borrowers USING (borrowernumber)
1576 $sth->execute($userid);
1578 my $user_perms = {};
1579 while (my $perm = $sth->fetchrow_hashref) {
1580 $user_perms->{$perm->{'flag'}}->{$perm->{'code'}} = 1;
1585 =head2 get_all_subpermissions
1587 my $perm_hashref = get_all_subpermissions();
1589 Returns a hashref of hashrefs defining all specific
1590 permissions currently defined. The return value
1591 has the same structure as that of C<get_user_subpermissions>,
1592 except that the innermost hash value is the description
1593 of the subpermission.
1597 sub get_all_subpermissions {
1598 my $dbh = C4::Context->dbh;
1599 my $sth = $dbh->prepare("SELECT flag, code, description
1601 JOIN userflags ON (module_bit = bit)");
1605 while (my $perm = $sth->fetchrow_hashref) {
1606 $all_perms->{$perm->{'flag'}}->{$perm->{'code'}} = $perm->{'description'};
1611 =head2 haspermission
1613 $flags = ($userid, $flagsrequired);
1615 C<$userid> the userid of the member
1616 C<$flags> is a hashref of required flags like C<$borrower-<{authflags}>
1618 Returns member's flags or 0 if a permission is not met.
1623 my ($userid, $flagsrequired) = @_;
1624 my $sth = C4::Context->dbh->prepare("SELECT flags FROM borrowers WHERE userid=?");
1625 $sth->execute($userid);
1626 my $flags = getuserflags($sth->fetchrow(), $userid);
1627 if ( $userid eq C4::Context->config('user') ) {
1628 # Super User Account from /etc/koha.conf
1629 $flags->{'superlibrarian'} = 1;
1631 elsif ( $userid eq 'demo' && C4::Context->config('demo') ) {
1632 # Demo user that can do "anything" (demo=1 in /etc/koha.conf)
1633 $flags->{'superlibrarian'} = 1;
1636 return $flags if $flags->{superlibrarian};
1638 foreach my $module ( keys %$flagsrequired ) {
1639 my $subperm = $flagsrequired->{$module};
1640 if ($subperm eq '*') {
1641 return 0 unless ( $flags->{$module} == 1 or ref($flags->{$module}) );
1643 return 0 unless ( $flags->{$module} == 1 or
1644 ( ref($flags->{$module}) and
1645 exists $flags->{$module}->{$subperm} and
1646 $flags->{$module}->{$subperm} == 1
1652 #FIXME - This fcn should return the failed permission so a suitable error msg can be delivered.
1656 sub getborrowernumber {
1658 my $userenv = C4::Context->userenv;
1659 if ( defined( $userenv ) && ref( $userenv ) eq 'HASH' && $userenv->{number} ) {
1660 return $userenv->{number};
1662 my $dbh = C4::Context->dbh;
1663 for my $field ( 'userid', 'cardnumber' ) {
1665 $dbh->prepare("select borrowernumber from borrowers where $field=?");
1666 $sth->execute($userid);
1668 my ($bnumber) = $sth->fetchrow;
1675 END { } # module clean-up code here (global destructor)