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 JSON qw/encode_json/;
29 use C4::Templates; # to get the template
30 use C4::Branch; # GetBranches
31 use C4::Search::History;
32 use C4::VirtualShelves;
33 use Koha::AuthUtils qw(hash_password);
34 use POSIX qw/strftime/;
35 use List::MoreUtils qw/ any /;
38 use vars qw($VERSION @ISA @EXPORT @EXPORT_OK %EXPORT_TAGS $debug $ldap $cas $caslogout);
41 sub psgi_env { any { /^psgi\./ } keys %ENV }
43 if ( psgi_env ) { die 'psgi:exit' }
46 $VERSION = 3.07.00.049; # set version for version checking
50 @EXPORT = qw(&checkauth &get_template_and_user &haspermission &get_user_subpermissions);
51 @EXPORT_OK = qw(&check_api_auth &get_session &check_cookie_auth &checkpw &checkpw_internal &checkpw_hash
52 &get_all_subpermissions &get_user_subpermissions
54 %EXPORT_TAGS = ( EditPermissions => [qw(get_all_subpermissions get_user_subpermissions)] );
55 $ldap = C4::Context->config('useldapserver') || 0;
56 $cas = C4::Context->preference('casAuthentication');
57 $caslogout = C4::Context->preference('casLogout');
58 require C4::Auth_with_cas; # no import
60 require C4::Auth_with_ldap;
61 import C4::Auth_with_ldap qw(checkpw_ldap);
64 import C4::Auth_with_cas qw(check_api_auth_cas checkpw_cas login_cas logout_cas login_cas_url);
71 C4::Auth - Authenticates Koha users
81 my ($template, $borrowernumber, $cookie)
82 = get_template_and_user(
84 template_name => "opac-main.tmpl",
88 flagsrequired => {borrow => 1, catalogue => '*', tools => 'import_patrons' },
92 output_html_with_http_headers $query, $cookie, $template->output;
96 The main function of this module is to provide
97 authentification. However the get_template_and_user function has
98 been provided so that a users login information is passed along
99 automatically. This gets loaded into the template.
103 =head2 get_template_and_user
105 my ($template, $borrowernumber, $cookie)
106 = get_template_and_user(
108 template_name => "opac-main.tmpl",
111 authnotrequired => 1,
112 flagsrequired => {borrow => 1, catalogue => '*', tools => 'import_patrons' },
116 This call passes the C<query>, C<flagsrequired> and C<authnotrequired>
117 to C<&checkauth> (in this module) to perform authentification.
118 See C<&checkauth> for an explanation of these parameters.
120 The C<template_name> is then used to find the correct template for
121 the page. The authenticated users details are loaded onto the
122 template in the HTML::Template LOOP variable C<USER_INFO>. Also the
123 C<sessionID> is passed to the template. This can be used in templates
124 if cookies are disabled. It needs to be put as and input to every
127 More information on the C<gettemplate> sub can be found in the
132 sub get_template_and_user {
135 my ( $user, $cookie, $sessionID, $flags );
137 my $template = C4::Templates::gettemplate(
138 $in->{'template_name'},
144 if ( $in->{'template_name'} !~m/maintenance/ ) {
145 ( $user, $cookie, $sessionID, $flags ) = checkauth(
147 $in->{'authnotrequired'},
148 $in->{'flagsrequired'},
156 # It's possible for $user to be the borrowernumber if they don't have a
157 # userid defined (and are logging in through some other method, such
158 # as SSL certs against an email address)
159 $borrowernumber = getborrowernumber($user) if defined($user);
160 if (!defined($borrowernumber) && defined($user)) {
161 my $borrower = C4::Members::GetMember(borrowernumber => $user);
163 $borrowernumber = $user;
164 # A bit of a hack, but I don't know there's a nicer way
166 $user = $borrower->{firstname} . ' ' . $borrower->{surname};
171 $template->param( loggedinusername => $user );
172 $template->param( sessionID => $sessionID );
174 my ($total, $pubshelves, $barshelves) = C4::VirtualShelves::GetSomeShelfNames($borrowernumber, 'MASTHEAD');
176 pubshelves => $total->{pubtotal},
177 pubshelvesloop => $pubshelves,
178 barshelves => $total->{bartotal},
179 barshelvesloop => $barshelves,
182 my ( $borr ) = C4::Members::GetMemberDetails( $borrowernumber );
185 $template->param( "USER_INFO" => \@bordat );
187 my $all_perms = get_all_subpermissions();
189 my @flagroots = qw(circulate catalogue parameters borrowers permissions reserveforothers borrow
190 editcatalogue updatecharges management tools editauthorities serials reports acquisition);
191 # We are going to use the $flags returned by checkauth
192 # to create the template's parameters that will indicate
193 # which menus the user can access.
194 if ( $flags && $flags->{superlibrarian}==1 ) {
195 $template->param( CAN_user_circulate => 1 );
196 $template->param( CAN_user_catalogue => 1 );
197 $template->param( CAN_user_parameters => 1 );
198 $template->param( CAN_user_borrowers => 1 );
199 $template->param( CAN_user_permissions => 1 );
200 $template->param( CAN_user_reserveforothers => 1 );
201 $template->param( CAN_user_borrow => 1 );
202 $template->param( CAN_user_editcatalogue => 1 );
203 $template->param( CAN_user_updatecharges => 1 );
204 $template->param( CAN_user_acquisition => 1 );
205 $template->param( CAN_user_management => 1 );
206 $template->param( CAN_user_tools => 1 );
207 $template->param( CAN_user_editauthorities => 1 );
208 $template->param( CAN_user_serials => 1 );
209 $template->param( CAN_user_reports => 1 );
210 $template->param( CAN_user_staffaccess => 1 );
211 $template->param( CAN_user_plugins => 1 );
212 $template->param( CAN_user_coursereserves => 1 );
213 foreach my $module (keys %$all_perms) {
214 foreach my $subperm (keys %{ $all_perms->{$module} }) {
215 $template->param( "CAN_user_${module}_${subperm}" => 1 );
221 foreach my $module (keys %$all_perms) {
222 if ( $flags->{$module} == 1) {
223 foreach my $subperm (keys %{ $all_perms->{$module} }) {
224 $template->param( "CAN_user_${module}_${subperm}" => 1 );
226 } elsif ( ref($flags->{$module}) ) {
227 foreach my $subperm (keys %{ $flags->{$module} } ) {
228 $template->param( "CAN_user_${module}_${subperm}" => 1 );
235 foreach my $module (keys %$flags) {
236 if ( $flags->{$module} == 1 or ref($flags->{$module}) ) {
237 $template->param( "CAN_user_$module" => 1 );
238 if ($module eq "parameters") {
239 $template->param( CAN_user_management => 1 );
244 # Logged-in opac search history
245 # If the requested template is an opac one and opac search history is enabled
246 if ($in->{type} eq 'opac' && C4::Context->preference('EnableOpacSearchHistory')) {
247 my $dbh = C4::Context->dbh;
248 my $query = "SELECT COUNT(*) FROM search_history WHERE userid=?";
249 my $sth = $dbh->prepare($query);
250 $sth->execute($borrowernumber);
252 # If at least one search has already been performed
253 if ($sth->fetchrow_array > 0) {
254 # We show the link in opac
255 $template->param( EnableOpacSearchHistory => 1 );
258 # And if there are searches performed when the user was not logged in,
259 # we add them to the logged-in search history
260 my @recentSearches = C4::Search::History::get_from_session({ cgi => $in->{'query'} });
261 if (@recentSearches) {
262 my $dbh = C4::Context->dbh;
265 INSERT INTO search_history(userid, sessionid, query_desc, query_cgi, type, total, time )
266 VALUES (?, ?, ?, ?, ?, ?, ?)
269 my $sth = $dbh->prepare($query);
270 $sth->execute( $borrowernumber,
271 $in->{query}->cookie("CGISESSID"),
274 $_->{type} || 'biblio',
277 ) foreach @recentSearches;
279 # clear out the search history from the session now that
280 # we've saved it to the database
281 C4::Search::History::set_to_session({ cgi => $in->{'query'}, search_history => [] });
285 else { # if this is an anonymous session, setup to display public lists...
287 $template->param( sessionID => $sessionID );
289 my ($total, $pubshelves) = C4::VirtualShelves::GetSomeShelfNames(undef, 'MASTHEAD');
291 pubshelves => $total->{pubtotal},
292 pubshelvesloop => $pubshelves,
295 # Anonymous opac search history
296 # If opac search history is enabled and at least one search has already been performed
297 if (C4::Context->preference('EnableOpacSearchHistory')) {
298 my @recentSearches = C4::Search::History::get_from_session({ cgi => $in->{'query'} });
299 if (@recentSearches) {
300 $template->param(EnableOpacSearchHistory => 1);
304 if(C4::Context->preference('dateformat')){
305 $template->param(dateformat => C4::Context->preference('dateformat'))
308 # these template parameters are set the same regardless of $in->{'type'}
310 "BiblioDefaultView".C4::Context->preference("BiblioDefaultView") => 1,
311 EnhancedMessagingPreferences => C4::Context->preference('EnhancedMessagingPreferences'),
312 GoogleJackets => C4::Context->preference("GoogleJackets"),
313 OpenLibraryCovers => C4::Context->preference("OpenLibraryCovers"),
314 KohaAdminEmailAddress => "" . C4::Context->preference("KohaAdminEmailAddress"),
315 LoginBranchcode => (C4::Context->userenv?C4::Context->userenv->{"branch"}:undef),
316 LoginFirstname => (C4::Context->userenv?C4::Context->userenv->{"firstname"}:"Bel"),
317 LoginSurname => C4::Context->userenv?C4::Context->userenv->{"surname"}:"Inconnu",
318 emailaddress => C4::Context->userenv?C4::Context->userenv->{"emailaddress"}:undef,
319 loggedinpersona => C4::Context->userenv?C4::Context->userenv->{"persona"}:undef,
320 TagsEnabled => C4::Context->preference("TagsEnabled"),
321 hide_marc => C4::Context->preference("hide_marc"),
322 item_level_itypes => C4::Context->preference('item-level_itypes'),
323 patronimages => C4::Context->preference("patronimages"),
324 singleBranchMode => C4::Context->preference("singleBranchMode"),
325 XSLTDetailsDisplay => C4::Context->preference("XSLTDetailsDisplay"),
326 XSLTResultsDisplay => C4::Context->preference("XSLTResultsDisplay"),
327 using_https => $in->{'query'}->https() ? 1 : 0,
328 noItemTypeImages => C4::Context->preference("noItemTypeImages"),
329 marcflavour => C4::Context->preference("marcflavour"),
330 persona => C4::Context->preference("persona"),
332 if ( $in->{'type'} eq "intranet" ) {
334 AmazonCoverImages => C4::Context->preference("AmazonCoverImages"),
335 AutoLocation => C4::Context->preference("AutoLocation"),
336 "BiblioDefaultView".C4::Context->preference("IntranetBiblioDefaultView") => 1,
337 CalendarFirstDayOfWeek => (C4::Context->preference("CalendarFirstDayOfWeek") eq "Sunday")?0:1,
338 CircAutocompl => C4::Context->preference("CircAutocompl"),
339 FRBRizeEditions => C4::Context->preference("FRBRizeEditions"),
340 IndependentBranches => C4::Context->preference("IndependentBranches"),
341 IntranetNav => C4::Context->preference("IntranetNav"),
342 IntranetmainUserblock => C4::Context->preference("IntranetmainUserblock"),
343 LibraryName => C4::Context->preference("LibraryName"),
344 LoginBranchname => (C4::Context->userenv?C4::Context->userenv->{"branchname"}:undef),
345 advancedMARCEditor => C4::Context->preference("advancedMARCEditor"),
346 canreservefromotherbranches => C4::Context->preference('canreservefromotherbranches'),
347 intranetcolorstylesheet => C4::Context->preference("intranetcolorstylesheet"),
348 IntranetFavicon => C4::Context->preference("IntranetFavicon"),
349 intranetreadinghistory => C4::Context->preference("intranetreadinghistory"),
350 intranetstylesheet => C4::Context->preference("intranetstylesheet"),
351 IntranetUserCSS => C4::Context->preference("IntranetUserCSS"),
352 intranetuserjs => C4::Context->preference("intranetuserjs"),
353 intranetbookbag => C4::Context->preference("intranetbookbag"),
354 suggestion => C4::Context->preference("suggestion"),
355 virtualshelves => C4::Context->preference("virtualshelves"),
356 StaffSerialIssueDisplayCount => C4::Context->preference("StaffSerialIssueDisplayCount"),
357 EasyAnalyticalRecords => C4::Context->preference('EasyAnalyticalRecords'),
358 LocalCoverImages => C4::Context->preference('LocalCoverImages'),
359 OPACLocalCoverImages => C4::Context->preference('OPACLocalCoverImages'),
360 AllowMultipleCovers => C4::Context->preference('AllowMultipleCovers'),
361 EnableBorrowerFiles => C4::Context->preference('EnableBorrowerFiles'),
362 UseKohaPlugins => C4::Context->preference('UseKohaPlugins'),
363 UseCourseReserves => C4::Context->preference("UseCourseReserves"),
367 warn "template type should be OPAC, here it is=[" . $in->{'type'} . "]" unless ( $in->{'type'} eq 'opac' );
368 #TODO : replace LibraryName syspref with 'system name', and remove this html processing
369 my $LibraryNameTitle = C4::Context->preference("LibraryName");
370 $LibraryNameTitle =~ s/<(?:\/?)(?:br|p)\s*(?:\/?)>/ /sgi;
371 $LibraryNameTitle =~ s/<(?:[^<>'"]|'(?:[^']*)'|"(?:[^"]*)")*>//sg;
372 # clean up the busc param in the session if the page is not opac-detail and not the "add to list" page
373 if ( C4::Context->preference("OpacBrowseResults")
374 && $in->{'template_name'} =~ /opac-(.+)\.(?:tt|tmpl)$/ ) {
376 unless ( $pagename =~ /^(?:MARC|ISBD)?detail$/
377 or $pagename =~ /^addbybiblionumber$/ ) {
378 my $sessionSearch = get_session($sessionID || $in->{'query'}->cookie("CGISESSID"));
379 $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 ( $in->{'query'}->param('multibranchlimit') ) {
389 $opac_name = $in->{'query'}->param('multibranchlimit');
390 } elsif (C4::Context->preference("SearchMyLibraryFirst") && C4::Context->userenv && C4::Context->userenv->{'branch'}) {
391 $opac_name = C4::Context->userenv->{'branch'};
394 opaccolorstylesheet => C4::Context->preference("opaccolorstylesheet"),
395 AnonSuggestions => "" . C4::Context->preference("AnonSuggestions"),
396 AuthorisedValueImages => C4::Context->preference("AuthorisedValueImages"),
397 BranchesLoop => GetBranchesLoop($opac_name),
398 BranchCategoriesLoop => GetBranchCategories( 'searchdomain', 1, $opac_name ),
399 CalendarFirstDayOfWeek => (C4::Context->preference("CalendarFirstDayOfWeek") eq "Sunday")?0:1,
400 LibraryName => "" . C4::Context->preference("LibraryName"),
401 LibraryNameTitle => "" . $LibraryNameTitle,
402 LoginBranchname => C4::Context->userenv?C4::Context->userenv->{"branchname"}:"",
403 OPACAmazonCoverImages => C4::Context->preference("OPACAmazonCoverImages"),
404 OPACFRBRizeEditions => C4::Context->preference("OPACFRBRizeEditions"),
405 OpacHighlightedWords => C4::Context->preference("OpacHighlightedWords"),
406 OPACItemHolds => C4::Context->preference("OPACItemHolds"),
407 OPACShelfBrowser => "". C4::Context->preference("OPACShelfBrowser"),
408 OPACURLOpenInNewWindow => "" . C4::Context->preference("OPACURLOpenInNewWindow"),
409 OPACUserCSS => "". C4::Context->preference("OPACUserCSS"),
410 OPACMobileUserCSS => "". C4::Context->preference("OPACMobileUserCSS"),
411 OPACViewOthersSuggestions => "" . C4::Context->preference("OPACViewOthersSuggestions"),
412 OpacAuthorities => C4::Context->preference("OpacAuthorities"),
413 OPACBaseURL => ($in->{'query'}->https() ? "https://" : "http://") . $ENV{'SERVER_NAME'} .
414 ($ENV{'SERVER_PORT'} eq ($in->{'query'}->https() ? "443" : "80") ? '' : ":$ENV{'SERVER_PORT'}"),
415 opac_css_override => $ENV{'OPAC_CSS_OVERRIDE'},
416 opac_search_limit => $opac_search_limit,
417 opac_limit_override => $opac_limit_override,
418 OpacBrowser => C4::Context->preference("OpacBrowser"),
419 OpacCloud => C4::Context->preference("OpacCloud"),
420 OpacKohaUrl => C4::Context->preference("OpacKohaUrl"),
421 OpacMainUserBlock => "" . C4::Context->preference("OpacMainUserBlock"),
422 OpacMainUserBlockMobile => "" . C4::Context->preference("OpacMainUserBlockMobile"),
423 OpacShowFiltersPulldownMobile => C4::Context->preference("OpacShowFiltersPulldownMobile"),
424 OpacShowLibrariesPulldownMobile => C4::Context->preference("OpacShowLibrariesPulldownMobile"),
425 OpacNav => "" . C4::Context->preference("OpacNav"),
426 OpacNavRight => "" . C4::Context->preference("OpacNavRight"),
427 OpacNavBottom => "" . C4::Context->preference("OpacNavBottom"),
428 OpacPasswordChange => C4::Context->preference("OpacPasswordChange"),
429 OPACPatronDetails => C4::Context->preference("OPACPatronDetails"),
430 OPACPrivacy => C4::Context->preference("OPACPrivacy"),
431 OPACFinesTab => C4::Context->preference("OPACFinesTab"),
432 OpacTopissue => C4::Context->preference("OpacTopissue"),
433 RequestOnOpac => C4::Context->preference("RequestOnOpac"),
434 'Version' => C4::Context->preference('Version'),
435 hidelostitems => C4::Context->preference("hidelostitems"),
436 mylibraryfirst => (C4::Context->preference("SearchMyLibraryFirst") && C4::Context->userenv) ? C4::Context->userenv->{'branch'} : '',
437 opaclayoutstylesheet => "" . C4::Context->preference("opaclayoutstylesheet"),
438 opacbookbag => "" . C4::Context->preference("opacbookbag"),
439 opaccredits => "" . C4::Context->preference("opaccredits"),
440 OpacFavicon => C4::Context->preference("OpacFavicon"),
441 opacheader => "" . C4::Context->preference("opacheader"),
442 opaclanguagesdisplay => "" . C4::Context->preference("opaclanguagesdisplay"),
443 opacreadinghistory => C4::Context->preference("opacreadinghistory"),
444 opacsmallimage => "" . C4::Context->preference("opacsmallimage"),
445 opacuserjs => C4::Context->preference("opacuserjs"),
446 opacuserlogin => "" . C4::Context->preference("opacuserlogin"),
447 ShowReviewer => C4::Context->preference("ShowReviewer"),
448 ShowReviewerPhoto => C4::Context->preference("ShowReviewerPhoto"),
449 suggestion => "" . C4::Context->preference("suggestion"),
450 virtualshelves => "" . C4::Context->preference("virtualshelves"),
451 OPACSerialIssueDisplayCount => C4::Context->preference("OPACSerialIssueDisplayCount"),
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"));
474 # Check if we were asked using parameters to force a specific language
475 if ( defined $in->{'query'}->param('language') ) {
476 # Extract the language, let C4::Templates::getlanguage choose
478 my $language = C4::Templates::getlanguage($in->{'query'},$in->{'type'});
479 my $languagecookie = C4::Templates::getlanguagecookie($in->{'query'},$language);
480 if ( ref $cookie eq 'ARRAY' ) {
481 push @{ $cookie }, $languagecookie;
483 $cookie = [$cookie, $languagecookie];
487 return ( $template, $borrowernumber, $cookie, $flags);
492 ($userid, $cookie, $sessionID) = &checkauth($query, $noauth, $flagsrequired, $type);
494 Verifies that the user is authorized to run this script. If
495 the user is authorized, a (userid, cookie, session-id, flags)
496 quadruple is returned. If the user is not authorized but does
497 not have the required privilege (see $flagsrequired below), it
498 displays an error page and exits. Otherwise, it displays the
499 login page and exits.
501 Note that C<&checkauth> will return if and only if the user
502 is authorized, so it should be called early on, before any
503 unfinished operations (e.g., if you've opened a file, then
504 C<&checkauth> won't close it for you).
506 C<$query> is the CGI object for the script calling C<&checkauth>.
508 The C<$noauth> argument is optional. If it is set, then no
509 authorization is required for the script.
511 C<&checkauth> fetches user and session information from C<$query> and
512 ensures that the user is authorized to run scripts that require
515 The C<$flagsrequired> argument specifies the required privileges
516 the user must have if the username and password are correct.
517 It should be specified as a reference-to-hash; keys in the hash
518 should be the "flags" for the user, as specified in the Members
519 intranet module. Any key specified must correspond to a "flag"
520 in the userflags table. E.g., { circulate => 1 } would specify
521 that the user must have the "circulate" privilege in order to
522 proceed. To make sure that access control is correct, the
523 C<$flagsrequired> parameter must be specified correctly.
525 Koha also has a concept of sub-permissions, also known as
526 granular permissions. This makes the value of each key
527 in the C<flagsrequired> hash take on an additional
532 The user must have access to all subfunctions of the module
533 specified by the hash key.
537 The user must have access to at least one subfunction of the module
538 specified by the hash key.
540 specific permission, e.g., 'export_catalog'
542 The user must have access to the specific subfunction list, which
543 must correspond to a row in the permissions table.
545 The C<$type> argument specifies whether the template should be
546 retrieved from the opac or intranet directory tree. "opac" is
547 assumed if it is not specified; however, if C<$type> is specified,
548 "intranet" is assumed if it is not "opac".
550 If C<$query> does not have a valid session ID associated with it
551 (i.e., the user has not logged in) or if the session has expired,
552 C<&checkauth> presents the user with a login page (from the point of
553 view of the original script, C<&checkauth> does not return). Once the
554 user has authenticated, C<&checkauth> restarts the original script
555 (this time, C<&checkauth> returns).
557 The login page is provided using a HTML::Template, which is set in the
558 systempreferences table or at the top of this file. The variable C<$type>
559 selects which template to use, either the opac or the intranet
560 authentification template.
562 C<&checkauth> returns a user ID, a cookie, and a session ID. The
563 cookie should be sent back to the browser; it verifies that the user
572 # If Version syspref is unavailable, it means Koha is beeing installed,
573 # and so we must redirect to OPAC maintenance page or to the WebInstaller
574 # also, if OpacMaintenance is ON, OPAC should redirect to maintenance
575 if (C4::Context->preference('OpacMaintenance') && $type eq 'opac') {
576 warn "OPAC Install required, redirecting to maintenance";
577 print $query->redirect("/cgi-bin/koha/maintenance.pl");
580 unless ( $version = C4::Context->preference('Version') ) { # assignment, not comparison
581 if ( $type ne 'opac' ) {
582 warn "Install required, redirecting to Installer";
583 print $query->redirect("/cgi-bin/koha/installer/install.pl");
585 warn "OPAC Install required, redirecting to maintenance";
586 print $query->redirect("/cgi-bin/koha/maintenance.pl");
591 # check that database and koha version are the same
592 # there is no DB version, it's a fresh install,
593 # go to web installer
594 # there is a DB version, compare it to the code version
595 my $kohaversion=C4::Context::KOHAVERSION;
596 # remove the 3 last . to have a Perl number
597 $kohaversion =~ s/(.*\..*)\.(.*)\.(.*)/$1$2$3/;
598 $debug and print STDERR "kohaversion : $kohaversion\n";
599 if ($version < $kohaversion){
600 my $warning = "Database update needed, redirecting to %s. Database is $version and Koha is $kohaversion";
601 if ($type ne 'opac'){
602 warn sprintf($warning, 'Installer');
603 print $query->redirect("/cgi-bin/koha/installer/install.pl?step=3");
605 warn sprintf("OPAC: " . $warning, 'maintenance');
606 print $query->redirect("/cgi-bin/koha/maintenance.pl");
614 open my $fh, '>>', "/tmp/sessionlog" or warn "ERROR: Cannot append to /tmp/sessionlog";
615 printf $fh join("\n",@_);
619 sub _timeout_syspref {
620 my $timeout = C4::Context->preference('timeout') || 600;
621 # value in days, convert in seconds
622 if ($timeout =~ /(\d+)[dD]/) {
623 $timeout = $1 * 86400;
630 $debug and warn "Checking Auth";
631 # $authnotrequired will be set for scripts which will run without authentication
632 my $authnotrequired = shift;
633 my $flagsrequired = shift;
636 $type = 'opac' unless $type;
638 my $dbh = C4::Context->dbh;
639 my $timeout = _timeout_syspref();
641 _version_check($type,$query);
645 my ( $userid, $cookie, $sessionID, $flags, $barshelves, $pubshelves );
646 my $logout = $query->param('logout.x');
648 my $anon_search_history;
650 # This parameter is the name of the CAS server we want to authenticate against,
651 # when using authentication against multiple CAS servers, as configured in Auth_cas_servers.yaml
652 my $casparam = $query->param('cas');
653 my $q_userid = $query->param('userid') // '';
655 if ( $userid = $ENV{'REMOTE_USER'} ) {
656 # Using Basic Authentication, no cookies required
657 $cookie = $query->cookie(
658 -name => 'CGISESSID',
666 # we dont want to set a session because we are being called by a persona callback
668 elsif ( $sessionID = $query->cookie("CGISESSID") )
669 { # assignment, not comparison
670 my $session = get_session($sessionID);
671 C4::Context->_new_userenv($sessionID);
672 my ($ip, $lasttime, $sessiontype);
675 $s_userid = $session->param('id') // '';
676 C4::Context::set_userenv(
677 $session->param('number'), $s_userid,
678 $session->param('cardnumber'), $session->param('firstname'),
679 $session->param('surname'), $session->param('branch'),
680 $session->param('branchname'), $session->param('flags'),
681 $session->param('emailaddress'), $session->param('branchprinter'),
682 $session->param('persona')
684 C4::Context::set_shelves_userenv('bar',$session->param('barshelves'));
685 C4::Context::set_shelves_userenv('pub',$session->param('pubshelves'));
686 C4::Context::set_shelves_userenv('tot',$session->param('totshelves'));
687 $debug and printf STDERR "AUTH_SESSION: (%s)\t%s %s - %s\n", map {$session->param($_)} qw(cardnumber firstname surname branch) ;
688 $ip = $session->param('ip');
689 $lasttime = $session->param('lasttime');
691 $sessiontype = $session->param('sessiontype') || '';
693 if ( ( $query->param('koha_login_context') && ($q_userid ne $s_userid) )
694 || ( $cas && $query->param('ticket') ) ) {
695 #if a user enters an id ne to the id in the current session, we need to log them in...
696 #first we need to clear the anonymous session...
697 $debug and warn "query id = $q_userid but session id = $s_userid";
698 $anon_search_history = $session->param('search_history');
701 C4::Context->_unset_userenv($sessionID);
706 # voluntary logout the user
709 C4::Context->_unset_userenv($sessionID);
710 #_session_log(sprintf "%20s from %16s logged out at %30s (manually).\n", $userid,$ip,(strftime "%c",localtime));
714 if ($cas and $caslogout) {
718 elsif ( !$lasttime || ($lasttime < time() - $timeout) ) {
720 $info{'timed_out'} = 1;
725 C4::Context->_unset_userenv($sessionID);
726 #_session_log(sprintf "%20s from %16s logged out at %30s (inactivity).\n", $userid,$ip,(strftime "%c",localtime));
730 elsif ( $ip ne $ENV{'REMOTE_ADDR'} ) {
731 # Different ip than originally logged in from
732 $info{'oldip'} = $ip;
733 $info{'newip'} = $ENV{'REMOTE_ADDR'};
734 $info{'different_ip'} = 1;
737 C4::Context->_unset_userenv($sessionID);
738 #_session_log(sprintf "%20s from %16s logged out at %30s (ip changed to %16s).\n", $userid,$ip,(strftime "%c",localtime), $info{'newip'});
743 $cookie = $query->cookie(
744 -name => 'CGISESSID',
745 -value => $session->id,
748 $session->param( 'lasttime', time() );
749 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...
750 $flags = haspermission($userid, $flagsrequired);
754 $info{'nopermission'} = 1;
759 unless ($userid || $sessionID) {
761 #we initiate a session prior to checking for a username to allow for anonymous sessions...
762 my $session = get_session("") or die "Auth ERROR: Cannot get_session()";
764 # Save anonymous search history in new session so it can be retrieved
765 # by get_template_and_user to store it in user's search history after
766 # a successful login.
767 if ($anon_search_history) {
768 $session->param('search_history', $anon_search_history);
771 my $sessionID = $session->id;
772 C4::Context->_new_userenv($sessionID);
773 $cookie = $query->cookie(
774 -name => 'CGISESSID',
775 -value => $session->id,
779 my $pki_field = C4::Context->preference('AllowPKIAuth');
780 if (! defined($pki_field) ) {
781 print STDERR "ERROR: Missing system preference AllowPKIAuth.\n";
784 if ( ( $cas && $query->param('ticket') )
786 || $pki_field ne 'None'
789 my $password = $query->param('password');
791 my ( $return, $cardnumber );
792 if ( $cas && $query->param('ticket') ) {
794 ( $return, $cardnumber, $retuserid ) =
795 checkpw( $dbh, $userid, $password, $query );
796 $userid = $retuserid;
797 $info{'invalidCasLogin'} = 1 unless ($return);
801 my $value = $persona;
803 # If we're looking up the email, there's a chance that the person
804 # doesn't have a userid. So if there is none, we pass along the
805 # borrower number, and the bits of code that need to know the user
806 # ID will have to be smart enough to handle that.
808 my @users_info = C4::Members::GetBorrowersWithEmail($value);
811 # First the userid, then the borrowernum
812 $value = $users_info[0][1] || $users_info[0][0];
817 $return = $value ? 1 : 0;
822 ( $pki_field eq 'Common Name' && $ENV{'SSL_CLIENT_S_DN_CN'} )
823 || ( $pki_field eq 'emailAddress'
824 && $ENV{'SSL_CLIENT_S_DN_Email'} )
828 if ( $pki_field eq 'Common Name' ) {
829 $value = $ENV{'SSL_CLIENT_S_DN_CN'};
831 elsif ( $pki_field eq 'emailAddress' ) {
832 $value = $ENV{'SSL_CLIENT_S_DN_Email'};
834 # If we're looking up the email, there's a chance that the person
835 # doesn't have a userid. So if there is none, we pass along the
836 # borrower number, and the bits of code that need to know the user
837 # ID will have to be smart enough to handle that.
839 my @users_info = C4::Members::GetBorrowersWithEmail($value);
842 # First the userid, then the borrowernum
843 $value = $users_info[0][1] || $users_info[0][0];
850 $return = $value ? 1 : 0;
856 ( $return, $cardnumber, $retuserid ) =
857 checkpw( $dbh, $userid, $password, $query );
858 $userid = $retuserid if ( $retuserid );
861 #_session_log(sprintf "%20s from %16s logged in at %30s.\n", $userid,$ENV{'REMOTE_ADDR'},(strftime '%c', localtime));
862 if ( $flags = haspermission( $userid, $flagsrequired ) ) {
866 $info{'nopermission'} = 1;
867 C4::Context->_unset_userenv($sessionID);
869 my ($borrowernumber, $firstname, $surname, $userflags,
870 $branchcode, $branchname, $branchprinter, $emailaddress);
872 if ( $return == 1 ) {
874 SELECT borrowernumber, firstname, surname, flags, borrowers.branchcode,
875 branches.branchname as branchname,
876 branches.branchprinter as branchprinter,
879 LEFT JOIN branches on borrowers.branchcode=branches.branchcode
881 my $sth = $dbh->prepare("$select where userid=?");
882 $sth->execute($userid);
883 unless ($sth->rows) {
884 $debug and print STDERR "AUTH_1: no rows for userid='$userid'\n";
885 $sth = $dbh->prepare("$select where cardnumber=?");
886 $sth->execute($cardnumber);
888 unless ($sth->rows) {
889 $debug and print STDERR "AUTH_2a: no rows for cardnumber='$cardnumber'\n";
890 $sth->execute($userid);
891 unless ($sth->rows) {
892 $debug and print STDERR "AUTH_2b: no rows for userid='$userid' AS cardnumber\n";
897 ($borrowernumber, $firstname, $surname, $userflags,
898 $branchcode, $branchname, $branchprinter, $emailaddress) = $sth->fetchrow;
899 $debug and print STDERR "AUTH_3 results: " .
900 "$cardnumber,$borrowernumber,$userid,$firstname,$surname,$userflags,$branchcode,$emailaddress\n";
902 print STDERR "AUTH_3: no results for userid='$userid', cardnumber='$cardnumber'.\n";
905 # launch a sequence to check if we have a ip for the branch, i
906 # if we have one we replace the branchcode of the userenv by the branch bound in the ip.
908 my $ip = $ENV{'REMOTE_ADDR'};
909 # if they specify at login, use that
910 if ($query->param('branch')) {
911 $branchcode = $query->param('branch');
912 $branchname = GetBranchName($branchcode);
914 my $branches = GetBranches();
915 if (C4::Context->boolean_preference('IndependentBranches') && C4::Context->boolean_preference('Autolocation')){
916 # we have to check they are coming from the right ip range
917 my $domain = $branches->{$branchcode}->{'branchip'};
918 if ($ip !~ /^$domain/){
920 $info{'wrongip'} = 1;
925 foreach my $br ( keys %$branches ) {
926 # now we work with the treatment of ip
927 my $domain = $branches->{$br}->{'branchip'};
928 if ( $domain && $ip =~ /^$domain/ ) {
929 $branchcode = $branches->{$br}->{'branchcode'};
931 # new op dev : add the branchprinter and branchname in the cookie
932 $branchprinter = $branches->{$br}->{'branchprinter'};
933 $branchname = $branches->{$br}->{'branchname'};
936 $session->param('number',$borrowernumber);
937 $session->param('id',$userid);
938 $session->param('cardnumber',$cardnumber);
939 $session->param('firstname',$firstname);
940 $session->param('surname',$surname);
941 $session->param('branch',$branchcode);
942 $session->param('branchname',$branchname);
943 $session->param('flags',$userflags);
944 $session->param('emailaddress',$emailaddress);
945 $session->param('ip',$session->remote_addr());
946 $session->param('lasttime',time());
947 $debug and printf STDERR "AUTH_4: (%s)\t%s %s - %s\n", map {$session->param($_)} qw(cardnumber firstname surname branch) ;
949 elsif ( $return == 2 ) {
950 #We suppose the user is the superlibrarian
952 $session->param('number',0);
953 $session->param('id',C4::Context->config('user'));
954 $session->param('cardnumber',C4::Context->config('user'));
955 $session->param('firstname',C4::Context->config('user'));
956 $session->param('surname',C4::Context->config('user'));
957 $session->param('branch','NO_LIBRARY_SET');
958 $session->param('branchname','NO_LIBRARY_SET');
959 $session->param('flags',1);
960 $session->param('emailaddress', C4::Context->preference('KohaAdminEmailAddress'));
961 $session->param('ip',$session->remote_addr());
962 $session->param('lasttime',time());
965 $session->param('persona',1);
967 C4::Context::set_userenv(
968 $session->param('number'), $session->param('id'),
969 $session->param('cardnumber'), $session->param('firstname'),
970 $session->param('surname'), $session->param('branch'),
971 $session->param('branchname'), $session->param('flags'),
972 $session->param('emailaddress'), $session->param('branchprinter'),
973 $session->param('persona')
979 $info{'invalid_username_or_password'} = 1;
980 C4::Context->_unset_userenv($sessionID);
982 $session->param('lasttime',time());
983 $session->param('ip',$session->remote_addr());
985 } # END if ( $userid = $query->param('userid') )
986 elsif ($type eq "opac") {
987 # if we are here this is an anonymous session; add public lists to it and a few other items...
988 # anonymous sessions are created only for the OPAC
989 $debug and warn "Initiating an anonymous session...";
991 # setting a couple of other session vars...
992 $session->param('ip',$session->remote_addr());
993 $session->param('lasttime',time());
994 $session->param('sessiontype','anon');
996 } # END unless ($userid)
998 # finished authentification, now respond
999 if ( $loggedin || $authnotrequired )
1003 $cookie = $query->cookie(
1004 -name => 'CGISESSID',
1009 return ( $userid, $cookie, $sessionID, $flags );
1014 # AUTH rejected, show the login/password template, after checking the DB.
1018 # get the inputs from the incoming query
1020 foreach my $name ( param $query) {
1021 (next) if ( $name eq 'userid' || $name eq 'password' || $name eq 'ticket' );
1022 my $value = $query->param($name);
1023 push @inputs, { name => $name, value => $value };
1026 my $LibraryNameTitle = C4::Context->preference("LibraryName");
1027 $LibraryNameTitle =~ s/<(?:\/?)(?:br|p)\s*(?:\/?)>/ /sgi;
1028 $LibraryNameTitle =~ s/<(?:[^<>'"]|'(?:[^']*)'|"(?:[^"]*)")*>//sg;
1030 my $template_name = ( $type eq 'opac' ) ? 'opac-auth.tmpl' : 'auth.tmpl';
1031 my $template = C4::Templates::gettemplate($template_name, $type, $query );
1033 branchloop => GetBranchesLoop(),
1034 opaccolorstylesheet => C4::Context->preference("opaccolorstylesheet"),
1035 opaclayoutstylesheet => C4::Context->preference("opaclayoutstylesheet"),
1038 casAuthentication => C4::Context->preference("casAuthentication"),
1039 suggestion => C4::Context->preference("suggestion"),
1040 virtualshelves => C4::Context->preference("virtualshelves"),
1041 LibraryName => "" . C4::Context->preference("LibraryName"),
1042 LibraryNameTitle => "" . $LibraryNameTitle,
1043 opacuserlogin => C4::Context->preference("opacuserlogin"),
1044 OpacNav => C4::Context->preference("OpacNav"),
1045 OpacNavRight => C4::Context->preference("OpacNavRight"),
1046 OpacNavBottom => C4::Context->preference("OpacNavBottom"),
1047 opaccredits => C4::Context->preference("opaccredits"),
1048 OpacFavicon => C4::Context->preference("OpacFavicon"),
1049 opacreadinghistory => C4::Context->preference("opacreadinghistory"),
1050 opacsmallimage => C4::Context->preference("opacsmallimage"),
1051 opaclanguagesdisplay => C4::Context->preference("opaclanguagesdisplay"),
1052 opacuserjs => C4::Context->preference("opacuserjs"),
1053 opacbookbag => "" . C4::Context->preference("opacbookbag"),
1054 OpacCloud => C4::Context->preference("OpacCloud"),
1055 OpacTopissue => C4::Context->preference("OpacTopissue"),
1056 OpacAuthorities => C4::Context->preference("OpacAuthorities"),
1057 OpacBrowser => C4::Context->preference("OpacBrowser"),
1058 opacheader => C4::Context->preference("opacheader"),
1059 TagsEnabled => C4::Context->preference("TagsEnabled"),
1060 OPACUserCSS => C4::Context->preference("OPACUserCSS"),
1061 intranetcolorstylesheet => C4::Context->preference("intranetcolorstylesheet"),
1062 intranetstylesheet => C4::Context->preference("intranetstylesheet"),
1063 intranetbookbag => C4::Context->preference("intranetbookbag"),
1064 IntranetNav => C4::Context->preference("IntranetNav"),
1065 IntranetFavicon => C4::Context->preference("IntranetFavicon"),
1066 intranetuserjs => C4::Context->preference("intranetuserjs"),
1067 IndependentBranches=> C4::Context->preference("IndependentBranches"),
1068 AutoLocation => C4::Context->preference("AutoLocation"),
1069 wrongip => $info{'wrongip'},
1070 PatronSelfRegistration => C4::Context->preference("PatronSelfRegistration"),
1071 PatronSelfRegistrationDefaultCategory => C4::Context->preference("PatronSelfRegistrationDefaultCategory"),
1072 persona => C4::Context->preference("Persona"),
1073 opac_css_override => $ENV{'OPAC_CSS_OVERRIDE'},
1076 $template->param( OpacPublic => C4::Context->preference("OpacPublic"));
1077 $template->param( loginprompt => 1 ) unless $info{'nopermission'};
1079 if($type eq 'opac'){
1080 my ($total, $pubshelves) = C4::VirtualShelves::GetSomeShelfNames(undef, 'MASTHEAD');
1082 pubshelves => $total->{pubtotal},
1083 pubshelvesloop => $pubshelves,
1089 # Is authentication against multiple CAS servers enabled?
1090 if (C4::Auth_with_cas::multipleAuth && !$casparam) {
1091 my $casservers = C4::Auth_with_cas::getMultipleAuth();
1093 foreach my $key (keys %$casservers) {
1094 push @tmplservers, {name => $key, value => login_cas_url($query, $key) . "?cas=$key" };
1097 casServersLoop => \@tmplservers
1101 casServerUrl => login_cas_url($query),
1106 invalidCasLogin => $info{'invalidCasLogin'}
1110 my $self_url = $query->url( -absolute => 1 );
1113 LibraryName => C4::Context->preference("LibraryName"),
1115 $template->param( %info );
1116 # $cookie = $query->cookie(CGISESSID => $session->id
1118 print $query->header(
1119 -type => 'text/html',
1120 -charset => 'utf-8',
1127 =head2 check_api_auth
1129 ($status, $cookie, $sessionId) = check_api_auth($query, $userflags);
1131 Given a CGI query containing the parameters 'userid' and 'password' and/or a session
1132 cookie, determine if the user has the privileges specified by C<$userflags>.
1134 C<check_api_auth> is is meant for authenticating users of web services, and
1135 consequently will always return and will not attempt to redirect the user
1138 If a valid session cookie is already present, check_api_auth will return a status
1139 of "ok", the cookie, and the Koha session ID.
1141 If no session cookie is present, check_api_auth will check the 'userid' and 'password
1142 parameters and create a session cookie and Koha session if the supplied credentials
1145 Possible return values in C<$status> are:
1149 =item "ok" -- user authenticated; C<$cookie> and C<$sessionid> have valid values.
1151 =item "failed" -- credentials are not correct; C<$cookie> and C<$sessionid> are undef
1153 =item "maintenance" -- DB is in maintenance mode; no login possible at the moment
1155 =item "expired -- session cookie has expired; API user should resubmit userid and password
1161 sub check_api_auth {
1163 my $flagsrequired = shift;
1165 my $dbh = C4::Context->dbh;
1166 my $timeout = _timeout_syspref();
1168 unless (C4::Context->preference('Version')) {
1169 # database has not been installed yet
1170 return ("maintenance", undef, undef);
1172 my $kohaversion=C4::Context::KOHAVERSION;
1173 $kohaversion =~ s/(.*\..*)\.(.*)\.(.*)/$1$2$3/;
1174 if (C4::Context->preference('Version') < $kohaversion) {
1175 # database in need of version update; assume that
1176 # no API should be called while databsae is in
1178 return ("maintenance", undef, undef);
1181 # FIXME -- most of what follows is a copy-and-paste
1182 # of code from checkauth. There is an obvious need
1183 # for refactoring to separate the various parts of
1184 # the authentication code, but as of 2007-11-19 this
1185 # is deferred so as to not introduce bugs into the
1186 # regular authentication code for Koha 3.0.
1188 # see if we have a valid session cookie already
1189 # however, if a userid parameter is present (i.e., from
1190 # a form submission, assume that any current cookie
1192 my $sessionID = undef;
1193 unless ($query->param('userid')) {
1194 $sessionID = $query->cookie("CGISESSID");
1196 if ($sessionID && not ($cas && $query->param('PT')) ) {
1197 my $session = get_session($sessionID);
1198 C4::Context->_new_userenv($sessionID);
1200 C4::Context::set_userenv(
1201 $session->param('number'), $session->param('id'),
1202 $session->param('cardnumber'), $session->param('firstname'),
1203 $session->param('surname'), $session->param('branch'),
1204 $session->param('branchname'), $session->param('flags'),
1205 $session->param('emailaddress'), $session->param('branchprinter')
1208 my $ip = $session->param('ip');
1209 my $lasttime = $session->param('lasttime');
1210 my $userid = $session->param('id');
1211 if ( $lasttime < time() - $timeout ) {
1215 C4::Context->_unset_userenv($sessionID);
1218 return ("expired", undef, undef);
1219 } elsif ( $ip ne $ENV{'REMOTE_ADDR'} ) {
1220 # IP address changed
1223 C4::Context->_unset_userenv($sessionID);
1226 return ("expired", undef, undef);
1228 my $cookie = $query->cookie(
1229 -name => 'CGISESSID',
1230 -value => $session->id,
1233 $session->param('lasttime',time());
1234 my $flags = haspermission($userid, $flagsrequired);
1236 return ("ok", $cookie, $sessionID);
1240 C4::Context->_unset_userenv($sessionID);
1243 return ("failed", undef, undef);
1247 return ("expired", undef, undef);
1251 my $userid = $query->param('userid');
1252 my $password = $query->param('password');
1253 my ($return, $cardnumber);
1256 if ($cas && $query->param('PT')) {
1258 $debug and print STDERR "## check_api_auth - checking CAS\n";
1259 # In case of a CAS authentication, we use the ticket instead of the password
1260 my $PT = $query->param('PT');
1261 ($return,$cardnumber,$userid) = check_api_auth_cas($dbh, $PT, $query); # EXTERNAL AUTH
1263 # User / password auth
1264 unless ($userid and $password) {
1265 # caller did something wrong, fail the authenticateion
1266 return ("failed", undef, undef);
1268 ( $return, $cardnumber ) = checkpw( $dbh, $userid, $password, $query );
1271 if ($return and haspermission( $userid, $flagsrequired)) {
1272 my $session = get_session("");
1273 return ("failed", undef, undef) unless $session;
1275 my $sessionID = $session->id;
1276 C4::Context->_new_userenv($sessionID);
1277 my $cookie = $query->cookie(
1278 -name => 'CGISESSID',
1279 -value => $sessionID,
1282 if ( $return == 1 ) {
1284 $borrowernumber, $firstname, $surname,
1285 $userflags, $branchcode, $branchname,
1286 $branchprinter, $emailaddress
1290 "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=?"
1292 $sth->execute($userid);
1294 $borrowernumber, $firstname, $surname,
1295 $userflags, $branchcode, $branchname,
1296 $branchprinter, $emailaddress
1297 ) = $sth->fetchrow if ( $sth->rows );
1299 unless ($sth->rows ) {
1300 my $sth = $dbh->prepare(
1301 "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=?"
1303 $sth->execute($cardnumber);
1305 $borrowernumber, $firstname, $surname,
1306 $userflags, $branchcode, $branchname,
1307 $branchprinter, $emailaddress
1308 ) = $sth->fetchrow if ( $sth->rows );
1310 unless ( $sth->rows ) {
1311 $sth->execute($userid);
1313 $borrowernumber, $firstname, $surname, $userflags,
1314 $branchcode, $branchname, $branchprinter, $emailaddress
1315 ) = $sth->fetchrow if ( $sth->rows );
1319 my $ip = $ENV{'REMOTE_ADDR'};
1320 # if they specify at login, use that
1321 if ($query->param('branch')) {
1322 $branchcode = $query->param('branch');
1323 $branchname = GetBranchName($branchcode);
1325 my $branches = GetBranches();
1327 foreach my $br ( keys %$branches ) {
1328 # now we work with the treatment of ip
1329 my $domain = $branches->{$br}->{'branchip'};
1330 if ( $domain && $ip =~ /^$domain/ ) {
1331 $branchcode = $branches->{$br}->{'branchcode'};
1333 # new op dev : add the branchprinter and branchname in the cookie
1334 $branchprinter = $branches->{$br}->{'branchprinter'};
1335 $branchname = $branches->{$br}->{'branchname'};
1338 $session->param('number',$borrowernumber);
1339 $session->param('id',$userid);
1340 $session->param('cardnumber',$cardnumber);
1341 $session->param('firstname',$firstname);
1342 $session->param('surname',$surname);
1343 $session->param('branch',$branchcode);
1344 $session->param('branchname',$branchname);
1345 $session->param('flags',$userflags);
1346 $session->param('emailaddress',$emailaddress);
1347 $session->param('ip',$session->remote_addr());
1348 $session->param('lasttime',time());
1349 } elsif ( $return == 2 ) {
1350 #We suppose the user is the superlibrarian
1351 $session->param('number',0);
1352 $session->param('id',C4::Context->config('user'));
1353 $session->param('cardnumber',C4::Context->config('user'));
1354 $session->param('firstname',C4::Context->config('user'));
1355 $session->param('surname',C4::Context->config('user'));
1356 $session->param('branch','NO_LIBRARY_SET');
1357 $session->param('branchname','NO_LIBRARY_SET');
1358 $session->param('flags',1);
1359 $session->param('emailaddress', C4::Context->preference('KohaAdminEmailAddress'));
1360 $session->param('ip',$session->remote_addr());
1361 $session->param('lasttime',time());
1363 C4::Context::set_userenv(
1364 $session->param('number'), $session->param('id'),
1365 $session->param('cardnumber'), $session->param('firstname'),
1366 $session->param('surname'), $session->param('branch'),
1367 $session->param('branchname'), $session->param('flags'),
1368 $session->param('emailaddress'), $session->param('branchprinter')
1370 return ("ok", $cookie, $sessionID);
1372 return ("failed", undef, undef);
1377 =head2 check_cookie_auth
1379 ($status, $sessionId) = check_api_auth($cookie, $userflags);
1381 Given a CGISESSID cookie set during a previous login to Koha, determine
1382 if the user has the privileges specified by C<$userflags>.
1384 C<check_cookie_auth> is meant for authenticating special services
1385 such as tools/upload-file.pl that are invoked by other pages that
1386 have been authenticated in the usual way.
1388 Possible return values in C<$status> are:
1392 =item "ok" -- user authenticated; C<$sessionID> have valid values.
1394 =item "failed" -- credentials are not correct; C<$sessionid> are undef
1396 =item "maintenance" -- DB is in maintenance mode; no login possible at the moment
1398 =item "expired -- session cookie has expired; API user should resubmit userid and password
1404 sub check_cookie_auth {
1406 my $flagsrequired = shift;
1408 my $dbh = C4::Context->dbh;
1409 my $timeout = _timeout_syspref();
1411 unless (C4::Context->preference('Version')) {
1412 # database has not been installed yet
1413 return ("maintenance", undef);
1415 my $kohaversion=C4::Context::KOHAVERSION;
1416 $kohaversion =~ s/(.*\..*)\.(.*)\.(.*)/$1$2$3/;
1417 if (C4::Context->preference('Version') < $kohaversion) {
1418 # database in need of version update; assume that
1419 # no API should be called while databsae is in
1421 return ("maintenance", undef);
1424 # FIXME -- most of what follows is a copy-and-paste
1425 # of code from checkauth. There is an obvious need
1426 # for refactoring to separate the various parts of
1427 # the authentication code, but as of 2007-11-23 this
1428 # is deferred so as to not introduce bugs into the
1429 # regular authentication code for Koha 3.0.
1431 # see if we have a valid session cookie already
1432 # however, if a userid parameter is present (i.e., from
1433 # a form submission, assume that any current cookie
1435 unless (defined $cookie and $cookie) {
1436 return ("failed", undef);
1438 my $sessionID = $cookie;
1439 my $session = get_session($sessionID);
1440 C4::Context->_new_userenv($sessionID);
1442 C4::Context::set_userenv(
1443 $session->param('number'), $session->param('id'),
1444 $session->param('cardnumber'), $session->param('firstname'),
1445 $session->param('surname'), $session->param('branch'),
1446 $session->param('branchname'), $session->param('flags'),
1447 $session->param('emailaddress'), $session->param('branchprinter')
1450 my $ip = $session->param('ip');
1451 my $lasttime = $session->param('lasttime');
1452 my $userid = $session->param('id');
1453 if ( $lasttime < time() - $timeout ) {
1457 C4::Context->_unset_userenv($sessionID);
1460 return ("expired", undef);
1461 } elsif ( $ip ne $ENV{'REMOTE_ADDR'} ) {
1462 # IP address changed
1465 C4::Context->_unset_userenv($sessionID);
1468 return ("expired", undef);
1470 $session->param('lasttime',time());
1471 my $flags = haspermission($userid, $flagsrequired);
1473 return ("ok", $sessionID);
1477 C4::Context->_unset_userenv($sessionID);
1480 return ("failed", undef);
1484 return ("expired", undef);
1491 my $session = get_session($sessionID);
1493 Given a session ID, retrieve the CGI::Session object used to store
1494 the session's state. The session object can be used to store
1495 data that needs to be accessed by different scripts during a
1498 If the C<$sessionID> parameter is an empty string, a new session
1504 my $sessionID = shift;
1505 my $storage_method = C4::Context->preference('SessionStorage');
1506 my $dbh = C4::Context->dbh;
1508 if ($storage_method eq 'mysql'){
1509 $session = new CGI::Session("driver:MySQL;serializer:yaml;id:md5", $sessionID, {Handle=>$dbh});
1511 elsif ($storage_method eq 'Pg') {
1512 $session = new CGI::Session("driver:PostgreSQL;serializer:yaml;id:md5", $sessionID, {Handle=>$dbh});
1514 elsif ($storage_method eq 'memcached' && C4::Context->ismemcached){
1515 $session = new CGI::Session("driver:memcached;serializer:yaml;id:md5", $sessionID, { Memcached => C4::Context->memcached } );
1518 # catch all defaults to tmp should work on all systems
1519 $session = new CGI::Session("driver:File;serializer:yaml;id:md5", $sessionID, {Directory=>'/tmp'});
1525 my ( $dbh, $userid, $password, $query ) = @_;
1528 $debug and print STDERR "## checkpw - checking LDAP\n";
1529 my ($retval,$retcard,$retuserid) = checkpw_ldap(@_); # EXTERNAL AUTH
1530 ($retval) and return ($retval,$retcard,$retuserid);
1533 if ($cas && $query && $query->param('ticket')) {
1534 $debug and print STDERR "## checkpw - checking CAS\n";
1535 # In case of a CAS authentication, we use the ticket instead of the password
1536 my $ticket = $query->param('ticket');
1537 my ($retval,$retcard,$retuserid) = checkpw_cas($dbh, $ticket, $query); # EXTERNAL AUTH
1538 ($retval) and return ($retval,$retcard,$retuserid);
1542 return checkpw_internal(@_)
1545 sub checkpw_internal {
1546 my ( $dbh, $userid, $password ) = @_;
1550 "select password,cardnumber,borrowernumber,userid,firstname,surname,branchcode,flags from borrowers where userid=?"
1552 $sth->execute($userid);
1554 my ( $stored_hash, $cardnumber, $borrowernumber, $userid, $firstname,
1555 $surname, $branchcode, $flags )
1558 if ( checkpw_hash($password, $stored_hash) ) {
1560 C4::Context->set_userenv( "$borrowernumber", $userid, $cardnumber,
1561 $firstname, $surname, $branchcode, $flags );
1562 return 1, $cardnumber, $userid;
1567 "select password,cardnumber,borrowernumber,userid, firstname,surname,branchcode,flags from borrowers where cardnumber=?"
1569 $sth->execute($userid);
1571 my ( $stored_hash, $cardnumber, $borrowernumber, $userid, $firstname,
1572 $surname, $branchcode, $flags )
1575 if ( checkpw_hash($password, $stored_hash) ) {
1577 C4::Context->set_userenv( $borrowernumber, $userid, $cardnumber,
1578 $firstname, $surname, $branchcode, $flags );
1579 return 1, $cardnumber, $userid;
1582 if ( $userid && $userid eq C4::Context->config('user')
1583 && "$password" eq C4::Context->config('pass') )
1586 # Koha superuser account
1587 # C4::Context->set_userenv(0,0,C4::Context->config('user'),C4::Context->config('user'),C4::Context->config('user'),"",1);
1590 if ( $userid && $userid eq 'demo'
1591 && "$password" eq 'demo'
1592 && C4::Context->config('demo') )
1595 # DEMO => the demo user is allowed to do everything (if demo set to 1 in koha.conf
1596 # some features won't be effective : modify systempref, modify MARC structure,
1603 my ( $password, $stored_hash ) = @_;
1605 return if $stored_hash eq '!';
1607 # check what encryption algorithm was implemented: Bcrypt - if the hash starts with '$2' it is Bcrypt else md5
1609 if ( substr($stored_hash,0,2) eq '$2') {
1610 $hash = hash_password($password, $stored_hash);
1612 $hash = md5_base64($password);
1614 return $hash eq $stored_hash;
1619 my $authflags = getuserflags($flags, $userid, [$dbh]);
1621 Translates integer flags into permissions strings hash.
1623 C<$flags> is the integer userflags value ( borrowers.userflags )
1624 C<$userid> is the members.userid, used for building subpermissions
1625 C<$authflags> is a hashref of permissions
1632 my $dbh = @_ ? shift : C4::Context->dbh;
1635 # I don't want to do this, but if someone logs in as the database
1636 # user, it would be preferable not to spam them to death with
1637 # numeric warnings. So, we make $flags numeric.
1638 no warnings 'numeric';
1641 my $sth = $dbh->prepare("SELECT bit, flag, defaulton FROM userflags");
1644 while ( my ( $bit, $flag, $defaulton ) = $sth->fetchrow ) {
1645 if ( ( $flags & ( 2**$bit ) ) || $defaulton ) {
1646 $userflags->{$flag} = 1;
1649 $userflags->{$flag} = 0;
1652 # get subpermissions and merge with top-level permissions
1653 my $user_subperms = get_user_subpermissions($userid);
1654 foreach my $module (keys %$user_subperms) {
1655 next if $userflags->{$module} == 1; # user already has permission for everything in this module
1656 $userflags->{$module} = $user_subperms->{$module};
1662 =head2 get_user_subpermissions
1664 $user_perm_hashref = get_user_subpermissions($userid);
1666 Given the userid (note, not the borrowernumber) of a staff user,
1667 return a hashref of hashrefs of the specific subpermissions
1668 accorded to the user. An example return is
1672 export_catalog => 1,
1673 import_patrons => 1,
1677 The top-level hash-key is a module or function code from
1678 userflags.flag, while the second-level key is a code
1681 The results of this function do not give a complete picture
1682 of the functions that a staff user can access; it is also
1683 necessary to check borrowers.flags.
1687 sub get_user_subpermissions {
1690 my $dbh = C4::Context->dbh;
1691 my $sth = $dbh->prepare("SELECT flag, user_permissions.code
1692 FROM user_permissions
1693 JOIN permissions USING (module_bit, code)
1694 JOIN userflags ON (module_bit = bit)
1695 JOIN borrowers USING (borrowernumber)
1697 $sth->execute($userid);
1699 my $user_perms = {};
1700 while (my $perm = $sth->fetchrow_hashref) {
1701 $user_perms->{$perm->{'flag'}}->{$perm->{'code'}} = 1;
1706 =head2 get_all_subpermissions
1708 my $perm_hashref = get_all_subpermissions();
1710 Returns a hashref of hashrefs defining all specific
1711 permissions currently defined. The return value
1712 has the same structure as that of C<get_user_subpermissions>,
1713 except that the innermost hash value is the description
1714 of the subpermission.
1718 sub get_all_subpermissions {
1719 my $dbh = C4::Context->dbh;
1720 my $sth = $dbh->prepare("SELECT flag, code, description
1722 JOIN userflags ON (module_bit = bit)");
1726 while (my $perm = $sth->fetchrow_hashref) {
1727 $all_perms->{$perm->{'flag'}}->{$perm->{'code'}} = $perm->{'description'};
1732 =head2 haspermission
1734 $flags = ($userid, $flagsrequired);
1736 C<$userid> the userid of the member
1737 C<$flags> is a hashref of required flags like C<$borrower-<{authflags}>
1739 Returns member's flags or 0 if a permission is not met.
1744 my ($userid, $flagsrequired) = @_;
1745 my $sth = C4::Context->dbh->prepare("SELECT flags FROM borrowers WHERE userid=?");
1746 $sth->execute($userid);
1747 my $row = $sth->fetchrow();
1748 my $flags = getuserflags($row, $userid);
1749 if ( $userid eq C4::Context->config('user') ) {
1750 # Super User Account from /etc/koha.conf
1751 $flags->{'superlibrarian'} = 1;
1753 elsif ( $userid eq 'demo' && C4::Context->config('demo') ) {
1754 # Demo user that can do "anything" (demo=1 in /etc/koha.conf)
1755 $flags->{'superlibrarian'} = 1;
1758 return $flags if $flags->{superlibrarian};
1760 foreach my $module ( keys %$flagsrequired ) {
1761 my $subperm = $flagsrequired->{$module};
1762 if ($subperm eq '*') {
1763 return 0 unless ( $flags->{$module} == 1 or ref($flags->{$module}) );
1765 return 0 unless ( $flags->{$module} == 1 or
1766 ( ref($flags->{$module}) and
1767 exists $flags->{$module}->{$subperm} and
1768 $flags->{$module}->{$subperm} == 1
1774 #FIXME - This fcn should return the failed permission so a suitable error msg can be delivered.
1778 sub getborrowernumber {
1780 my $userenv = C4::Context->userenv;
1781 if ( defined( $userenv ) && ref( $userenv ) eq 'HASH' && $userenv->{number} ) {
1782 return $userenv->{number};
1784 my $dbh = C4::Context->dbh;
1785 for my $field ( 'userid', 'cardnumber' ) {
1787 $dbh->prepare("select borrowernumber from borrowers where $field=?");
1788 $sth->execute($userid);
1790 my ($bnumber) = $sth->fetchrow;
1797 END { } # module clean-up code here (global destructor)
1807 Crypt::Eksblowfish::Bcrypt(3)