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.
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' }
44 $VERSION = 3.07.00.049; # 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(?))
133 sub get_template_and_user {
136 C4::Templates::gettemplate( $in->{'template_name'}, $in->{'type'}, $in->{'query'} );
137 my ( $user, $cookie, $sessionID, $flags );
138 if ( $in->{'template_name'} !~m/maintenance/ ) {
139 ( $user, $cookie, $sessionID, $flags ) = checkauth(
141 $in->{'authnotrequired'},
142 $in->{'flagsrequired'},
148 my $insecure = C4::Context->preference('insecure');
149 if ($user or $insecure) {
151 # It's possible for $user to be the borrowernumber if they don't have a
152 # userid defined (and are logging in through some other method, such
153 # as SSL certs against an email address)
154 $borrowernumber = getborrowernumber($user) if defined($user);
155 if (!defined($borrowernumber) && defined($user)) {
156 my $borrower = C4::Members::GetMember(borrowernumber => $user);
158 $borrowernumber = $user;
159 # A bit of a hack, but I don't know there's a nicer way
161 $user = $borrower->{firstname} . ' ' . $borrower->{surname};
166 $template->param( loggedinusername => $user );
167 $template->param( sessionID => $sessionID );
169 my ($total, $pubshelves, $barshelves) = C4::VirtualShelves::GetSomeShelfNames($borrowernumber, 'MASTHEAD');
171 pubshelves => $total->{pubtotal},
172 pubshelvesloop => $pubshelves,
173 barshelves => $total->{bartotal},
174 barshelvesloop => $barshelves,
177 my ( $borr ) = C4::Members::GetMemberDetails( $borrowernumber );
180 $template->param( "USER_INFO" => \@bordat );
182 my $all_perms = get_all_subpermissions();
184 my @flagroots = qw(circulate catalogue parameters borrowers permissions reserveforothers borrow
185 editcatalogue updatecharges management tools editauthorities serials reports acquisition);
186 # We are going to use the $flags returned by checkauth
187 # to create the template's parameters that will indicate
188 # which menus the user can access.
189 if (( $flags && $flags->{superlibrarian}==1) or $insecure==1) {
190 $template->param( CAN_user_circulate => 1 );
191 $template->param( CAN_user_catalogue => 1 );
192 $template->param( CAN_user_parameters => 1 );
193 $template->param( CAN_user_borrowers => 1 );
194 $template->param( CAN_user_permissions => 1 );
195 $template->param( CAN_user_reserveforothers => 1 );
196 $template->param( CAN_user_borrow => 1 );
197 $template->param( CAN_user_editcatalogue => 1 );
198 $template->param( CAN_user_updatecharges => 1 );
199 $template->param( CAN_user_acquisition => 1 );
200 $template->param( CAN_user_management => 1 );
201 $template->param( CAN_user_tools => 1 );
202 $template->param( CAN_user_editauthorities => 1 );
203 $template->param( CAN_user_serials => 1 );
204 $template->param( CAN_user_reports => 1 );
205 $template->param( CAN_user_staffaccess => 1 );
206 foreach my $module (keys %$all_perms) {
207 foreach my $subperm (keys %{ $all_perms->{$module} }) {
208 $template->param( "CAN_user_${module}_${subperm}" => 1 );
214 foreach my $module (keys %$all_perms) {
215 if ( $flags->{$module} == 1) {
216 foreach my $subperm (keys %{ $all_perms->{$module} }) {
217 $template->param( "CAN_user_${module}_${subperm}" => 1 );
219 } elsif ( ref($flags->{$module}) ) {
220 foreach my $subperm (keys %{ $flags->{$module} } ) {
221 $template->param( "CAN_user_${module}_${subperm}" => 1 );
228 foreach my $module (keys %$flags) {
229 if ( $flags->{$module} == 1 or ref($flags->{$module}) ) {
230 $template->param( "CAN_user_$module" => 1 );
231 if ($module eq "parameters") {
232 $template->param( CAN_user_management => 1 );
237 # Logged-in opac search history
238 # If the requested template is an opac one and opac search history is enabled
239 if ($in->{type} eq 'opac' && C4::Context->preference('EnableOpacSearchHistory')) {
240 my $dbh = C4::Context->dbh;
241 my $query = "SELECT COUNT(*) FROM search_history WHERE userid=?";
242 my $sth = $dbh->prepare($query);
243 $sth->execute($borrowernumber);
245 # If at least one search has already been performed
246 if ($sth->fetchrow_array > 0) {
247 # We show the link in opac
248 $template->param(ShowOpacRecentSearchLink => 1);
251 # And if there's a cookie with searches performed when the user was not logged in,
252 # we add them to the logged-in search history
253 my $searchcookie = $in->{'query'}->cookie('KohaOpacRecentSearches');
255 $searchcookie = uri_unescape($searchcookie);
256 my @recentSearches = @{thaw($searchcookie) || []};
257 if (@recentSearches) {
258 my $sth = $dbh->prepare($SEARCH_HISTORY_INSERT_SQL);
259 $sth->execute( $borrowernumber,
260 $in->{'query'}->cookie("CGISESSID"),
265 ) foreach @recentSearches;
267 # And then, delete the cookie's content
268 my $newsearchcookie = $in->{'query'}->cookie(
269 -name => 'KohaOpacRecentSearches',
270 -value => freeze([]),
274 $cookie = [$cookie, $newsearchcookie];
279 else { # if this is an anonymous session, setup to display public lists...
281 $template->param( sessionID => $sessionID );
283 my ($total, $pubshelves) = C4::VirtualShelves::GetSomeShelfNames(undef, 'MASTHEAD');
285 pubshelves => $total->{pubtotal},
286 pubshelvesloop => $pubshelves,
289 # Anonymous opac search history
290 # If opac search history is enabled and at least one search has already been performed
291 if (C4::Context->preference('EnableOpacSearchHistory')) {
292 my $searchcookie = $in->{'query'}->cookie('KohaOpacRecentSearches');
294 $searchcookie = uri_unescape($searchcookie);
295 my @recentSearches = @{thaw($searchcookie) || []};
296 # We show the link in opac
297 if (@recentSearches) {
298 $template->param(ShowOpacRecentSearchLink => 1);
303 if(C4::Context->preference('dateformat')){
304 $template->param( dateformat => C4::Context->preference('dateformat') );
305 if(C4::Context->preference('dateformat') eq "metric"){
306 $template->param(dateformat_metric => 1);
307 } elsif(C4::Context->preference('dateformat') eq "us"){
308 $template->param(dateformat_us => 1);
310 $template->param(dateformat_iso => 1);
313 $template->param(dateformat_iso => 1);
316 # these template parameters are set the same regardless of $in->{'type'}
318 "BiblioDefaultView".C4::Context->preference("BiblioDefaultView") => 1,
319 EnhancedMessagingPreferences => C4::Context->preference('EnhancedMessagingPreferences'),
320 GoogleJackets => C4::Context->preference("GoogleJackets"),
321 OpenLibraryCovers => C4::Context->preference("OpenLibraryCovers"),
322 KohaAdminEmailAddress => "" . C4::Context->preference("KohaAdminEmailAddress"),
323 LoginBranchcode => (C4::Context->userenv?C4::Context->userenv->{"branch"}:"insecure"),
324 LoginFirstname => (C4::Context->userenv?C4::Context->userenv->{"firstname"}:"Bel"),
325 LoginSurname => C4::Context->userenv?C4::Context->userenv->{"surname"}:"Inconnu",
326 TagsEnabled => C4::Context->preference("TagsEnabled"),
327 hide_marc => C4::Context->preference("hide_marc"),
328 item_level_itypes => C4::Context->preference('item-level_itypes'),
329 patronimages => C4::Context->preference("patronimages"),
330 singleBranchMode => C4::Context->preference("singleBranchMode"),
331 XSLTDetailsDisplay => C4::Context->preference("XSLTDetailsDisplay"),
332 XSLTResultsDisplay => C4::Context->preference("XSLTResultsDisplay"),
333 using_https => $in->{'query'}->https() ? 1 : 0,
334 noItemTypeImages => C4::Context->preference("noItemTypeImages"),
335 marcflavour => C4::Context->preference("marcflavour"),
338 if ( $in->{'type'} eq "intranet" ) {
340 AmazonCoverImages => C4::Context->preference("AmazonCoverImages"),
341 AutoLocation => C4::Context->preference("AutoLocation"),
342 "BiblioDefaultView".C4::Context->preference("IntranetBiblioDefaultView") => 1,
343 CalendarFirstDayOfWeek => (C4::Context->preference("CalendarFirstDayOfWeek") eq "Sunday")?0:1,
344 CircAutocompl => C4::Context->preference("CircAutocompl"),
345 FRBRizeEditions => C4::Context->preference("FRBRizeEditions"),
346 IndependantBranches => C4::Context->preference("IndependantBranches"),
347 IntranetNav => C4::Context->preference("IntranetNav"),
348 IntranetmainUserblock => C4::Context->preference("IntranetmainUserblock"),
349 LibraryName => C4::Context->preference("LibraryName"),
350 LoginBranchname => (C4::Context->userenv?C4::Context->userenv->{"branchname"}:"insecure"),
351 advancedMARCEditor => C4::Context->preference("advancedMARCEditor"),
352 canreservefromotherbranches => C4::Context->preference('canreservefromotherbranches'),
353 intranetcolorstylesheet => C4::Context->preference("intranetcolorstylesheet"),
354 IntranetFavicon => C4::Context->preference("IntranetFavicon"),
355 intranetreadinghistory => C4::Context->preference("intranetreadinghistory"),
356 intranetstylesheet => C4::Context->preference("intranetstylesheet"),
357 IntranetUserCSS => C4::Context->preference("IntranetUserCSS"),
358 intranetuserjs => C4::Context->preference("intranetuserjs"),
359 intranetbookbag => C4::Context->preference("intranetbookbag"),
360 suggestion => C4::Context->preference("suggestion"),
361 virtualshelves => C4::Context->preference("virtualshelves"),
362 StaffSerialIssueDisplayCount => C4::Context->preference("StaffSerialIssueDisplayCount"),
363 NoZebra => C4::Context->preference('NoZebra'),
364 EasyAnalyticalRecords => C4::Context->preference('EasyAnalyticalRecords'),
365 LocalCoverImages => C4::Context->preference('LocalCoverImages'),
366 OPACLocalCoverImages => C4::Context->preference('OPACLocalCoverImages'),
367 AllowMultipleCovers => C4::Context->preference('AllowMultipleCovers'),
368 EnableBorrowerFiles => C4::Context->preference('EnableBorrowerFiles'),
372 warn "template type should be OPAC, here it is=[" . $in->{'type'} . "]" unless ( $in->{'type'} eq 'opac' );
373 #TODO : replace LibraryName syspref with 'system name', and remove this html processing
374 my $LibraryNameTitle = C4::Context->preference("LibraryName");
375 $LibraryNameTitle =~ s/<(?:\/?)(?:br|p)\s*(?:\/?)>/ /sgi;
376 $LibraryNameTitle =~ s/<(?:[^<>'"]|'(?:[^']*)'|"(?:[^"]*)")*>//sg;
377 # clean up the busc param in the session if the page is not opac-detail
378 if (C4::Context->preference("OpacBrowseResults") && $in->{'template_name'} =~ /opac-(.+)\.(?:tt|tmpl)$/ && $1 !~ /^(?:MARC|ISBD)?detail$/) {
379 my $sessionSearch = get_session($sessionID || $in->{'query'}->cookie("CGISESSID"));
380 $sessionSearch->clear(["busc"]) if ($sessionSearch->param("busc"));
382 # variables passed from CGI: opac_css_override and opac_search_limits.
383 my $opac_search_limit = $ENV{'OPAC_SEARCH_LIMIT'};
384 my $opac_limit_override = $ENV{'OPAC_LIMIT_OVERRIDE'};
386 if (($opac_search_limit && $opac_search_limit =~ /branch:(\w+)/ && $opac_limit_override) || ($in->{'query'}->param('limit') && $in->{'query'}->param('limit') =~ /branch:(\w+)/)){
387 $opac_name = $1; # opac_search_limit is a branch, so we use it.
388 } elsif (C4::Context->preference("SearchMyLibraryFirst") && C4::Context->userenv && C4::Context->userenv->{'branch'}) {
389 $opac_name = C4::Context->userenv->{'branch'};
392 opaccolorstylesheet => C4::Context->preference("opaccolorstylesheet"),
393 AnonSuggestions => "" . C4::Context->preference("AnonSuggestions"),
394 AuthorisedValueImages => C4::Context->preference("AuthorisedValueImages"),
395 BranchesLoop => GetBranchesLoop($opac_name),
396 CalendarFirstDayOfWeek => (C4::Context->preference("CalendarFirstDayOfWeek") eq "Sunday")?0:1,
397 LibraryName => "" . C4::Context->preference("LibraryName"),
398 LibraryNameTitle => "" . $LibraryNameTitle,
399 LoginBranchname => C4::Context->userenv?C4::Context->userenv->{"branchname"}:"",
400 OPACAmazonCoverImages => C4::Context->preference("OPACAmazonCoverImages"),
401 OPACFRBRizeEditions => C4::Context->preference("OPACFRBRizeEditions"),
402 OpacHighlightedWords => C4::Context->preference("OpacHighlightedWords"),
403 OPACItemHolds => C4::Context->preference("OPACItemHolds"),
404 OPACShelfBrowser => "". C4::Context->preference("OPACShelfBrowser"),
405 OpacShowRecentComments => C4::Context->preference("OpacShowRecentComments"),
406 OPACURLOpenInNewWindow => "" . C4::Context->preference("OPACURLOpenInNewWindow"),
407 OPACUserCSS => "". C4::Context->preference("OPACUserCSS"),
408 OPACMobileUserCSS => "". C4::Context->preference("OPACMobileUserCSS"),
409 OPACViewOthersSuggestions => "" . C4::Context->preference("OPACViewOthersSuggestions"),
410 OpacAuthorities => C4::Context->preference("OpacAuthorities"),
411 OPACBaseURL => ($in->{'query'}->https() ? "https://" : "http://") . $ENV{'SERVER_NAME'} .
412 ($ENV{'SERVER_PORT'} eq ($in->{'query'}->https() ? "443" : "80") ? '' : ":$ENV{'SERVER_PORT'}"),
413 opac_css_override => $ENV{'OPAC_CSS_OVERRIDE'},
414 opac_search_limit => $opac_search_limit,
415 opac_limit_override => $opac_limit_override,
416 OpacBrowser => C4::Context->preference("OpacBrowser"),
417 OpacCloud => C4::Context->preference("OpacCloud"),
418 OpacKohaUrl => C4::Context->preference("OpacKohaUrl"),
419 OpacMainUserBlock => "" . C4::Context->preference("OpacMainUserBlock"),
420 OpacMainUserBlockMobile => "" . C4::Context->preference("OpacMainUserBlockMobile"),
421 OpacShowFiltersPulldownMobile => C4::Context->preference("OpacShowFiltersPulldownMobile"),
422 OpacShowLibrariesPulldownMobile => C4::Context->preference("OpacShowLibrariesPulldownMobile"),
423 OpacNav => "" . C4::Context->preference("OpacNav"),
424 OpacNavRight => "" . C4::Context->preference("OpacNavRight"),
425 OpacNavBottom => "" . C4::Context->preference("OpacNavBottom"),
426 OpacPasswordChange => C4::Context->preference("OpacPasswordChange"),
427 OPACPatronDetails => C4::Context->preference("OPACPatronDetails"),
428 OPACPrivacy => C4::Context->preference("OPACPrivacy"),
429 OPACFinesTab => C4::Context->preference("OPACFinesTab"),
430 OpacTopissue => C4::Context->preference("OpacTopissue"),
431 RequestOnOpac => C4::Context->preference("RequestOnOpac"),
432 'Version' => C4::Context->preference('Version'),
433 hidelostitems => C4::Context->preference("hidelostitems"),
434 mylibraryfirst => (C4::Context->preference("SearchMyLibraryFirst") && C4::Context->userenv) ? C4::Context->userenv->{'branch'} : '',
435 opaclayoutstylesheet => "" . C4::Context->preference("opaclayoutstylesheet"),
436 opacbookbag => "" . C4::Context->preference("opacbookbag"),
437 opaccredits => "" . C4::Context->preference("opaccredits"),
438 OpacFavicon => C4::Context->preference("OpacFavicon"),
439 opacheader => "" . C4::Context->preference("opacheader"),
440 opaclanguagesdisplay => "" . C4::Context->preference("opaclanguagesdisplay"),
441 opacreadinghistory => C4::Context->preference("opacreadinghistory"),
442 opacsmallimage => "" . C4::Context->preference("opacsmallimage"),
443 opacuserjs => C4::Context->preference("opacuserjs"),
444 opacuserlogin => "" . C4::Context->preference("opacuserlogin"),
445 reviewson => C4::Context->preference("reviewson"),
446 ShowReviewer => C4::Context->preference("ShowReviewer"),
447 ShowReviewerPhoto => C4::Context->preference("ShowReviewerPhoto"),
448 suggestion => "" . C4::Context->preference("suggestion"),
449 virtualshelves => "" . C4::Context->preference("virtualshelves"),
450 OPACSerialIssueDisplayCount => C4::Context->preference("OPACSerialIssueDisplayCount"),
451 OpacAddMastheadLibraryPulldown => C4::Context->preference("OpacAddMastheadLibraryPulldown"),
452 OPACXSLTDetailsDisplay => C4::Context->preference("OPACXSLTDetailsDisplay"),
453 OPACXSLTResultsDisplay => C4::Context->preference("OPACXSLTResultsDisplay"),
454 SyndeticsClientCode => C4::Context->preference("SyndeticsClientCode"),
455 SyndeticsEnabled => C4::Context->preference("SyndeticsEnabled"),
456 SyndeticsCoverImages => C4::Context->preference("SyndeticsCoverImages"),
457 SyndeticsTOC => C4::Context->preference("SyndeticsTOC"),
458 SyndeticsSummary => C4::Context->preference("SyndeticsSummary"),
459 SyndeticsEditions => C4::Context->preference("SyndeticsEditions"),
460 SyndeticsExcerpt => C4::Context->preference("SyndeticsExcerpt"),
461 SyndeticsReviews => C4::Context->preference("SyndeticsReviews"),
462 SyndeticsAuthorNotes => C4::Context->preference("SyndeticsAuthorNotes"),
463 SyndeticsAwards => C4::Context->preference("SyndeticsAwards"),
464 SyndeticsSeries => C4::Context->preference("SyndeticsSeries"),
465 SyndeticsCoverImageSize => C4::Context->preference("SyndeticsCoverImageSize"),
466 OPACLocalCoverImages => C4::Context->preference("OPACLocalCoverImages"),
467 PatronSelfRegistration => C4::Context->preference("PatronSelfRegistration"),
468 PatronSelfRegistrationDefaultCategory => C4::Context->preference("PatronSelfRegistrationDefaultCategory"),
471 $template->param(OpacPublic => '1') if ($user || C4::Context->preference("OpacPublic"));
473 return ( $template, $borrowernumber, $cookie, $flags);
478 ($userid, $cookie, $sessionID) = &checkauth($query, $noauth, $flagsrequired, $type);
480 Verifies that the user is authorized to run this script. If
481 the user is authorized, a (userid, cookie, session-id, flags)
482 quadruple is returned. If the user is not authorized but does
483 not have the required privilege (see $flagsrequired below), it
484 displays an error page and exits. Otherwise, it displays the
485 login page and exits.
487 Note that C<&checkauth> will return if and only if the user
488 is authorized, so it should be called early on, before any
489 unfinished operations (e.g., if you've opened a file, then
490 C<&checkauth> won't close it for you).
492 C<$query> is the CGI object for the script calling C<&checkauth>.
494 The C<$noauth> argument is optional. If it is set, then no
495 authorization is required for the script.
497 C<&checkauth> fetches user and session information from C<$query> and
498 ensures that the user is authorized to run scripts that require
501 The C<$flagsrequired> argument specifies the required privileges
502 the user must have if the username and password are correct.
503 It should be specified as a reference-to-hash; keys in the hash
504 should be the "flags" for the user, as specified in the Members
505 intranet module. Any key specified must correspond to a "flag"
506 in the userflags table. E.g., { circulate => 1 } would specify
507 that the user must have the "circulate" privilege in order to
508 proceed. To make sure that access control is correct, the
509 C<$flagsrequired> parameter must be specified correctly.
511 Koha also has a concept of sub-permissions, also known as
512 granular permissions. This makes the value of each key
513 in the C<flagsrequired> hash take on an additional
518 The user must have access to all subfunctions of the module
519 specified by the hash key.
523 The user must have access to at least one subfunction of the module
524 specified by the hash key.
526 specific permission, e.g., 'export_catalog'
528 The user must have access to the specific subfunction list, which
529 must correspond to a row in the permissions table.
531 The C<$type> argument specifies whether the template should be
532 retrieved from the opac or intranet directory tree. "opac" is
533 assumed if it is not specified; however, if C<$type> is specified,
534 "intranet" is assumed if it is not "opac".
536 If C<$query> does not have a valid session ID associated with it
537 (i.e., the user has not logged in) or if the session has expired,
538 C<&checkauth> presents the user with a login page (from the point of
539 view of the original script, C<&checkauth> does not return). Once the
540 user has authenticated, C<&checkauth> restarts the original script
541 (this time, C<&checkauth> returns).
543 The login page is provided using a HTML::Template, which is set in the
544 systempreferences table or at the top of this file. The variable C<$type>
545 selects which template to use, either the opac or the intranet
546 authentification template.
548 C<&checkauth> returns a user ID, a cookie, and a session ID. The
549 cookie should be sent back to the browser; it verifies that the user
558 # If Version syspref is unavailable, it means Koha is beeing installed,
559 # and so we must redirect to OPAC maintenance page or to the WebInstaller
560 # also, if OpacMaintenance is ON, OPAC should redirect to maintenance
561 if (C4::Context->preference('OpacMaintenance') && $type eq 'opac') {
562 warn "OPAC Install required, redirecting to maintenance";
563 print $query->redirect("/cgi-bin/koha/maintenance.pl");
566 unless ( $version = C4::Context->preference('Version') ) { # assignment, not comparison
567 if ( $type ne 'opac' ) {
568 warn "Install required, redirecting to Installer";
569 print $query->redirect("/cgi-bin/koha/installer/install.pl");
571 warn "OPAC Install required, redirecting to maintenance";
572 print $query->redirect("/cgi-bin/koha/maintenance.pl");
577 # check that database and koha version are the same
578 # there is no DB version, it's a fresh install,
579 # go to web installer
580 # there is a DB version, compare it to the code version
581 my $kohaversion=C4::Context::KOHAVERSION;
582 # remove the 3 last . to have a Perl number
583 $kohaversion =~ s/(.*\..*)\.(.*)\.(.*)/$1$2$3/;
584 $debug and print STDERR "kohaversion : $kohaversion\n";
585 if ($version < $kohaversion){
586 my $warning = "Database update needed, redirecting to %s. Database is $version and Koha is $kohaversion";
587 if ($type ne 'opac'){
588 warn sprintf($warning, 'Installer');
589 print $query->redirect("/cgi-bin/koha/installer/install.pl?step=3");
591 warn sprintf("OPAC: " . $warning, 'maintenance');
592 print $query->redirect("/cgi-bin/koha/maintenance.pl");
600 open my $fh, '>>', "/tmp/sessionlog" or warn "ERROR: Cannot append to /tmp/sessionlog";
601 printf $fh join("\n",@_);
605 sub _timeout_syspref {
606 my $timeout = C4::Context->preference('timeout') || 600;
607 # value in days, convert in seconds
608 if ($timeout =~ /(\d+)[dD]/) {
609 $timeout = $1 * 86400;
616 $debug and warn "Checking Auth";
617 # $authnotrequired will be set for scripts which will run without authentication
618 my $authnotrequired = shift;
619 my $flagsrequired = shift;
621 $type = 'opac' unless $type;
623 my $dbh = C4::Context->dbh;
624 my $timeout = _timeout_syspref();
626 _version_check($type,$query);
630 my ( $userid, $cookie, $sessionID, $flags, $barshelves, $pubshelves );
631 my $logout = $query->param('logout.x');
633 # This parameter is the name of the CAS server we want to authenticate against,
634 # when using authentication against multiple CAS servers, as configured in Auth_cas_servers.yaml
635 my $casparam = $query->param('cas');
637 if ( $userid = $ENV{'REMOTE_USER'} ) {
638 # Using Basic Authentication, no cookies required
639 $cookie = $query->cookie(
640 -name => 'CGISESSID',
647 elsif ( $sessionID = $query->cookie("CGISESSID") )
648 { # assignment, not comparison
649 my $session = get_session($sessionID);
650 C4::Context->_new_userenv($sessionID);
651 my ($ip, $lasttime, $sessiontype);
653 C4::Context::set_userenv(
654 $session->param('number'), $session->param('id'),
655 $session->param('cardnumber'), $session->param('firstname'),
656 $session->param('surname'), $session->param('branch'),
657 $session->param('branchname'), $session->param('flags'),
658 $session->param('emailaddress'), $session->param('branchprinter')
660 C4::Context::set_shelves_userenv('bar',$session->param('barshelves'));
661 C4::Context::set_shelves_userenv('pub',$session->param('pubshelves'));
662 C4::Context::set_shelves_userenv('tot',$session->param('totshelves'));
663 $debug and printf STDERR "AUTH_SESSION: (%s)\t%s %s - %s\n", map {$session->param($_)} qw(cardnumber firstname surname branch) ;
664 $ip = $session->param('ip');
665 $lasttime = $session->param('lasttime');
666 $userid = $session->param('id');
667 $sessiontype = $session->param('sessiontype') || '';
669 if ( ( ($query->param('koha_login_context')) && ($query->param('userid') ne $session->param('id')) )
670 || ( $cas && $query->param('ticket') ) ) {
671 #if a user enters an id ne to the id in the current session, we need to log them in...
672 #first we need to clear the anonymous session...
673 $debug and warn "query id = " . $query->param('userid') . " but session id = " . $session->param('id');
676 C4::Context->_unset_userenv($sessionID);
681 # voluntary logout the user
684 C4::Context->_unset_userenv($sessionID);
685 #_session_log(sprintf "%20s from %16s logged out at %30s (manually).\n", $userid,$ip,(strftime "%c",localtime));
689 if ($cas and $caslogout) {
693 elsif ( $lasttime < time() - $timeout ) {
695 $info{'timed_out'} = 1;
696 $session->delete() if $session;
697 C4::Context->_unset_userenv($sessionID);
698 #_session_log(sprintf "%20s from %16s logged out at %30s (inactivity).\n", $userid,$ip,(strftime "%c",localtime));
702 elsif ( $ip ne $ENV{'REMOTE_ADDR'} ) {
703 # Different ip than originally logged in from
704 $info{'oldip'} = $ip;
705 $info{'newip'} = $ENV{'REMOTE_ADDR'};
706 $info{'different_ip'} = 1;
708 C4::Context->_unset_userenv($sessionID);
709 #_session_log(sprintf "%20s from %16s logged out at %30s (ip changed to %16s).\n", $userid,$ip,(strftime "%c",localtime), $info{'newip'});
714 $cookie = $query->cookie(
715 -name => 'CGISESSID',
716 -value => $session->id,
719 $session->param( 'lasttime', time() );
720 unless ( $sessiontype && $sessiontype eq 'anon' ) { #if this is an anonymous session, we want to update the session, but not behave as if they are logged in...
721 $flags = haspermission($userid, $flagsrequired);
725 $info{'nopermission'} = 1;
730 unless ($userid || $sessionID) {
731 #we initiate a session prior to checking for a username to allow for anonymous sessions...
732 my $session = get_session("") or die "Auth ERROR: Cannot get_session()";
733 my $sessionID = $session->id;
734 C4::Context->_new_userenv($sessionID);
735 $cookie = $query->cookie(
736 -name => 'CGISESSID',
737 -value => $session->id,
740 $userid = $query->param('userid');
741 if ( ( $cas && $query->param('ticket') )
743 || ( my $pki_field = C4::Context->preference('AllowPKIAuth') ) ne
746 my $password = $query->param('password');
747 my ( $return, $cardnumber );
748 if ( $cas && $query->param('ticket') ) {
750 ( $return, $cardnumber, $retuserid ) =
751 checkpw( $dbh, $userid, $password, $query );
752 $userid = $retuserid;
753 $info{'invalidCasLogin'} = 1 unless ($return);
756 ( $pki_field eq 'Common Name' && $ENV{'SSL_CLIENT_S_DN_CN'} )
757 || ( $pki_field eq 'emailAddress'
758 && $ENV{'SSL_CLIENT_S_DN_Email'} )
762 if ( $pki_field eq 'Common Name' ) {
763 $value = $ENV{'SSL_CLIENT_S_DN_CN'};
765 elsif ( $pki_field eq 'emailAddress' ) {
766 $value = $ENV{'SSL_CLIENT_S_DN_Email'};
768 # If we're looking up the email, there's a chance that the person
769 # doesn't have a userid. So if there is none, we pass along the
770 # borrower number, and the bits of code that need to know the user
771 # ID will have to be smart enough to handle that.
773 my @users_info = C4::Members::GetBorrowersWithEmail($value);
776 # First the userid, then the borrowernum
777 $value = $users_info[0][1] || $users_info[0][0];
783 # 0 for no user, 1 for normal, 2 for demo user.
784 $return = $value ? 1 : 0;
789 ( $return, $cardnumber, $retuserid ) =
790 checkpw( $dbh, $userid, $password, $query );
791 $userid = $retuserid if ( $retuserid ne '' );
794 #_session_log(sprintf "%20s from %16s logged in at %30s.\n", $userid,$ENV{'REMOTE_ADDR'},(strftime '%c', localtime));
795 if ( $flags = haspermission( $userid, $flagsrequired ) ) {
799 $info{'nopermission'} = 1;
800 C4::Context->_unset_userenv($sessionID);
802 my ($borrowernumber, $firstname, $surname, $userflags,
803 $branchcode, $branchname, $branchprinter, $emailaddress);
805 if ( $return == 1 ) {
807 SELECT borrowernumber, firstname, surname, flags, borrowers.branchcode,
808 branches.branchname as branchname,
809 branches.branchprinter as branchprinter,
812 LEFT JOIN branches on borrowers.branchcode=branches.branchcode
814 my $sth = $dbh->prepare("$select where userid=?");
815 $sth->execute($userid);
816 unless ($sth->rows) {
817 $debug and print STDERR "AUTH_1: no rows for userid='$userid'\n";
818 $sth = $dbh->prepare("$select where cardnumber=?");
819 $sth->execute($cardnumber);
821 unless ($sth->rows) {
822 $debug and print STDERR "AUTH_2a: no rows for cardnumber='$cardnumber'\n";
823 $sth->execute($userid);
824 unless ($sth->rows) {
825 $debug and print STDERR "AUTH_2b: no rows for userid='$userid' AS cardnumber\n";
830 ($borrowernumber, $firstname, $surname, $userflags,
831 $branchcode, $branchname, $branchprinter, $emailaddress) = $sth->fetchrow;
832 $debug and print STDERR "AUTH_3 results: " .
833 "$cardnumber,$borrowernumber,$userid,$firstname,$surname,$userflags,$branchcode,$emailaddress\n";
835 print STDERR "AUTH_3: no results for userid='$userid', cardnumber='$cardnumber'.\n";
838 # launch a sequence to check if we have a ip for the branch, i
839 # if we have one we replace the branchcode of the userenv by the branch bound in the ip.
841 my $ip = $ENV{'REMOTE_ADDR'};
842 # if they specify at login, use that
843 if ($query->param('branch')) {
844 $branchcode = $query->param('branch');
845 $branchname = GetBranchName($branchcode);
847 my $branches = GetBranches();
848 if (C4::Context->boolean_preference('IndependantBranches') && C4::Context->boolean_preference('Autolocation')){
849 # we have to check they are coming from the right ip range
850 my $domain = $branches->{$branchcode}->{'branchip'};
851 if ($ip !~ /^$domain/){
853 $info{'wrongip'} = 1;
858 foreach my $br ( keys %$branches ) {
859 # now we work with the treatment of ip
860 my $domain = $branches->{$br}->{'branchip'};
861 if ( $domain && $ip =~ /^$domain/ ) {
862 $branchcode = $branches->{$br}->{'branchcode'};
864 # new op dev : add the branchprinter and branchname in the cookie
865 $branchprinter = $branches->{$br}->{'branchprinter'};
866 $branchname = $branches->{$br}->{'branchname'};
869 $session->param('number',$borrowernumber);
870 $session->param('id',$userid);
871 $session->param('cardnumber',$cardnumber);
872 $session->param('firstname',$firstname);
873 $session->param('surname',$surname);
874 $session->param('branch',$branchcode);
875 $session->param('branchname',$branchname);
876 $session->param('flags',$userflags);
877 $session->param('emailaddress',$emailaddress);
878 $session->param('ip',$session->remote_addr());
879 $session->param('lasttime',time());
880 $debug and printf STDERR "AUTH_4: (%s)\t%s %s - %s\n", map {$session->param($_)} qw(cardnumber firstname surname branch) ;
882 elsif ( $return == 2 ) {
883 #We suppose the user is the superlibrarian
885 $session->param('number',0);
886 $session->param('id',C4::Context->config('user'));
887 $session->param('cardnumber',C4::Context->config('user'));
888 $session->param('firstname',C4::Context->config('user'));
889 $session->param('surname',C4::Context->config('user'));
890 $session->param('branch','NO_LIBRARY_SET');
891 $session->param('branchname','NO_LIBRARY_SET');
892 $session->param('flags',1);
893 $session->param('emailaddress', C4::Context->preference('KohaAdminEmailAddress'));
894 $session->param('ip',$session->remote_addr());
895 $session->param('lasttime',time());
897 C4::Context::set_userenv(
898 $session->param('number'), $session->param('id'),
899 $session->param('cardnumber'), $session->param('firstname'),
900 $session->param('surname'), $session->param('branch'),
901 $session->param('branchname'), $session->param('flags'),
902 $session->param('emailaddress'), $session->param('branchprinter')
908 $info{'invalid_username_or_password'} = 1;
909 C4::Context->_unset_userenv($sessionID);
912 } # END if ( $userid = $query->param('userid') )
913 elsif ($type eq "opac") {
914 # if we are here this is an anonymous session; add public lists to it and a few other items...
915 # anonymous sessions are created only for the OPAC
916 $debug and warn "Initiating an anonymous session...";
918 # setting a couple of other session vars...
919 $session->param('ip',$session->remote_addr());
920 $session->param('lasttime',time());
921 $session->param('sessiontype','anon');
923 } # END unless ($userid)
924 my $insecure = C4::Context->boolean_preference('insecure');
926 # finished authentification, now respond
927 if ( $loggedin || $authnotrequired || ( defined($insecure) && $insecure ) )
931 $cookie = $query->cookie(
932 -name => 'CGISESSID',
937 return ( $userid, $cookie, $sessionID, $flags );
942 # AUTH rejected, show the login/password template, after checking the DB.
946 # get the inputs from the incoming query
948 foreach my $name ( param $query) {
949 (next) if ( $name eq 'userid' || $name eq 'password' || $name eq 'ticket' );
950 my $value = $query->param($name);
951 push @inputs, { name => $name, value => $value };
954 my $template_name = ( $type eq 'opac' ) ? 'opac-auth.tmpl' : 'auth.tmpl';
955 my $template = C4::Templates::gettemplate($template_name, $type, $query );
957 branchloop => GetBranchesLoop(),
958 opaccolorstylesheet => C4::Context->preference("opaccolorstylesheet"),
959 opaclayoutstylesheet => C4::Context->preference("opaclayoutstylesheet"),
962 casAuthentication => C4::Context->preference("casAuthentication"),
963 suggestion => C4::Context->preference("suggestion"),
964 virtualshelves => C4::Context->preference("virtualshelves"),
965 LibraryName => C4::Context->preference("LibraryName"),
966 opacuserlogin => C4::Context->preference("opacuserlogin"),
967 OpacNav => C4::Context->preference("OpacNav"),
968 OpacNavRight => C4::Context->preference("OpacNavRight"),
969 OpacNavBottom => C4::Context->preference("OpacNavBottom"),
970 opaccredits => C4::Context->preference("opaccredits"),
971 OpacFavicon => C4::Context->preference("OpacFavicon"),
972 opacreadinghistory => C4::Context->preference("opacreadinghistory"),
973 opacsmallimage => C4::Context->preference("opacsmallimage"),
974 opaclanguagesdisplay => C4::Context->preference("opaclanguagesdisplay"),
975 opacuserjs => C4::Context->preference("opacuserjs"),
976 opacbookbag => "" . C4::Context->preference("opacbookbag"),
977 OpacCloud => C4::Context->preference("OpacCloud"),
978 OpacTopissue => C4::Context->preference("OpacTopissue"),
979 OpacAuthorities => C4::Context->preference("OpacAuthorities"),
980 OpacBrowser => C4::Context->preference("OpacBrowser"),
981 opacheader => C4::Context->preference("opacheader"),
982 TagsEnabled => C4::Context->preference("TagsEnabled"),
983 OPACUserCSS => C4::Context->preference("OPACUserCSS"),
984 intranetcolorstylesheet => C4::Context->preference("intranetcolorstylesheet"),
985 intranetstylesheet => C4::Context->preference("intranetstylesheet"),
986 intranetbookbag => C4::Context->preference("intranetbookbag"),
987 IntranetNav => C4::Context->preference("IntranetNav"),
988 IntranetFavicon => C4::Context->preference("IntranetFavicon"),
989 intranetuserjs => C4::Context->preference("intranetuserjs"),
990 IndependantBranches=> C4::Context->preference("IndependantBranches"),
991 AutoLocation => C4::Context->preference("AutoLocation"),
992 wrongip => $info{'wrongip'},
993 PatronSelfRegistration => C4::Context->preference("PatronSelfRegistration"),
994 PatronSelfRegistrationDefaultCategory => C4::Context->preference("PatronSelfRegistrationDefaultCategory"),
997 $template->param( OpacPublic => C4::Context->preference("OpacPublic"));
998 $template->param( loginprompt => 1 ) unless $info{'nopermission'};
1002 # Is authentication against multiple CAS servers enabled?
1003 if (C4::Auth_with_cas::multipleAuth && !$casparam) {
1004 my $casservers = C4::Auth_with_cas::getMultipleAuth();
1006 foreach my $key (keys %$casservers) {
1007 push @tmplservers, {name => $key, value => login_cas_url($query, $key) . "?cas=$key" };
1009 #warn Data::Dumper::Dumper(\@tmplservers);
1011 casServersLoop => \@tmplservers
1015 casServerUrl => login_cas_url($query),
1020 invalidCasLogin => $info{'invalidCasLogin'}
1024 my $self_url = $query->url( -absolute => 1 );
1027 LibraryName => C4::Context->preference("LibraryName"),
1029 $template->param( %info );
1030 # $cookie = $query->cookie(CGISESSID => $session->id
1032 print $query->header(
1033 -type => 'text/html',
1034 -charset => 'utf-8',
1041 =head2 check_api_auth
1043 ($status, $cookie, $sessionId) = check_api_auth($query, $userflags);
1045 Given a CGI query containing the parameters 'userid' and 'password' and/or a session
1046 cookie, determine if the user has the privileges specified by C<$userflags>.
1048 C<check_api_auth> is is meant for authenticating users of web services, and
1049 consequently will always return and will not attempt to redirect the user
1052 If a valid session cookie is already present, check_api_auth will return a status
1053 of "ok", the cookie, and the Koha session ID.
1055 If no session cookie is present, check_api_auth will check the 'userid' and 'password
1056 parameters and create a session cookie and Koha session if the supplied credentials
1059 Possible return values in C<$status> are:
1063 =item "ok" -- user authenticated; C<$cookie> and C<$sessionid> have valid values.
1065 =item "failed" -- credentials are not correct; C<$cookie> and C<$sessionid> are undef
1067 =item "maintenance" -- DB is in maintenance mode; no login possible at the moment
1069 =item "expired -- session cookie has expired; API user should resubmit userid and password
1075 sub check_api_auth {
1077 my $flagsrequired = shift;
1079 my $dbh = C4::Context->dbh;
1080 my $timeout = _timeout_syspref();
1082 unless (C4::Context->preference('Version')) {
1083 # database has not been installed yet
1084 return ("maintenance", undef, undef);
1086 my $kohaversion=C4::Context::KOHAVERSION;
1087 $kohaversion =~ s/(.*\..*)\.(.*)\.(.*)/$1$2$3/;
1088 if (C4::Context->preference('Version') < $kohaversion) {
1089 # database in need of version update; assume that
1090 # no API should be called while databsae is in
1092 return ("maintenance", undef, undef);
1095 # FIXME -- most of what follows is a copy-and-paste
1096 # of code from checkauth. There is an obvious need
1097 # for refactoring to separate the various parts of
1098 # the authentication code, but as of 2007-11-19 this
1099 # is deferred so as to not introduce bugs into the
1100 # regular authentication code for Koha 3.0.
1102 # see if we have a valid session cookie already
1103 # however, if a userid parameter is present (i.e., from
1104 # a form submission, assume that any current cookie
1106 my $sessionID = undef;
1107 unless ($query->param('userid')) {
1108 $sessionID = $query->cookie("CGISESSID");
1110 if ($sessionID && not ($cas && $query->param('PT')) ) {
1111 my $session = get_session($sessionID);
1112 C4::Context->_new_userenv($sessionID);
1114 C4::Context::set_userenv(
1115 $session->param('number'), $session->param('id'),
1116 $session->param('cardnumber'), $session->param('firstname'),
1117 $session->param('surname'), $session->param('branch'),
1118 $session->param('branchname'), $session->param('flags'),
1119 $session->param('emailaddress'), $session->param('branchprinter')
1122 my $ip = $session->param('ip');
1123 my $lasttime = $session->param('lasttime');
1124 my $userid = $session->param('id');
1125 if ( $lasttime < time() - $timeout ) {
1128 C4::Context->_unset_userenv($sessionID);
1131 return ("expired", undef, undef);
1132 } elsif ( $ip ne $ENV{'REMOTE_ADDR'} ) {
1133 # IP address changed
1135 C4::Context->_unset_userenv($sessionID);
1138 return ("expired", undef, undef);
1140 my $cookie = $query->cookie(
1141 -name => 'CGISESSID',
1142 -value => $session->id,
1145 $session->param('lasttime',time());
1146 my $flags = haspermission($userid, $flagsrequired);
1148 return ("ok", $cookie, $sessionID);
1151 C4::Context->_unset_userenv($sessionID);
1154 return ("failed", undef, undef);
1158 return ("expired", undef, undef);
1162 my $userid = $query->param('userid');
1163 my $password = $query->param('password');
1164 my ($return, $cardnumber);
1167 if ($cas && $query->param('PT')) {
1169 $debug and print STDERR "## check_api_auth - checking CAS\n";
1170 # In case of a CAS authentication, we use the ticket instead of the password
1171 my $PT = $query->param('PT');
1172 ($return,$cardnumber,$userid) = check_api_auth_cas($dbh, $PT, $query); # EXTERNAL AUTH
1174 # User / password auth
1175 unless ($userid and $password) {
1176 # caller did something wrong, fail the authenticateion
1177 return ("failed", undef, undef);
1179 ( $return, $cardnumber ) = checkpw( $dbh, $userid, $password, $query );
1182 if ($return and haspermission( $userid, $flagsrequired)) {
1183 my $session = get_session("");
1184 return ("failed", undef, undef) unless $session;
1186 my $sessionID = $session->id;
1187 C4::Context->_new_userenv($sessionID);
1188 my $cookie = $query->cookie(
1189 -name => 'CGISESSID',
1190 -value => $sessionID,
1193 if ( $return == 1 ) {
1195 $borrowernumber, $firstname, $surname,
1196 $userflags, $branchcode, $branchname,
1197 $branchprinter, $emailaddress
1201 "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=?"
1203 $sth->execute($userid);
1205 $borrowernumber, $firstname, $surname,
1206 $userflags, $branchcode, $branchname,
1207 $branchprinter, $emailaddress
1208 ) = $sth->fetchrow if ( $sth->rows );
1210 unless ($sth->rows ) {
1211 my $sth = $dbh->prepare(
1212 "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=?"
1214 $sth->execute($cardnumber);
1216 $borrowernumber, $firstname, $surname,
1217 $userflags, $branchcode, $branchname,
1218 $branchprinter, $emailaddress
1219 ) = $sth->fetchrow if ( $sth->rows );
1221 unless ( $sth->rows ) {
1222 $sth->execute($userid);
1224 $borrowernumber, $firstname, $surname, $userflags,
1225 $branchcode, $branchname, $branchprinter, $emailaddress
1226 ) = $sth->fetchrow if ( $sth->rows );
1230 my $ip = $ENV{'REMOTE_ADDR'};
1231 # if they specify at login, use that
1232 if ($query->param('branch')) {
1233 $branchcode = $query->param('branch');
1234 $branchname = GetBranchName($branchcode);
1236 my $branches = GetBranches();
1238 foreach my $br ( keys %$branches ) {
1239 # now we work with the treatment of ip
1240 my $domain = $branches->{$br}->{'branchip'};
1241 if ( $domain && $ip =~ /^$domain/ ) {
1242 $branchcode = $branches->{$br}->{'branchcode'};
1244 # new op dev : add the branchprinter and branchname in the cookie
1245 $branchprinter = $branches->{$br}->{'branchprinter'};
1246 $branchname = $branches->{$br}->{'branchname'};
1249 $session->param('number',$borrowernumber);
1250 $session->param('id',$userid);
1251 $session->param('cardnumber',$cardnumber);
1252 $session->param('firstname',$firstname);
1253 $session->param('surname',$surname);
1254 $session->param('branch',$branchcode);
1255 $session->param('branchname',$branchname);
1256 $session->param('flags',$userflags);
1257 $session->param('emailaddress',$emailaddress);
1258 $session->param('ip',$session->remote_addr());
1259 $session->param('lasttime',time());
1260 } elsif ( $return == 2 ) {
1261 #We suppose the user is the superlibrarian
1262 $session->param('number',0);
1263 $session->param('id',C4::Context->config('user'));
1264 $session->param('cardnumber',C4::Context->config('user'));
1265 $session->param('firstname',C4::Context->config('user'));
1266 $session->param('surname',C4::Context->config('user'));
1267 $session->param('branch','NO_LIBRARY_SET');
1268 $session->param('branchname','NO_LIBRARY_SET');
1269 $session->param('flags',1);
1270 $session->param('emailaddress', C4::Context->preference('KohaAdminEmailAddress'));
1271 $session->param('ip',$session->remote_addr());
1272 $session->param('lasttime',time());
1274 C4::Context::set_userenv(
1275 $session->param('number'), $session->param('id'),
1276 $session->param('cardnumber'), $session->param('firstname'),
1277 $session->param('surname'), $session->param('branch'),
1278 $session->param('branchname'), $session->param('flags'),
1279 $session->param('emailaddress'), $session->param('branchprinter')
1281 return ("ok", $cookie, $sessionID);
1283 return ("failed", undef, undef);
1288 =head2 check_cookie_auth
1290 ($status, $sessionId) = check_api_auth($cookie, $userflags);
1292 Given a CGISESSID cookie set during a previous login to Koha, determine
1293 if the user has the privileges specified by C<$userflags>.
1295 C<check_cookie_auth> is meant for authenticating special services
1296 such as tools/upload-file.pl that are invoked by other pages that
1297 have been authenticated in the usual way.
1299 Possible return values in C<$status> are:
1303 =item "ok" -- user authenticated; C<$sessionID> have valid values.
1305 =item "failed" -- credentials are not correct; C<$sessionid> are undef
1307 =item "maintenance" -- DB is in maintenance mode; no login possible at the moment
1309 =item "expired -- session cookie has expired; API user should resubmit userid and password
1315 sub check_cookie_auth {
1317 my $flagsrequired = shift;
1319 my $dbh = C4::Context->dbh;
1320 my $timeout = _timeout_syspref();
1322 unless (C4::Context->preference('Version')) {
1323 # database has not been installed yet
1324 return ("maintenance", undef);
1326 my $kohaversion=C4::Context::KOHAVERSION;
1327 $kohaversion =~ s/(.*\..*)\.(.*)\.(.*)/$1$2$3/;
1328 if (C4::Context->preference('Version') < $kohaversion) {
1329 # database in need of version update; assume that
1330 # no API should be called while databsae is in
1332 return ("maintenance", undef);
1335 # FIXME -- most of what follows is a copy-and-paste
1336 # of code from checkauth. There is an obvious need
1337 # for refactoring to separate the various parts of
1338 # the authentication code, but as of 2007-11-23 this
1339 # is deferred so as to not introduce bugs into the
1340 # regular authentication code for Koha 3.0.
1342 # see if we have a valid session cookie already
1343 # however, if a userid parameter is present (i.e., from
1344 # a form submission, assume that any current cookie
1346 unless (defined $cookie and $cookie) {
1347 return ("failed", undef);
1349 my $sessionID = $cookie;
1350 my $session = get_session($sessionID);
1351 C4::Context->_new_userenv($sessionID);
1353 C4::Context::set_userenv(
1354 $session->param('number'), $session->param('id'),
1355 $session->param('cardnumber'), $session->param('firstname'),
1356 $session->param('surname'), $session->param('branch'),
1357 $session->param('branchname'), $session->param('flags'),
1358 $session->param('emailaddress'), $session->param('branchprinter')
1361 my $ip = $session->param('ip');
1362 my $lasttime = $session->param('lasttime');
1363 my $userid = $session->param('id');
1364 if ( $lasttime < time() - $timeout ) {
1367 C4::Context->_unset_userenv($sessionID);
1370 return ("expired", undef);
1371 } elsif ( $ip ne $ENV{'REMOTE_ADDR'} ) {
1372 # IP address changed
1374 C4::Context->_unset_userenv($sessionID);
1377 return ("expired", undef);
1379 $session->param('lasttime',time());
1380 my $flags = haspermission($userid, $flagsrequired);
1382 return ("ok", $sessionID);
1385 C4::Context->_unset_userenv($sessionID);
1388 return ("failed", undef);
1392 return ("expired", undef);
1399 my $session = get_session($sessionID);
1401 Given a session ID, retrieve the CGI::Session object used to store
1402 the session's state. The session object can be used to store
1403 data that needs to be accessed by different scripts during a
1406 If the C<$sessionID> parameter is an empty string, a new session
1412 my $sessionID = shift;
1413 my $storage_method = C4::Context->preference('SessionStorage');
1414 my $dbh = C4::Context->dbh;
1416 if ($storage_method eq 'mysql'){
1417 $session = new CGI::Session("driver:MySQL;serializer:yaml;id:md5", $sessionID, {Handle=>$dbh});
1419 elsif ($storage_method eq 'Pg') {
1420 $session = new CGI::Session("driver:PostgreSQL;serializer:yaml;id:md5", $sessionID, {Handle=>$dbh});
1422 elsif ($storage_method eq 'memcached' && C4::Context->ismemcached){
1423 $session = new CGI::Session("driver:memcached;serializer:yaml;id:md5", $sessionID, { Memcached => C4::Context->memcached } );
1426 # catch all defaults to tmp should work on all systems
1427 $session = new CGI::Session("driver:File;serializer:yaml;id:md5", $sessionID, {Directory=>'/tmp'});
1434 my ( $dbh, $userid, $password, $query ) = @_;
1436 $debug and print STDERR "## checkpw - checking LDAP\n";
1437 my ($retval,$retcard,$retuserid) = checkpw_ldap(@_); # EXTERNAL AUTH
1438 ($retval) and return ($retval,$retcard,$retuserid);
1441 if ($cas && $query && $query->param('ticket')) {
1442 $debug and print STDERR "## checkpw - checking CAS\n";
1443 # In case of a CAS authentication, we use the ticket instead of the password
1444 my $ticket = $query->param('ticket');
1445 my ($retval,$retcard,$retuserid) = checkpw_cas($dbh, $ticket, $query); # EXTERNAL AUTH
1446 ($retval) and return ($retval,$retcard,$retuserid);
1453 "select password,cardnumber,borrowernumber,userid,firstname,surname,branchcode,flags from borrowers where userid=?"
1455 $sth->execute($userid);
1457 my ( $md5password, $cardnumber, $borrowernumber, $userid, $firstname,
1458 $surname, $branchcode, $flags )
1460 if ( md5_base64($password) eq $md5password and $md5password ne "!") {
1462 C4::Context->set_userenv( "$borrowernumber", $userid, $cardnumber,
1463 $firstname, $surname, $branchcode, $flags );
1464 return 1, $cardnumber, $userid;
1469 "select password,cardnumber,borrowernumber,userid, firstname,surname,branchcode,flags from borrowers where cardnumber=?"
1471 $sth->execute($userid);
1473 my ( $md5password, $cardnumber, $borrowernumber, $userid, $firstname,
1474 $surname, $branchcode, $flags )
1476 if ( md5_base64($password) eq $md5password ) {
1478 C4::Context->set_userenv( $borrowernumber, $userid, $cardnumber,
1479 $firstname, $surname, $branchcode, $flags );
1480 return 1, $cardnumber, $userid;
1483 if ( $userid && $userid eq C4::Context->config('user')
1484 && "$password" eq C4::Context->config('pass') )
1487 # Koha superuser account
1488 # C4::Context->set_userenv(0,0,C4::Context->config('user'),C4::Context->config('user'),C4::Context->config('user'),"",1);
1491 if ( $userid && $userid eq 'demo'
1492 && "$password" eq 'demo'
1493 && C4::Context->config('demo') )
1496 # DEMO => the demo user is allowed to do everything (if demo set to 1 in koha.conf
1497 # some features won't be effective : modify systempref, modify MARC structure,
1505 my $authflags = getuserflags($flags, $userid, [$dbh]);
1507 Translates integer flags into permissions strings hash.
1509 C<$flags> is the integer userflags value ( borrowers.userflags )
1510 C<$userid> is the members.userid, used for building subpermissions
1511 C<$authflags> is a hashref of permissions
1518 my $dbh = @_ ? shift : C4::Context->dbh;
1521 # I don't want to do this, but if someone logs in as the database
1522 # user, it would be preferable not to spam them to death with
1523 # numeric warnings. So, we make $flags numeric.
1524 no warnings 'numeric';
1527 my $sth = $dbh->prepare("SELECT bit, flag, defaulton FROM userflags");
1530 while ( my ( $bit, $flag, $defaulton ) = $sth->fetchrow ) {
1531 if ( ( $flags & ( 2**$bit ) ) || $defaulton ) {
1532 $userflags->{$flag} = 1;
1535 $userflags->{$flag} = 0;
1539 # get subpermissions and merge with top-level permissions
1540 my $user_subperms = get_user_subpermissions($userid);
1541 foreach my $module (keys %$user_subperms) {
1542 next if $userflags->{$module} == 1; # user already has permission for everything in this module
1543 $userflags->{$module} = $user_subperms->{$module};
1549 =head2 get_user_subpermissions
1551 $user_perm_hashref = get_user_subpermissions($userid);
1553 Given the userid (note, not the borrowernumber) of a staff user,
1554 return a hashref of hashrefs of the specific subpermissions
1555 accorded to the user. An example return is
1559 export_catalog => 1,
1560 import_patrons => 1,
1564 The top-level hash-key is a module or function code from
1565 userflags.flag, while the second-level key is a code
1568 The results of this function do not give a complete picture
1569 of the functions that a staff user can access; it is also
1570 necessary to check borrowers.flags.
1574 sub get_user_subpermissions {
1577 my $dbh = C4::Context->dbh;
1578 my $sth = $dbh->prepare("SELECT flag, user_permissions.code
1579 FROM user_permissions
1580 JOIN permissions USING (module_bit, code)
1581 JOIN userflags ON (module_bit = bit)
1582 JOIN borrowers USING (borrowernumber)
1584 $sth->execute($userid);
1586 my $user_perms = {};
1587 while (my $perm = $sth->fetchrow_hashref) {
1588 $user_perms->{$perm->{'flag'}}->{$perm->{'code'}} = 1;
1593 =head2 get_all_subpermissions
1595 my $perm_hashref = get_all_subpermissions();
1597 Returns a hashref of hashrefs defining all specific
1598 permissions currently defined. The return value
1599 has the same structure as that of C<get_user_subpermissions>,
1600 except that the innermost hash value is the description
1601 of the subpermission.
1605 sub get_all_subpermissions {
1606 my $dbh = C4::Context->dbh;
1607 my $sth = $dbh->prepare("SELECT flag, code, description
1609 JOIN userflags ON (module_bit = bit)");
1613 while (my $perm = $sth->fetchrow_hashref) {
1614 $all_perms->{$perm->{'flag'}}->{$perm->{'code'}} = $perm->{'description'};
1619 =head2 haspermission
1621 $flags = ($userid, $flagsrequired);
1623 C<$userid> the userid of the member
1624 C<$flags> is a hashref of required flags like C<$borrower-<{authflags}>
1626 Returns member's flags or 0 if a permission is not met.
1631 my ($userid, $flagsrequired) = @_;
1632 my $sth = C4::Context->dbh->prepare("SELECT flags FROM borrowers WHERE userid=?");
1633 $sth->execute($userid);
1634 my $flags = getuserflags($sth->fetchrow(), $userid);
1635 if ( $userid eq C4::Context->config('user') ) {
1636 # Super User Account from /etc/koha.conf
1637 $flags->{'superlibrarian'} = 1;
1639 elsif ( $userid eq 'demo' && C4::Context->config('demo') ) {
1640 # Demo user that can do "anything" (demo=1 in /etc/koha.conf)
1641 $flags->{'superlibrarian'} = 1;
1644 return $flags if $flags->{superlibrarian};
1646 foreach my $module ( keys %$flagsrequired ) {
1647 my $subperm = $flagsrequired->{$module};
1648 if ($subperm eq '*') {
1649 return 0 unless ( $flags->{$module} == 1 or ref($flags->{$module}) );
1651 return 0 unless ( $flags->{$module} == 1 or
1652 ( ref($flags->{$module}) and
1653 exists $flags->{$module}->{$subperm} and
1654 $flags->{$module}->{$subperm} == 1
1660 #FIXME - This fcn should return the failed permission so a suitable error msg can be delivered.
1664 sub getborrowernumber {
1666 my $userenv = C4::Context->userenv;
1667 if ( defined( $userenv ) && ref( $userenv ) eq 'HASH' && $userenv->{number} ) {
1668 return $userenv->{number};
1670 my $dbh = C4::Context->dbh;
1671 for my $field ( 'userid', 'cardnumber' ) {
1673 $dbh->prepare("select borrowernumber from borrowers where $field=?");
1674 $sth->execute($userid);
1676 my ($bnumber) = $sth->fetchrow;
1684 END { } # module clean-up code here (global destructor)