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
30 use C4::Branch; # GetBranches
31 use C4::VirtualShelves;
32 use POSIX qw/strftime/;
33 use List::MoreUtils qw/ any /;
36 use vars qw($VERSION @ISA @EXPORT @EXPORT_OK %EXPORT_TAGS $debug $ldap $cas $caslogout);
39 sub psgi_env { any { /^psgi\./ } keys %ENV }
41 if ( psgi_env ) { die 'psgi:exit' }
45 $VERSION = 3.02; # set version for version checking
48 @EXPORT = qw(&checkauth &get_template_and_user &haspermission &get_user_subpermissions);
49 @EXPORT_OK = qw(&check_api_auth &get_session &check_cookie_auth &checkpw &get_all_subpermissions &get_user_subpermissions);
50 %EXPORT_TAGS = ( EditPermissions => [qw(get_all_subpermissions get_user_subpermissions)] );
51 $ldap = C4::Context->config('useldapserver') || 0;
52 $cas = C4::Context->preference('casAuthentication');
53 $caslogout = C4::Context->preference('casLogout');
54 require C4::Auth_with_cas; # no import
56 require C4::Auth_with_ldap;
57 import C4::Auth_with_ldap qw(checkpw_ldap);
60 import C4::Auth_with_cas qw(check_api_auth_cas checkpw_cas login_cas logout_cas login_cas_url);
67 C4::Auth - Authenticates Koha users
77 my ($template, $borrowernumber, $cookie)
78 = get_template_and_user(
80 template_name => "opac-main.tmpl",
84 flagsrequired => {borrow => 1, catalogue => '*', tools => 'import_patrons' },
88 output_html_with_http_headers $query, $cookie, $template->output;
92 The main function of this module is to provide
93 authentification. However the get_template_and_user function has
94 been provided so that a users login information is passed along
95 automatically. This gets loaded into the template.
99 =head2 get_template_and_user
101 my ($template, $borrowernumber, $cookie)
102 = get_template_and_user(
104 template_name => "opac-main.tmpl",
107 authnotrequired => 1,
108 flagsrequired => {borrow => 1, catalogue => '*', tools => 'import_patrons' },
112 This call passes the C<query>, C<flagsrequired> and C<authnotrequired>
113 to C<&checkauth> (in this module) to perform authentification.
114 See C<&checkauth> for an explanation of these parameters.
116 The C<template_name> is then used to find the correct template for
117 the page. The authenticated users details are loaded onto the
118 template in the HTML::Template LOOP variable C<USER_INFO>. Also the
119 C<sessionID> is passed to the template. This can be used in templates
120 if cookies are disabled. It needs to be put as and input to every
123 More information on the C<gettemplate> sub can be found in the
128 my $SEARCH_HISTORY_INSERT_SQL =<<EOQ;
129 INSERT INTO search_history(userid, sessionid, query_desc, query_cgi, total, time )
130 VALUES ( ?, ?, ?, ?, ?, FROM_UNIXTIME(?))
132 sub get_template_and_user {
135 C4::Templates::gettemplate( $in->{'template_name'}, $in->{'type'}, $in->{'query'} );
136 my ( $user, $cookie, $sessionID, $flags );
137 if ( $in->{'template_name'} !~m/maintenance/ ) {
138 ( $user, $cookie, $sessionID, $flags ) = checkauth(
140 $in->{'authnotrequired'},
141 $in->{'flagsrequired'},
147 my $insecure = C4::Context->preference('insecure');
148 if ($user or $insecure) {
150 # load the template variables for stylesheets and JavaScript
151 $template->param( css_libs => $in->{'css_libs'} );
152 $template->param( css_module => $in->{'css_module'} );
153 $template->param( css_page => $in->{'css_page'} );
154 $template->param( css_widgets => $in->{'css_widgets'} );
156 $template->param( js_libs => $in->{'js_libs'} );
157 $template->param( js_module => $in->{'js_module'} );
158 $template->param( js_page => $in->{'js_page'} );
159 $template->param( js_widgets => $in->{'js_widgets'} );
162 $template->param( loggedinusername => $user );
163 $template->param( sessionID => $sessionID );
165 my ($total, $pubshelves, $barshelves) = C4::Context->get_shelves_userenv();
166 if (defined($pubshelves)) {
167 $template->param( pubshelves => scalar @{$pubshelves},
168 pubshelvesloop => $pubshelves,
170 $template->param( pubtotal => $total->{'pubtotal'}, ) if ($total->{'pubtotal'} > scalar @{$pubshelves});
172 if (defined($barshelves)) {
173 $template->param( barshelves => scalar @{$barshelves},
174 barshelvesloop => $barshelves,
176 $template->param( bartotal => $total->{'bartotal'}, ) if ($total->{'bartotal'} > scalar @{$barshelves});
179 $borrowernumber = getborrowernumber($user) if defined($user);
181 my ( $borr ) = C4::Members::GetMemberDetails( $borrowernumber );
184 $template->param( "USER_INFO" => \@bordat );
186 my $all_perms = get_all_subpermissions();
188 my @flagroots = qw(circulate catalogue parameters borrowers permissions reserveforothers borrow
189 editcatalogue updatecharges management tools editauthorities serials reports acquisition);
190 # We are going to use the $flags returned by checkauth
191 # to create the template's parameters that will indicate
192 # which menus the user can access.
193 if (( $flags && $flags->{superlibrarian}==1) or $insecure==1) {
194 $template->param( CAN_user_circulate => 1 );
195 $template->param( CAN_user_catalogue => 1 );
196 $template->param( CAN_user_parameters => 1 );
197 $template->param( CAN_user_borrowers => 1 );
198 $template->param( CAN_user_permissions => 1 );
199 $template->param( CAN_user_reserveforothers => 1 );
200 $template->param( CAN_user_borrow => 1 );
201 $template->param( CAN_user_editcatalogue => 1 );
202 $template->param( CAN_user_updatecharges => 1 );
203 $template->param( CAN_user_acquisition => 1 );
204 $template->param( CAN_user_management => 1 );
205 $template->param( CAN_user_tools => 1 );
206 $template->param( CAN_user_editauthorities => 1 );
207 $template->param( CAN_user_serials => 1 );
208 $template->param( CAN_user_reports => 1 );
209 $template->param( CAN_user_staffaccess => 1 );
210 foreach my $module (keys %$all_perms) {
211 foreach my $subperm (keys %{ $all_perms->{$module} }) {
212 $template->param( "CAN_user_${module}_${subperm}" => 1 );
218 foreach my $module (keys %$all_perms) {
219 if ( $flags->{$module} == 1) {
220 foreach my $subperm (keys %{ $all_perms->{$module} }) {
221 $template->param( "CAN_user_${module}_${subperm}" => 1 );
223 } elsif ( ref($flags->{$module}) ) {
224 foreach my $subperm (keys %{ $flags->{$module} } ) {
225 $template->param( "CAN_user_${module}_${subperm}" => 1 );
232 foreach my $module (keys %$flags) {
233 if ( $flags->{$module} == 1 or ref($flags->{$module}) ) {
234 $template->param( "CAN_user_$module" => 1 );
235 if ($module eq "parameters") {
236 $template->param( CAN_user_management => 1 );
241 # Logged-in opac search history
242 # If the requested template is an opac one and opac search history is enabled
243 if ($in->{type} eq 'opac' && C4::Context->preference('EnableOpacSearchHistory')) {
244 my $dbh = C4::Context->dbh;
245 my $query = "SELECT COUNT(*) FROM search_history WHERE userid=?";
246 my $sth = $dbh->prepare($query);
247 $sth->execute($borrowernumber);
249 # If at least one search has already been performed
250 if ($sth->fetchrow_array > 0) {
251 # We show the link in opac
252 $template->param(ShowOpacRecentSearchLink => 1);
255 # And if there's a cookie with searches performed when the user was not logged in,
256 # we add them to the logged-in search history
257 my $searchcookie = $in->{'query'}->cookie('KohaOpacRecentSearches');
259 $searchcookie = uri_unescape($searchcookie);
260 my @recentSearches = @{thaw($searchcookie) || []};
261 if (@recentSearches) {
262 my $sth = $dbh->prepare($SEARCH_HISTORY_INSERT_SQL);
263 $sth->execute( $borrowernumber,
264 $in->{'query'}->cookie("CGISESSID"),
269 ) foreach @recentSearches;
271 # And then, delete the cookie's content
272 my $newsearchcookie = $in->{'query'}->cookie(
273 -name => 'KohaOpacRecentSearches',
274 -value => freeze([]),
277 $cookie = [$cookie, $newsearchcookie];
282 else { # if this is an anonymous session, setup to display public lists...
284 # load the template variables for stylesheets and JavaScript
285 $template->param( css_libs => $in->{'css_libs'} );
286 $template->param( css_module => $in->{'css_module'} );
287 $template->param( css_page => $in->{'css_page'} );
288 $template->param( css_widgets => $in->{'css_widgets'} );
290 $template->param( js_libs => $in->{'js_libs'} );
291 $template->param( js_module => $in->{'js_module'} );
292 $template->param( js_page => $in->{'js_page'} );
293 $template->param( js_widgets => $in->{'js_widgets'} );
295 $template->param( sessionID => $sessionID );
297 my ($total, $pubshelves) = C4::Context->get_shelves_userenv(); # an anonymous user has no 'barshelves'...
298 if (defined $pubshelves) {
299 $template->param( pubshelves => scalar @{$pubshelves},
300 pubshelvesloop => $pubshelves,
302 $template->param( pubtotal => $total->{'pubtotal'}, ) if ($total->{'pubtotal'} > scalar @{$pubshelves});
306 # Anonymous opac search history
307 # If opac search history is enabled and at least one search has already been performed
308 if (C4::Context->preference('EnableOpacSearchHistory')) {
309 my $searchcookie = $in->{'query'}->cookie('KohaOpacRecentSearches');
311 $searchcookie = uri_unescape($searchcookie);
312 my @recentSearches = @{thaw($searchcookie) || []};
313 # We show the link in opac
314 if (@recentSearches) {
315 $template->param(ShowOpacRecentSearchLink => 1);
320 if(C4::Context->preference('dateformat')){
321 if(C4::Context->preference('dateformat') eq "metric"){
322 $template->param(dateformat_metric => 1);
323 } elsif(C4::Context->preference('dateformat') eq "us"){
324 $template->param(dateformat_us => 1);
326 $template->param(dateformat_iso => 1);
329 $template->param(dateformat_iso => 1);
332 # these template parameters are set the same regardless of $in->{'type'}
334 "BiblioDefaultView".C4::Context->preference("BiblioDefaultView") => 1,
335 EnhancedMessagingPreferences => C4::Context->preference('EnhancedMessagingPreferences'),
336 GoogleJackets => C4::Context->preference("GoogleJackets"),
337 OpenLibraryCovers => C4::Context->preference("OpenLibraryCovers"),
338 KohaAdminEmailAddress => "" . C4::Context->preference("KohaAdminEmailAddress"),
339 LoginBranchcode => (C4::Context->userenv?C4::Context->userenv->{"branch"}:"insecure"),
340 LoginFirstname => (C4::Context->userenv?C4::Context->userenv->{"firstname"}:"Bel"),
341 LoginSurname => C4::Context->userenv?C4::Context->userenv->{"surname"}:"Inconnu",
342 TagsEnabled => C4::Context->preference("TagsEnabled"),
343 hide_marc => C4::Context->preference("hide_marc"),
344 item_level_itypes => C4::Context->preference('item-level_itypes'),
345 patronimages => C4::Context->preference("patronimages"),
346 singleBranchMode => C4::Context->preference("singleBranchMode"),
347 XSLTDetailsDisplay => C4::Context->preference("XSLTDetailsDisplay"),
348 XSLTResultsDisplay => C4::Context->preference("XSLTResultsDisplay"),
349 using_https => $in->{'query'}->https() ? 1 : 0,
350 noItemTypeImages => C4::Context->preference("noItemTypeImages"),
353 if ( $in->{'type'} eq "intranet" ) {
355 AmazonContent => C4::Context->preference("AmazonContent"),
356 AmazonCoverImages => C4::Context->preference("AmazonCoverImages"),
357 AmazonEnabled => C4::Context->preference("AmazonEnabled"),
358 AmazonSimilarItems => C4::Context->preference("AmazonSimilarItems"),
359 AutoLocation => C4::Context->preference("AutoLocation"),
360 "BiblioDefaultView".C4::Context->preference("IntranetBiblioDefaultView") => 1,
361 CircAutocompl => C4::Context->preference("CircAutocompl"),
362 FRBRizeEditions => C4::Context->preference("FRBRizeEditions"),
363 IndependantBranches => C4::Context->preference("IndependantBranches"),
364 IntranetNav => C4::Context->preference("IntranetNav"),
365 IntranetmainUserblock => C4::Context->preference("IntranetmainUserblock"),
366 LibraryName => C4::Context->preference("LibraryName"),
367 LoginBranchname => (C4::Context->userenv?C4::Context->userenv->{"branchname"}:"insecure"),
368 advancedMARCEditor => C4::Context->preference("advancedMARCEditor"),
369 canreservefromotherbranches => C4::Context->preference('canreservefromotherbranches'),
370 intranetcolorstylesheet => C4::Context->preference("intranetcolorstylesheet"),
371 IntranetFavicon => C4::Context->preference("IntranetFavicon"),
372 intranetreadinghistory => C4::Context->preference("intranetreadinghistory"),
373 intranetstylesheet => C4::Context->preference("intranetstylesheet"),
374 IntranetUserCSS => C4::Context->preference("IntranetUserCSS"),
375 intranetuserjs => C4::Context->preference("intranetuserjs"),
376 intranetbookbag => C4::Context->preference("intranetbookbag"),
377 suggestion => C4::Context->preference("suggestion"),
378 virtualshelves => C4::Context->preference("virtualshelves"),
379 StaffSerialIssueDisplayCount => C4::Context->preference("StaffSerialIssueDisplayCount"),
380 NoZebra => C4::Context->preference('NoZebra'),
381 EasyAnalyticalRecords => C4::Context->preference('EasyAnalyticalRecords'),
382 LocalCoverImages => C4::Context->preference('LocalCoverImages'),
383 OPACLocalCoverImages => C4::Context->preference('OPACLocalCoverImages'),
384 AllowMultipleCovers => C4::Context->preference('AllowMultipleCovers'),
388 warn "template type should be OPAC, here it is=[" . $in->{'type'} . "]" unless ( $in->{'type'} eq 'opac' );
389 #TODO : replace LibraryName syspref with 'system name', and remove this html processing
390 my $LibraryNameTitle = C4::Context->preference("LibraryName");
391 $LibraryNameTitle =~ s/<(?:\/?)(?:br|p)\s*(?:\/?)>/ /sgi;
392 $LibraryNameTitle =~ s/<(?:[^<>'"]|'(?:[^']*)'|"(?:[^"]*)")*>//sg;
393 # clean up the busc param in the session if the page is not opac-detail
394 if ($in->{'template_name'} =~ /opac-(.+)\.(?:tt|tmpl)$/ && $1 !~ /^(?:MARC|ISBD)?detail$/) {
395 my $sessionSearch = get_session($sessionID || $in->{'query'}->cookie("CGISESSID"));
396 $sessionSearch->clear(["busc"]) if ($sessionSearch->param("busc"));
398 # variables passed from CGI: opac_css_override and opac_search_limits.
399 my $opac_search_limit = $ENV{'OPAC_SEARCH_LIMIT'};
400 my $opac_limit_override = $ENV{'OPAC_LIMIT_OVERRIDE'};
402 if (($opac_search_limit =~ /branch:(\w+)/ && $opac_limit_override) || $in->{'query'}->param('limit') =~ /branch:(\w+)/){
403 $opac_name = $1; # opac_search_limit is a branch, so we use it.
404 } elsif (C4::Context->preference("SearchMyLibraryFirst") && C4::Context->userenv && C4::Context->userenv->{'branch'}) {
405 $opac_name = C4::Context->userenv->{'branch'};
407 my $checkstyle = C4::Context->preference("opaccolorstylesheet");
408 if ($checkstyle =~ /http/)
410 $template->param( opacexternalsheet => $checkstyle);
413 my $opaccolorstylesheet = C4::Context->preference("opaccolorstylesheet");
414 $template->param( opaccolorstylesheet => $opaccolorstylesheet);
417 AmazonContent => "" . C4::Context->preference("AmazonContent"),
418 AnonSuggestions => "" . C4::Context->preference("AnonSuggestions"),
419 AuthorisedValueImages => C4::Context->preference("AuthorisedValueImages"),
420 BranchesLoop => GetBranchesLoop($opac_name),
421 LibraryName => "" . C4::Context->preference("LibraryName"),
422 LibraryNameTitle => "" . $LibraryNameTitle,
423 LoginBranchname => C4::Context->userenv?C4::Context->userenv->{"branchname"}:"",
424 OPACAmazonEnabled => C4::Context->preference("OPACAmazonEnabled"),
425 OPACAmazonSimilarItems => C4::Context->preference("OPACAmazonSimilarItems"),
426 OPACAmazonCoverImages => C4::Context->preference("OPACAmazonCoverImages"),
427 OPACAmazonReviews => C4::Context->preference("OPACAmazonReviews"),
428 OPACFRBRizeEditions => C4::Context->preference("OPACFRBRizeEditions"),
429 OpacHighlightedWords => C4::Context->preference("OpacHighlightedWords"),
430 OPACItemHolds => C4::Context->preference("OPACItemHolds"),
431 OPACShelfBrowser => "". C4::Context->preference("OPACShelfBrowser"),
432 OpacShowRecentComments => C4::Context->preference("OpacShowRecentComments"),
433 OPACURLOpenInNewWindow => "" . C4::Context->preference("OPACURLOpenInNewWindow"),
434 OPACUserCSS => "". C4::Context->preference("OPACUserCSS"),
435 OPACViewOthersSuggestions => "" . C4::Context->preference("OPACViewOthersSuggestions"),
436 OpacAuthorities => C4::Context->preference("OpacAuthorities"),
437 OPACBaseURL => ($in->{'query'}->https() ? "https://" : "http://") . $ENV{'SERVER_NAME'} .
438 ($ENV{'SERVER_PORT'} eq ($in->{'query'}->https() ? "443" : "80") ? '' : ":$ENV{'SERVER_PORT'}"),
439 opac_css_override => $ENV{'OPAC_CSS_OVERRIDE'},
440 opac_search_limit => $opac_search_limit,
441 opac_limit_override => $opac_limit_override,
442 OpacBrowser => C4::Context->preference("OpacBrowser"),
443 OpacCloud => C4::Context->preference("OpacCloud"),
444 OpacKohaUrl => C4::Context->preference("OpacKohaUrl"),
445 OpacMainUserBlock => "" . C4::Context->preference("OpacMainUserBlock"),
446 OpacNav => "" . C4::Context->preference("OpacNav"),
447 OpacNavBottom => "" . C4::Context->preference("OpacNavBottom"),
448 OpacPasswordChange => C4::Context->preference("OpacPasswordChange"),
449 OPACPatronDetails => C4::Context->preference("OPACPatronDetails"),
450 OPACPrivacy => C4::Context->preference("OPACPrivacy"),
451 OPACFinesTab => C4::Context->preference("OPACFinesTab"),
452 OpacTopissue => C4::Context->preference("OpacTopissue"),
453 RequestOnOpac => C4::Context->preference("RequestOnOpac"),
454 'Version' => C4::Context->preference('Version'),
455 hidelostitems => C4::Context->preference("hidelostitems"),
456 mylibraryfirst => (C4::Context->preference("SearchMyLibraryFirst") && C4::Context->userenv) ? C4::Context->userenv->{'branch'} : '',
457 opaclayoutstylesheet => "" . C4::Context->preference("opaclayoutstylesheet"),
458 opacstylesheet => "" . C4::Context->preference("opacstylesheet"),
459 opacbookbag => "" . C4::Context->preference("opacbookbag"),
460 opaccredits => "" . C4::Context->preference("opaccredits"),
461 OpacFavicon => C4::Context->preference("OpacFavicon"),
462 opacheader => "" . C4::Context->preference("opacheader"),
463 opaclanguagesdisplay => "" . C4::Context->preference("opaclanguagesdisplay"),
464 opacreadinghistory => C4::Context->preference("opacreadinghistory"),
465 opacsmallimage => "" . C4::Context->preference("opacsmallimage"),
466 opacuserjs => C4::Context->preference("opacuserjs"),
467 opacuserlogin => "" . C4::Context->preference("opacuserlogin"),
468 reviewson => C4::Context->preference("reviewson"),
469 ShowReviewer => C4::Context->preference("ShowReviewer"),
470 ShowReviewerPhoto => C4::Context->preference("ShowReviewerPhoto"),
471 suggestion => "" . C4::Context->preference("suggestion"),
472 virtualshelves => "" . C4::Context->preference("virtualshelves"),
473 OPACSerialIssueDisplayCount => C4::Context->preference("OPACSerialIssueDisplayCount"),
474 OpacAddMastheadLibraryPulldown => C4::Context->preference("OpacAddMastheadLibraryPulldown"),
475 OPACXSLTDetailsDisplay => C4::Context->preference("OPACXSLTDetailsDisplay"),
476 OPACXSLTResultsDisplay => C4::Context->preference("OPACXSLTResultsDisplay"),
477 SyndeticsClientCode => C4::Context->preference("SyndeticsClientCode"),
478 SyndeticsEnabled => C4::Context->preference("SyndeticsEnabled"),
479 SyndeticsCoverImages => C4::Context->preference("SyndeticsCoverImages"),
480 SyndeticsTOC => C4::Context->preference("SyndeticsTOC"),
481 SyndeticsSummary => C4::Context->preference("SyndeticsSummary"),
482 SyndeticsEditions => C4::Context->preference("SyndeticsEditions"),
483 SyndeticsExcerpt => C4::Context->preference("SyndeticsExcerpt"),
484 SyndeticsReviews => C4::Context->preference("SyndeticsReviews"),
485 SyndeticsAuthorNotes => C4::Context->preference("SyndeticsAuthorNotes"),
486 SyndeticsAwards => C4::Context->preference("SyndeticsAwards"),
487 SyndeticsSeries => C4::Context->preference("SyndeticsSeries"),
488 SyndeticsCoverImageSize => C4::Context->preference("SyndeticsCoverImageSize"),
489 OPACLocalCoverImages => C4::Context->preference("OPACLocalCoverImages"),
492 $template->param(OpacPublic => '1') if ($user || C4::Context->preference("OpacPublic"));
494 return ( $template, $borrowernumber, $cookie, $flags);
499 ($userid, $cookie, $sessionID) = &checkauth($query, $noauth, $flagsrequired, $type);
501 Verifies that the user is authorized to run this script. If
502 the user is authorized, a (userid, cookie, session-id, flags)
503 quadruple is returned. If the user is not authorized but does
504 not have the required privilege (see $flagsrequired below), it
505 displays an error page and exits. Otherwise, it displays the
506 login page and exits.
508 Note that C<&checkauth> will return if and only if the user
509 is authorized, so it should be called early on, before any
510 unfinished operations (e.g., if you've opened a file, then
511 C<&checkauth> won't close it for you).
513 C<$query> is the CGI object for the script calling C<&checkauth>.
515 The C<$noauth> argument is optional. If it is set, then no
516 authorization is required for the script.
518 C<&checkauth> fetches user and session information from C<$query> and
519 ensures that the user is authorized to run scripts that require
522 The C<$flagsrequired> argument specifies the required privileges
523 the user must have if the username and password are correct.
524 It should be specified as a reference-to-hash; keys in the hash
525 should be the "flags" for the user, as specified in the Members
526 intranet module. Any key specified must correspond to a "flag"
527 in the userflags table. E.g., { circulate => 1 } would specify
528 that the user must have the "circulate" privilege in order to
529 proceed. To make sure that access control is correct, the
530 C<$flagsrequired> parameter must be specified correctly.
532 Koha also has a concept of sub-permissions, also known as
533 granular permissions. This makes the value of each key
534 in the C<flagsrequired> hash take on an additional
539 The user must have access to all subfunctions of the module
540 specified by the hash key.
544 The user must have access to at least one subfunction of the module
545 specified by the hash key.
547 specific permission, e.g., 'export_catalog'
549 The user must have access to the specific subfunction list, which
550 must correspond to a row in the permissions table.
552 The C<$type> argument specifies whether the template should be
553 retrieved from the opac or intranet directory tree. "opac" is
554 assumed if it is not specified; however, if C<$type> is specified,
555 "intranet" is assumed if it is not "opac".
557 If C<$query> does not have a valid session ID associated with it
558 (i.e., the user has not logged in) or if the session has expired,
559 C<&checkauth> presents the user with a login page (from the point of
560 view of the original script, C<&checkauth> does not return). Once the
561 user has authenticated, C<&checkauth> restarts the original script
562 (this time, C<&checkauth> returns).
564 The login page is provided using a HTML::Template, which is set in the
565 systempreferences table or at the top of this file. The variable C<$type>
566 selects which template to use, either the opac or the intranet
567 authentification template.
569 C<&checkauth> returns a user ID, a cookie, and a session ID. The
570 cookie should be sent back to the browser; it verifies that the user
575 sub _version_check ($$) {
579 # If Version syspref is unavailable, it means Koha is beeing installed,
580 # and so we must redirect to OPAC maintenance page or to the WebInstaller
581 # also, if OpacMaintenance is ON, OPAC should redirect to maintenance
582 if (C4::Context->preference('OpacMaintenance') && $type eq 'opac') {
583 warn "OPAC Install required, redirecting to maintenance";
584 print $query->redirect("/cgi-bin/koha/maintenance.pl");
586 unless ( $version = C4::Context->preference('Version') ) { # assignment, not comparison
587 if ( $type ne 'opac' ) {
588 warn "Install required, redirecting to Installer";
589 print $query->redirect("/cgi-bin/koha/installer/install.pl");
591 warn "OPAC Install required, redirecting to maintenance";
592 print $query->redirect("/cgi-bin/koha/maintenance.pl");
597 # check that database and koha version are the same
598 # there is no DB version, it's a fresh install,
599 # go to web installer
600 # there is a DB version, compare it to the code version
601 my $kohaversion=C4::Context::KOHAVERSION;
602 # remove the 3 last . to have a Perl number
603 $kohaversion =~ s/(.*\..*)\.(.*)\.(.*)/$1$2$3/;
604 $debug and print STDERR "kohaversion : $kohaversion\n";
605 if ($version < $kohaversion){
606 my $warning = "Database update needed, redirecting to %s. Database is $version and Koha is $kohaversion";
607 if ($type ne 'opac'){
608 warn sprintf($warning, 'Installer');
609 print $query->redirect("/cgi-bin/koha/installer/install.pl?step=3");
611 warn sprintf("OPAC: " . $warning, 'maintenance');
612 print $query->redirect("/cgi-bin/koha/maintenance.pl");
620 open L, ">>/tmp/sessionlog" or warn "ERROR: Cannot append to /tmp/sessionlog";
621 printf L join("\n",@_);
627 $debug and warn "Checking Auth";
628 # $authnotrequired will be set for scripts which will run without authentication
629 my $authnotrequired = shift;
630 my $flagsrequired = shift;
632 $type = 'opac' unless $type;
634 my $dbh = C4::Context->dbh;
635 my $timeout = C4::Context->preference('timeout');
637 if ($timeout =~ /(\d+)[dD]/) {
638 $timeout = $1 * 86400;
640 $timeout = 600 unless $timeout;
642 _version_check($type,$query);
646 my ( $userid, $cookie, $sessionID, $flags, $barshelves, $pubshelves );
647 my $logout = $query->param('logout.x');
649 # This parameter is the name of the CAS server we want to authenticate against,
650 # when using authentication against multiple CAS servers, as configured in Auth_cas_servers.yaml
651 my $casparam = $query->param('cas');
653 if ( $userid = $ENV{'REMOTE_USER'} ) {
654 # Using Basic Authentication, no cookies required
655 $cookie = $query->cookie(
656 -name => 'CGISESSID',
662 elsif ( $sessionID = $query->cookie("CGISESSID")) { # assignment, not comparison
663 my $session = get_session($sessionID);
664 C4::Context->_new_userenv($sessionID);
665 my ($ip, $lasttime, $sessiontype);
667 C4::Context::set_userenv(
668 $session->param('number'), $session->param('id'),
669 $session->param('cardnumber'), $session->param('firstname'),
670 $session->param('surname'), $session->param('branch'),
671 $session->param('branchname'), $session->param('flags'),
672 $session->param('emailaddress'), $session->param('branchprinter')
674 C4::Context::set_shelves_userenv('bar',$session->param('barshelves'));
675 C4::Context::set_shelves_userenv('pub',$session->param('pubshelves'));
676 C4::Context::set_shelves_userenv('tot',$session->param('totshelves'));
677 $debug and printf STDERR "AUTH_SESSION: (%s)\t%s %s - %s\n", map {$session->param($_)} qw(cardnumber firstname surname branch) ;
678 $ip = $session->param('ip');
679 $lasttime = $session->param('lasttime');
680 $userid = $session->param('id');
681 $sessiontype = $session->param('sessiontype');
683 if ( ( ($query->param('koha_login_context')) && ($query->param('userid') ne $session->param('id')) )
684 || ( $cas && $query->param('ticket') ) ) {
685 #if a user enters an id ne to the id in the current session, we need to log them in...
686 #first we need to clear the anonymous session...
687 $debug and warn "query id = " . $query->param('userid') . " but session id = " . $session->param('id');
690 C4::Context->_unset_userenv($sessionID);
695 # voluntary logout the user
698 C4::Context->_unset_userenv($sessionID);
699 #_session_log(sprintf "%20s from %16s logged out at %30s (manually).\n", $userid,$ip,(strftime "%c",localtime));
703 if ($cas and $caslogout) {
707 elsif ( $lasttime < time() - $timeout ) {
709 $info{'timed_out'} = 1;
711 C4::Context->_unset_userenv($sessionID);
712 #_session_log(sprintf "%20s from %16s logged out at %30s (inactivity).\n", $userid,$ip,(strftime "%c",localtime));
716 elsif ( $ip ne $ENV{'REMOTE_ADDR'} ) {
717 # Different ip than originally logged in from
718 $info{'oldip'} = $ip;
719 $info{'newip'} = $ENV{'REMOTE_ADDR'};
720 $info{'different_ip'} = 1;
722 C4::Context->_unset_userenv($sessionID);
723 #_session_log(sprintf "%20s from %16s logged out at %30s (ip changed to %16s).\n", $userid,$ip,(strftime "%c",localtime), $info{'newip'});
728 $cookie = $query->cookie( CGISESSID => $session->id );
729 $session->param('lasttime',time());
730 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...
731 $flags = haspermission($userid, $flagsrequired);
735 $info{'nopermission'} = 1;
740 unless ($userid || $sessionID) {
741 #we initiate a session prior to checking for a username to allow for anonymous sessions...
742 my $session = get_session("") or die "Auth ERROR: Cannot get_session()";
743 my $sessionID = $session->id;
744 C4::Context->_new_userenv($sessionID);
745 $cookie = $query->cookie(CGISESSID => $sessionID);
746 $userid = $query->param('userid');
747 if (($cas && $query->param('ticket')) || $userid) {
748 my $password = $query->param('password');
749 my ($return, $cardnumber);
750 if ($cas && $query->param('ticket')) {
752 ( $return, $cardnumber, $retuserid ) = checkpw( $dbh, $userid, $password, $query );
753 $userid = $retuserid;
754 $info{'invalidCasLogin'} = 1 unless ($return);
757 ( $return, $cardnumber, $retuserid ) = checkpw( $dbh, $userid, $password, $query );
758 $userid = $retuserid if ($retuserid ne '');
761 #_session_log(sprintf "%20s from %16s logged in at %30s.\n", $userid,$ENV{'REMOTE_ADDR'},(strftime '%c', localtime));
762 if ( $flags = haspermission( $userid, $flagsrequired ) ) {
766 $info{'nopermission'} = 1;
767 C4::Context->_unset_userenv($sessionID);
770 my ($borrowernumber, $firstname, $surname, $userflags,
771 $branchcode, $branchname, $branchprinter, $emailaddress);
773 if ( $return == 1 ) {
775 SELECT borrowernumber, firstname, surname, flags, borrowers.branchcode,
776 branches.branchname as branchname,
777 branches.branchprinter as branchprinter,
780 LEFT JOIN branches on borrowers.branchcode=branches.branchcode
782 my $sth = $dbh->prepare("$select where userid=?");
783 $sth->execute($userid);
784 unless ($sth->rows) {
785 $debug and print STDERR "AUTH_1: no rows for userid='$userid'\n";
786 $sth = $dbh->prepare("$select where cardnumber=?");
787 $sth->execute($cardnumber);
789 unless ($sth->rows) {
790 $debug and print STDERR "AUTH_2a: no rows for cardnumber='$cardnumber'\n";
791 $sth->execute($userid);
792 unless ($sth->rows) {
793 $debug and print STDERR "AUTH_2b: no rows for userid='$userid' AS cardnumber\n";
798 ($borrowernumber, $firstname, $surname, $userflags,
799 $branchcode, $branchname, $branchprinter, $emailaddress) = $sth->fetchrow;
800 $debug and print STDERR "AUTH_3 results: " .
801 "$cardnumber,$borrowernumber,$userid,$firstname,$surname,$userflags,$branchcode,$emailaddress\n";
803 print STDERR "AUTH_3: no results for userid='$userid', cardnumber='$cardnumber'.\n";
806 # launch a sequence to check if we have a ip for the branch, i
807 # if we have one we replace the branchcode of the userenv by the branch bound in the ip.
809 my $ip = $ENV{'REMOTE_ADDR'};
810 # if they specify at login, use that
811 if ($query->param('branch')) {
812 $branchcode = $query->param('branch');
813 $branchname = GetBranchName($branchcode);
815 my $branches = GetBranches();
816 if (C4::Context->boolean_preference('IndependantBranches') && C4::Context->boolean_preference('Autolocation')){
817 # we have to check they are coming from the right ip range
818 my $domain = $branches->{$branchcode}->{'branchip'};
819 if ($ip !~ /^$domain/){
821 $info{'wrongip'} = 1;
826 foreach my $br ( keys %$branches ) {
827 # now we work with the treatment of ip
828 my $domain = $branches->{$br}->{'branchip'};
829 if ( $domain && $ip =~ /^$domain/ ) {
830 $branchcode = $branches->{$br}->{'branchcode'};
832 # new op dev : add the branchprinter and branchname in the cookie
833 $branchprinter = $branches->{$br}->{'branchprinter'};
834 $branchname = $branches->{$br}->{'branchname'};
837 $session->param('number',$borrowernumber);
838 $session->param('id',$userid);
839 $session->param('cardnumber',$cardnumber);
840 $session->param('firstname',$firstname);
841 $session->param('surname',$surname);
842 $session->param('branch',$branchcode);
843 $session->param('branchname',$branchname);
844 $session->param('flags',$userflags);
845 $session->param('emailaddress',$emailaddress);
846 $session->param('ip',$session->remote_addr());
847 $session->param('lasttime',time());
848 $debug and printf STDERR "AUTH_4: (%s)\t%s %s - %s\n", map {$session->param($_)} qw(cardnumber firstname surname branch) ;
850 elsif ( $return == 2 ) {
851 #We suppose the user is the superlibrarian
853 $session->param('number',0);
854 $session->param('id',C4::Context->config('user'));
855 $session->param('cardnumber',C4::Context->config('user'));
856 $session->param('firstname',C4::Context->config('user'));
857 $session->param('surname',C4::Context->config('user'));
858 $session->param('branch','NO_LIBRARY_SET');
859 $session->param('branchname','NO_LIBRARY_SET');
860 $session->param('flags',1);
861 $session->param('emailaddress', C4::Context->preference('KohaAdminEmailAddress'));
862 $session->param('ip',$session->remote_addr());
863 $session->param('lasttime',time());
865 C4::Context::set_userenv(
866 $session->param('number'), $session->param('id'),
867 $session->param('cardnumber'), $session->param('firstname'),
868 $session->param('surname'), $session->param('branch'),
869 $session->param('branchname'), $session->param('flags'),
870 $session->param('emailaddress'), $session->param('branchprinter')
873 # Grab borrower's shelves and public shelves and add them to the session
874 # $row_count determines how many records are returned from the db query
875 # and the number of lists to be displayed of each type in the 'Lists' button drop down
876 my $row_count = 10; # FIXME:This probably should be a syspref
877 my ($total, $totshelves, $barshelves, $pubshelves);
878 ($barshelves, $totshelves) = C4::VirtualShelves::GetRecentShelves(1, $row_count, $borrowernumber);
879 $total->{'bartotal'} = $totshelves;
880 ($pubshelves, $totshelves) = C4::VirtualShelves::GetRecentShelves(2, $row_count, undef);
881 $total->{'pubtotal'} = $totshelves;
882 $session->param('barshelves', $barshelves);
883 $session->param('pubshelves', $pubshelves);
884 $session->param('totshelves', $total);
886 C4::Context::set_shelves_userenv('bar',$barshelves);
887 C4::Context::set_shelves_userenv('pub',$pubshelves);
888 C4::Context::set_shelves_userenv('tot',$total);
892 $info{'invalid_username_or_password'} = 1;
893 C4::Context->_unset_userenv($sessionID);
896 } # END if ( $userid = $query->param('userid') )
897 elsif ($type eq "opac") {
898 # if we are here this is an anonymous session; add public lists to it and a few other items...
899 # anonymous sessions are created only for the OPAC
900 $debug and warn "Initiating an anonymous session...";
902 # Grab the public shelves and add to the session...
903 my $row_count = 20; # FIXME:This probably should be a syspref
904 my ($total, $totshelves, $pubshelves);
905 ($pubshelves, $totshelves) = C4::VirtualShelves::GetRecentShelves(2, $row_count, undef);
906 $total->{'pubtotal'} = $totshelves;
907 $session->param('pubshelves', $pubshelves);
908 $session->param('totshelves', $total);
909 C4::Context::set_shelves_userenv('pub',$pubshelves);
910 C4::Context::set_shelves_userenv('tot',$total);
912 # setting a couple of other session vars...
913 $session->param('ip',$session->remote_addr());
914 $session->param('lasttime',time());
915 $session->param('sessiontype','anon');
917 } # END unless ($userid)
918 my $insecure = C4::Context->boolean_preference('insecure');
920 # finished authentification, now respond
921 if ( $loggedin || $authnotrequired || ( defined($insecure) && $insecure ) )
925 $cookie = $query->cookie( CGISESSID => '' );
927 return ( $userid, $cookie, $sessionID, $flags );
932 # AUTH rejected, show the login/password template, after checking the DB.
936 # get the inputs from the incoming query
938 foreach my $name ( param $query) {
939 (next) if ( $name eq 'userid' || $name eq 'password' || $name eq 'ticket' );
940 my $value = $query->param($name);
941 push @inputs, { name => $name, value => $value };
943 # get the branchloop, which we need for authentication
944 my $branches = GetBranches();
946 for my $branch_hash (sort keys %$branches) {
947 push @branch_loop, {branchcode => "$branch_hash", branchname => $branches->{$branch_hash}->{'branchname'}, };
950 my $template_name = ( $type eq 'opac' ) ? 'opac-auth.tmpl' : 'auth.tmpl';
951 my $template = C4::Templates::gettemplate( $template_name, $type, $query );
952 $template->param(branchloop => \@branch_loop,);
953 my $checkstyle = C4::Context->preference("opaccolorstylesheet");
954 if ($checkstyle =~ /\//)
956 $template->param( opacexternalsheet => $checkstyle);
959 my $opaccolorstylesheet = C4::Context->preference("opaccolorstylesheet");
960 $template->param( opaccolorstylesheet => $opaccolorstylesheet);
965 casAuthentication => C4::Context->preference("casAuthentication"),
966 suggestion => C4::Context->preference("suggestion"),
967 virtualshelves => C4::Context->preference("virtualshelves"),
968 LibraryName => C4::Context->preference("LibraryName"),
969 opacuserlogin => C4::Context->preference("opacuserlogin"),
970 OpacNav => C4::Context->preference("OpacNav"),
971 OpacNavBottom => C4::Context->preference("OpacNavBottom"),
972 opaccredits => C4::Context->preference("opaccredits"),
973 OpacFavicon => C4::Context->preference("OpacFavicon"),
974 opacreadinghistory => C4::Context->preference("opacreadinghistory"),
975 opacsmallimage => C4::Context->preference("opacsmallimage"),
976 opaclayoutstylesheet => C4::Context->preference("opaclayoutstylesheet"),
977 opaclanguagesdisplay => C4::Context->preference("opaclanguagesdisplay"),
978 opacuserjs => C4::Context->preference("opacuserjs"),
979 opacbookbag => "" . C4::Context->preference("opacbookbag"),
980 OpacCloud => C4::Context->preference("OpacCloud"),
981 OpacTopissue => C4::Context->preference("OpacTopissue"),
982 OpacAuthorities => C4::Context->preference("OpacAuthorities"),
983 OpacBrowser => C4::Context->preference("OpacBrowser"),
984 opacheader => C4::Context->preference("opacheader"),
985 TagsEnabled => C4::Context->preference("TagsEnabled"),
986 OPACUserCSS => C4::Context->preference("OPACUserCSS"),
987 opacstylesheet => C4::Context->preference("opacstylesheet"),
988 intranetcolorstylesheet =>
989 C4::Context->preference("intranetcolorstylesheet"),
990 intranetstylesheet => C4::Context->preference("intranetstylesheet"),
991 intranetbookbag => C4::Context->preference("intranetbookbag"),
992 IntranetNav => C4::Context->preference("IntranetNav"),
993 intranetuserjs => C4::Context->preference("intranetuserjs"),
994 IndependantBranches=> C4::Context->preference("IndependantBranches"),
995 AutoLocation => C4::Context->preference("AutoLocation"),
996 wrongip => $info{'wrongip'},
999 $template->param( OpacPublic => C4::Context->preference("OpacPublic"));
1000 $template->param( loginprompt => 1 ) unless $info{'nopermission'};
1004 # Is authentication against multiple CAS servers enabled?
1005 if (C4::Auth_with_cas::multipleAuth && !$casparam) {
1006 my $casservers = C4::Auth_with_cas::getMultipleAuth();
1008 foreach my $key (keys %$casservers) {
1009 push @tmplservers, {name => $key, value => login_cas_url($query, $key) . "?cas=$key" };
1011 #warn Data::Dumper::Dumper(\@tmplservers);
1013 casServersLoop => \@tmplservers
1017 casServerUrl => login_cas_url($query),
1022 invalidCasLogin => $info{'invalidCasLogin'}
1026 my $self_url = $query->url( -absolute => 1 );
1029 LibraryName => C4::Context->preference("LibraryName"),
1031 $template->param( %info );
1032 # $cookie = $query->cookie(CGISESSID => $session->id
1034 print $query->header(
1035 -type => 'text/html',
1036 -charset => 'utf-8',
1043 =head2 check_api_auth
1045 ($status, $cookie, $sessionId) = check_api_auth($query, $userflags);
1047 Given a CGI query containing the parameters 'userid' and 'password' and/or a session
1048 cookie, determine if the user has the privileges specified by C<$userflags>.
1050 C<check_api_auth> is is meant for authenticating users of web services, and
1051 consequently will always return and will not attempt to redirect the user
1054 If a valid session cookie is already present, check_api_auth will return a status
1055 of "ok", the cookie, and the Koha session ID.
1057 If no session cookie is present, check_api_auth will check the 'userid' and 'password
1058 parameters and create a session cookie and Koha session if the supplied credentials
1061 Possible return values in C<$status> are:
1065 =item "ok" -- user authenticated; C<$cookie> and C<$sessionid> have valid values.
1067 =item "failed" -- credentials are not correct; C<$cookie> and C<$sessionid> are undef
1069 =item "maintenance" -- DB is in maintenance mode; no login possible at the moment
1071 =item "expired -- session cookie has expired; API user should resubmit userid and password
1077 sub check_api_auth {
1079 my $flagsrequired = shift;
1081 my $dbh = C4::Context->dbh;
1082 my $timeout = C4::Context->preference('timeout');
1083 $timeout = 600 unless $timeout;
1085 unless (C4::Context->preference('Version')) {
1086 # database has not been installed yet
1087 return ("maintenance", undef, undef);
1089 my $kohaversion=C4::Context::KOHAVERSION;
1090 $kohaversion =~ s/(.*\..*)\.(.*)\.(.*)/$1$2$3/;
1091 if (C4::Context->preference('Version') < $kohaversion) {
1092 # database in need of version update; assume that
1093 # no API should be called while databsae is in
1095 return ("maintenance", undef, undef);
1098 # FIXME -- most of what follows is a copy-and-paste
1099 # of code from checkauth. There is an obvious need
1100 # for refactoring to separate the various parts of
1101 # the authentication code, but as of 2007-11-19 this
1102 # is deferred so as to not introduce bugs into the
1103 # regular authentication code for Koha 3.0.
1105 # see if we have a valid session cookie already
1106 # however, if a userid parameter is present (i.e., from
1107 # a form submission, assume that any current cookie
1109 my $sessionID = undef;
1110 unless ($query->param('userid')) {
1111 $sessionID = $query->cookie("CGISESSID");
1113 if ($sessionID && not ($cas && $query->param('PT')) ) {
1114 my $session = get_session($sessionID);
1115 C4::Context->_new_userenv($sessionID);
1117 C4::Context::set_userenv(
1118 $session->param('number'), $session->param('id'),
1119 $session->param('cardnumber'), $session->param('firstname'),
1120 $session->param('surname'), $session->param('branch'),
1121 $session->param('branchname'), $session->param('flags'),
1122 $session->param('emailaddress'), $session->param('branchprinter')
1125 my $ip = $session->param('ip');
1126 my $lasttime = $session->param('lasttime');
1127 my $userid = $session->param('id');
1128 if ( $lasttime < time() - $timeout ) {
1131 C4::Context->_unset_userenv($sessionID);
1134 return ("expired", undef, undef);
1135 } elsif ( $ip ne $ENV{'REMOTE_ADDR'} ) {
1136 # IP address changed
1138 C4::Context->_unset_userenv($sessionID);
1141 return ("expired", undef, undef);
1143 my $cookie = $query->cookie( CGISESSID => $session->id );
1144 $session->param('lasttime',time());
1145 my $flags = haspermission($userid, $flagsrequired);
1147 return ("ok", $cookie, $sessionID);
1150 C4::Context->_unset_userenv($sessionID);
1153 return ("failed", undef, undef);
1157 return ("expired", undef, undef);
1161 my $userid = $query->param('userid');
1162 my $password = $query->param('password');
1163 my ($return, $cardnumber);
1166 if ($cas && $query->param('PT')) {
1168 $debug and print STDERR "## check_api_auth - checking CAS\n";
1169 # In case of a CAS authentication, we use the ticket instead of the password
1170 my $PT = $query->param('PT');
1171 ($return,$cardnumber,$userid) = check_api_auth_cas($dbh, $PT, $query); # EXTERNAL AUTH
1173 # User / password auth
1174 unless ($userid and $password) {
1175 # caller did something wrong, fail the authenticateion
1176 return ("failed", undef, undef);
1178 ( $return, $cardnumber ) = checkpw( $dbh, $userid, $password, $query );
1181 if ($return and haspermission( $userid, $flagsrequired)) {
1182 my $session = get_session("");
1183 return ("failed", undef, undef) unless $session;
1185 my $sessionID = $session->id;
1186 C4::Context->_new_userenv($sessionID);
1187 my $cookie = $query->cookie(CGISESSID => $sessionID);
1188 if ( $return == 1 ) {
1190 $borrowernumber, $firstname, $surname,
1191 $userflags, $branchcode, $branchname,
1192 $branchprinter, $emailaddress
1196 "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=?"
1198 $sth->execute($userid);
1200 $borrowernumber, $firstname, $surname,
1201 $userflags, $branchcode, $branchname,
1202 $branchprinter, $emailaddress
1203 ) = $sth->fetchrow if ( $sth->rows );
1205 unless ($sth->rows ) {
1206 my $sth = $dbh->prepare(
1207 "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=?"
1209 $sth->execute($cardnumber);
1211 $borrowernumber, $firstname, $surname,
1212 $userflags, $branchcode, $branchname,
1213 $branchprinter, $emailaddress
1214 ) = $sth->fetchrow if ( $sth->rows );
1216 unless ( $sth->rows ) {
1217 $sth->execute($userid);
1219 $borrowernumber, $firstname, $surname, $userflags,
1220 $branchcode, $branchname, $branchprinter, $emailaddress
1221 ) = $sth->fetchrow if ( $sth->rows );
1225 my $ip = $ENV{'REMOTE_ADDR'};
1226 # if they specify at login, use that
1227 if ($query->param('branch')) {
1228 $branchcode = $query->param('branch');
1229 $branchname = GetBranchName($branchcode);
1231 my $branches = GetBranches();
1233 foreach my $br ( keys %$branches ) {
1234 # now we work with the treatment of ip
1235 my $domain = $branches->{$br}->{'branchip'};
1236 if ( $domain && $ip =~ /^$domain/ ) {
1237 $branchcode = $branches->{$br}->{'branchcode'};
1239 # new op dev : add the branchprinter and branchname in the cookie
1240 $branchprinter = $branches->{$br}->{'branchprinter'};
1241 $branchname = $branches->{$br}->{'branchname'};
1244 $session->param('number',$borrowernumber);
1245 $session->param('id',$userid);
1246 $session->param('cardnumber',$cardnumber);
1247 $session->param('firstname',$firstname);
1248 $session->param('surname',$surname);
1249 $session->param('branch',$branchcode);
1250 $session->param('branchname',$branchname);
1251 $session->param('flags',$userflags);
1252 $session->param('emailaddress',$emailaddress);
1253 $session->param('ip',$session->remote_addr());
1254 $session->param('lasttime',time());
1255 } elsif ( $return == 2 ) {
1256 #We suppose the user is the superlibrarian
1257 $session->param('number',0);
1258 $session->param('id',C4::Context->config('user'));
1259 $session->param('cardnumber',C4::Context->config('user'));
1260 $session->param('firstname',C4::Context->config('user'));
1261 $session->param('surname',C4::Context->config('user'));
1262 $session->param('branch','NO_LIBRARY_SET');
1263 $session->param('branchname','NO_LIBRARY_SET');
1264 $session->param('flags',1);
1265 $session->param('emailaddress', C4::Context->preference('KohaAdminEmailAddress'));
1266 $session->param('ip',$session->remote_addr());
1267 $session->param('lasttime',time());
1269 C4::Context::set_userenv(
1270 $session->param('number'), $session->param('id'),
1271 $session->param('cardnumber'), $session->param('firstname'),
1272 $session->param('surname'), $session->param('branch'),
1273 $session->param('branchname'), $session->param('flags'),
1274 $session->param('emailaddress'), $session->param('branchprinter')
1276 return ("ok", $cookie, $sessionID);
1278 return ("failed", undef, undef);
1283 =head2 check_cookie_auth
1285 ($status, $sessionId) = check_api_auth($cookie, $userflags);
1287 Given a CGISESSID cookie set during a previous login to Koha, determine
1288 if the user has the privileges specified by C<$userflags>.
1290 C<check_cookie_auth> is meant for authenticating special services
1291 such as tools/upload-file.pl that are invoked by other pages that
1292 have been authenticated in the usual way.
1294 Possible return values in C<$status> are:
1298 =item "ok" -- user authenticated; C<$sessionID> have valid values.
1300 =item "failed" -- credentials are not correct; C<$sessionid> are undef
1302 =item "maintenance" -- DB is in maintenance mode; no login possible at the moment
1304 =item "expired -- session cookie has expired; API user should resubmit userid and password
1310 sub check_cookie_auth {
1312 my $flagsrequired = shift;
1314 my $dbh = C4::Context->dbh;
1315 my $timeout = C4::Context->preference('timeout');
1316 $timeout = 600 unless $timeout;
1318 unless (C4::Context->preference('Version')) {
1319 # database has not been installed yet
1320 return ("maintenance", undef);
1322 my $kohaversion=C4::Context::KOHAVERSION;
1323 $kohaversion =~ s/(.*\..*)\.(.*)\.(.*)/$1$2$3/;
1324 if (C4::Context->preference('Version') < $kohaversion) {
1325 # database in need of version update; assume that
1326 # no API should be called while databsae is in
1328 return ("maintenance", undef);
1331 # FIXME -- most of what follows is a copy-and-paste
1332 # of code from checkauth. There is an obvious need
1333 # for refactoring to separate the various parts of
1334 # the authentication code, but as of 2007-11-23 this
1335 # is deferred so as to not introduce bugs into the
1336 # regular authentication code for Koha 3.0.
1338 # see if we have a valid session cookie already
1339 # however, if a userid parameter is present (i.e., from
1340 # a form submission, assume that any current cookie
1342 unless (defined $cookie and $cookie) {
1343 return ("failed", undef);
1345 my $sessionID = $cookie;
1346 my $session = get_session($sessionID);
1347 C4::Context->_new_userenv($sessionID);
1349 C4::Context::set_userenv(
1350 $session->param('number'), $session->param('id'),
1351 $session->param('cardnumber'), $session->param('firstname'),
1352 $session->param('surname'), $session->param('branch'),
1353 $session->param('branchname'), $session->param('flags'),
1354 $session->param('emailaddress'), $session->param('branchprinter')
1357 my $ip = $session->param('ip');
1358 my $lasttime = $session->param('lasttime');
1359 my $userid = $session->param('id');
1360 if ( $lasttime < time() - $timeout ) {
1363 C4::Context->_unset_userenv($sessionID);
1366 return ("expired", undef);
1367 } elsif ( $ip ne $ENV{'REMOTE_ADDR'} ) {
1368 # IP address changed
1370 C4::Context->_unset_userenv($sessionID);
1373 return ("expired", undef);
1375 $session->param('lasttime',time());
1376 my $flags = haspermission($userid, $flagsrequired);
1378 return ("ok", $sessionID);
1381 C4::Context->_unset_userenv($sessionID);
1384 return ("failed", undef);
1388 return ("expired", undef);
1395 my $session = get_session($sessionID);
1397 Given a session ID, retrieve the CGI::Session object used to store
1398 the session's state. The session object can be used to store
1399 data that needs to be accessed by different scripts during a
1402 If the C<$sessionID> parameter is an empty string, a new session
1408 my $sessionID = shift;
1409 my $storage_method = C4::Context->preference('SessionStorage');
1410 my $dbh = C4::Context->dbh;
1412 if ($storage_method eq 'mysql'){
1413 $session = new CGI::Session("driver:MySQL;serializer:yaml;id:md5", $sessionID, {Handle=>$dbh});
1415 elsif ($storage_method eq 'Pg') {
1416 $session = new CGI::Session("driver:PostgreSQL;serializer:yaml;id:md5", $sessionID, {Handle=>$dbh});
1418 elsif ($storage_method eq 'memcached' && C4::Context->ismemcached){
1419 $session = new CGI::Session("driver:memcached;serializer:yaml;id:md5", $sessionID, { Memcached => C4::Context->memcached } );
1422 # catch all defaults to tmp should work on all systems
1423 $session = new CGI::Session("driver:File;serializer:yaml;id:md5", $sessionID, {Directory=>'/tmp'});
1430 my ( $dbh, $userid, $password, $query ) = @_;
1432 $debug and print "## checkpw - checking LDAP\n";
1433 my ($retval,$retcard,$retuserid) = checkpw_ldap(@_); # EXTERNAL AUTH
1434 ($retval) and return ($retval,$retcard,$retuserid);
1437 if ($cas && $query && $query->param('ticket')) {
1438 $debug and print STDERR "## checkpw - checking CAS\n";
1439 # In case of a CAS authentication, we use the ticket instead of the password
1440 my $ticket = $query->param('ticket');
1441 my ($retval,$retcard,$retuserid) = checkpw_cas($dbh, $ticket, $query); # EXTERNAL AUTH
1442 ($retval) and return ($retval,$retcard,$retuserid);
1449 "select password,cardnumber,borrowernumber,userid,firstname,surname,branchcode,flags from borrowers where userid=?"
1451 $sth->execute($userid);
1453 my ( $md5password, $cardnumber, $borrowernumber, $userid, $firstname,
1454 $surname, $branchcode, $flags )
1456 if ( md5_base64($password) eq $md5password and $md5password ne "!") {
1458 C4::Context->set_userenv( "$borrowernumber", $userid, $cardnumber,
1459 $firstname, $surname, $branchcode, $flags );
1460 return 1, $cardnumber, $userid;
1465 "select password,cardnumber,borrowernumber,userid, firstname,surname,branchcode,flags from borrowers where cardnumber=?"
1467 $sth->execute($userid);
1469 my ( $md5password, $cardnumber, $borrowernumber, $userid, $firstname,
1470 $surname, $branchcode, $flags )
1472 if ( md5_base64($password) eq $md5password ) {
1474 C4::Context->set_userenv( $borrowernumber, $userid, $cardnumber,
1475 $firstname, $surname, $branchcode, $flags );
1476 return 1, $cardnumber, $userid;
1479 if ( $userid && $userid eq C4::Context->config('user')
1480 && "$password" eq C4::Context->config('pass') )
1483 # Koha superuser account
1484 # C4::Context->set_userenv(0,0,C4::Context->config('user'),C4::Context->config('user'),C4::Context->config('user'),"",1);
1487 if ( $userid && $userid eq 'demo'
1488 && "$password" eq 'demo'
1489 && C4::Context->config('demo') )
1492 # DEMO => the demo user is allowed to do everything (if demo set to 1 in koha.conf
1493 # some features won't be effective : modify systempref, modify MARC structure,
1501 my $authflags = getuserflags($flags, $userid, [$dbh]);
1503 Translates integer flags into permissions strings hash.
1505 C<$flags> is the integer userflags value ( borrowers.userflags )
1506 C<$userid> is the members.userid, used for building subpermissions
1507 C<$authflags> is a hashref of permissions
1514 my $dbh = @_ ? shift : C4::Context->dbh;
1516 $flags = 0 unless $flags;
1517 my $sth = $dbh->prepare("SELECT bit, flag, defaulton FROM userflags");
1520 while ( my ( $bit, $flag, $defaulton ) = $sth->fetchrow ) {
1521 if ( ( $flags & ( 2**$bit ) ) || $defaulton ) {
1522 $userflags->{$flag} = 1;
1525 $userflags->{$flag} = 0;
1529 # get subpermissions and merge with top-level permissions
1530 my $user_subperms = get_user_subpermissions($userid);
1531 foreach my $module (keys %$user_subperms) {
1532 next if $userflags->{$module} == 1; # user already has permission for everything in this module
1533 $userflags->{$module} = $user_subperms->{$module};
1539 =head2 get_user_subpermissions
1541 $user_perm_hashref = get_user_subpermissions($userid);
1543 Given the userid (note, not the borrowernumber) of a staff user,
1544 return a hashref of hashrefs of the specific subpermissions
1545 accorded to the user. An example return is
1549 export_catalog => 1,
1550 import_patrons => 1,
1554 The top-level hash-key is a module or function code from
1555 userflags.flag, while the second-level key is a code
1558 The results of this function do not give a complete picture
1559 of the functions that a staff user can access; it is also
1560 necessary to check borrowers.flags.
1564 sub get_user_subpermissions {
1567 my $dbh = C4::Context->dbh;
1568 my $sth = $dbh->prepare("SELECT flag, user_permissions.code
1569 FROM user_permissions
1570 JOIN permissions USING (module_bit, code)
1571 JOIN userflags ON (module_bit = bit)
1572 JOIN borrowers USING (borrowernumber)
1574 $sth->execute($userid);
1576 my $user_perms = {};
1577 while (my $perm = $sth->fetchrow_hashref) {
1578 $user_perms->{$perm->{'flag'}}->{$perm->{'code'}} = 1;
1583 =head2 get_all_subpermissions
1585 my $perm_hashref = get_all_subpermissions();
1587 Returns a hashref of hashrefs defining all specific
1588 permissions currently defined. The return value
1589 has the same structure as that of C<get_user_subpermissions>,
1590 except that the innermost hash value is the description
1591 of the subpermission.
1595 sub get_all_subpermissions {
1596 my $dbh = C4::Context->dbh;
1597 my $sth = $dbh->prepare("SELECT flag, code, description
1599 JOIN userflags ON (module_bit = bit)");
1603 while (my $perm = $sth->fetchrow_hashref) {
1604 $all_perms->{$perm->{'flag'}}->{$perm->{'code'}} = $perm->{'description'};
1609 =head2 haspermission
1611 $flags = ($userid, $flagsrequired);
1613 C<$userid> the userid of the member
1614 C<$flags> is a hashref of required flags like C<$borrower-<{authflags}>
1616 Returns member's flags or 0 if a permission is not met.
1621 my ($userid, $flagsrequired) = @_;
1622 my $sth = C4::Context->dbh->prepare("SELECT flags FROM borrowers WHERE userid=?");
1623 $sth->execute($userid);
1624 my $flags = getuserflags($sth->fetchrow(), $userid);
1625 if ( $userid eq C4::Context->config('user') ) {
1626 # Super User Account from /etc/koha.conf
1627 $flags->{'superlibrarian'} = 1;
1629 elsif ( $userid eq 'demo' && C4::Context->config('demo') ) {
1630 # Demo user that can do "anything" (demo=1 in /etc/koha.conf)
1631 $flags->{'superlibrarian'} = 1;
1634 return $flags if $flags->{superlibrarian};
1636 foreach my $module ( keys %$flagsrequired ) {
1637 my $subperm = $flagsrequired->{$module};
1638 if ($subperm eq '*') {
1639 return 0 unless ( $flags->{$module} == 1 or ref($flags->{$module}) );
1641 return 0 unless ( $flags->{$module} == 1 or
1642 ( ref($flags->{$module}) and
1643 exists $flags->{$module}->{$subperm} and
1644 $flags->{$module}->{$subperm} == 1
1650 #FIXME - This fcn should return the failed permission so a suitable error msg can be delivered.
1654 sub getborrowernumber {
1656 my $userenv = C4::Context->userenv;
1657 if ( defined( $userenv ) && ref( $userenv ) eq 'HASH' && $userenv->{number} ) {
1658 return $userenv->{number};
1660 my $dbh = C4::Context->dbh;
1661 for my $field ( 'userid', 'cardnumber' ) {
1663 $dbh->prepare("select borrowernumber from borrowers where $field=?");
1664 $sth->execute($userid);
1666 my ($bnumber) = $sth->fetchrow;
1673 END { } # module clean-up code here (global destructor)