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 decode_json/;
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'}, $in->{'is_plugin'} );
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'},
150 # It's possible for $user to be the borrowernumber if they don't have a
151 # userid defined (and are logging in through some other method, such
152 # as SSL certs against an email address)
153 $borrowernumber = getborrowernumber($user) if defined($user);
154 if (!defined($borrowernumber) && defined($user)) {
155 my $borrower = C4::Members::GetMember(borrowernumber => $user);
157 $borrowernumber = $user;
158 # A bit of a hack, but I don't know there's a nicer way
160 $user = $borrower->{firstname} . ' ' . $borrower->{surname};
165 $template->param( loggedinusername => $user );
166 $template->param( sessionID => $sessionID );
168 my ($total, $pubshelves, $barshelves) = C4::VirtualShelves::GetSomeShelfNames($borrowernumber, 'MASTHEAD');
170 pubshelves => $total->{pubtotal},
171 pubshelvesloop => $pubshelves,
172 barshelves => $total->{bartotal},
173 barshelvesloop => $barshelves,
176 my ( $borr ) = C4::Members::GetMemberDetails( $borrowernumber );
179 $template->param( "USER_INFO" => \@bordat );
181 my $all_perms = get_all_subpermissions();
183 my @flagroots = qw(circulate catalogue parameters borrowers permissions reserveforothers borrow
184 editcatalogue updatecharges management tools editauthorities serials reports acquisition);
185 # We are going to use the $flags returned by checkauth
186 # to create the template's parameters that will indicate
187 # which menus the user can access.
188 if ( $flags && $flags->{superlibrarian}==1 ) {
189 $template->param( CAN_user_circulate => 1 );
190 $template->param( CAN_user_catalogue => 1 );
191 $template->param( CAN_user_parameters => 1 );
192 $template->param( CAN_user_borrowers => 1 );
193 $template->param( CAN_user_permissions => 1 );
194 $template->param( CAN_user_reserveforothers => 1 );
195 $template->param( CAN_user_borrow => 1 );
196 $template->param( CAN_user_editcatalogue => 1 );
197 $template->param( CAN_user_updatecharges => 1 );
198 $template->param( CAN_user_acquisition => 1 );
199 $template->param( CAN_user_management => 1 );
200 $template->param( CAN_user_tools => 1 );
201 $template->param( CAN_user_editauthorities => 1 );
202 $template->param( CAN_user_serials => 1 );
203 $template->param( CAN_user_reports => 1 );
204 $template->param( CAN_user_staffaccess => 1 );
205 $template->param( CAN_user_plugins => 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 = @{decode_json($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 => encode_json([]),
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 = @{decode_json($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'))
307 # these template parameters are set the same regardless of $in->{'type'}
309 "BiblioDefaultView".C4::Context->preference("BiblioDefaultView") => 1,
310 EnhancedMessagingPreferences => C4::Context->preference('EnhancedMessagingPreferences'),
311 GoogleJackets => C4::Context->preference("GoogleJackets"),
312 OpenLibraryCovers => C4::Context->preference("OpenLibraryCovers"),
313 KohaAdminEmailAddress => "" . C4::Context->preference("KohaAdminEmailAddress"),
314 LoginBranchcode => (C4::Context->userenv?C4::Context->userenv->{"branch"}:undef),
315 LoginFirstname => (C4::Context->userenv?C4::Context->userenv->{"firstname"}:"Bel"),
316 LoginSurname => C4::Context->userenv?C4::Context->userenv->{"surname"}:"Inconnu",
317 emailaddress => C4::Context->userenv?C4::Context->userenv->{"emailaddress"}:undef,
318 loggedinpersona => C4::Context->userenv?C4::Context->userenv->{"persona"}:undef,
319 TagsEnabled => C4::Context->preference("TagsEnabled"),
320 hide_marc => C4::Context->preference("hide_marc"),
321 item_level_itypes => C4::Context->preference('item-level_itypes'),
322 patronimages => C4::Context->preference("patronimages"),
323 singleBranchMode => C4::Context->preference("singleBranchMode"),
324 XSLTDetailsDisplay => C4::Context->preference("XSLTDetailsDisplay"),
325 XSLTResultsDisplay => C4::Context->preference("XSLTResultsDisplay"),
326 using_https => $in->{'query'}->https() ? 1 : 0,
327 noItemTypeImages => C4::Context->preference("noItemTypeImages"),
328 marcflavour => C4::Context->preference("marcflavour"),
329 persona => C4::Context->preference("persona"),
331 if ( $in->{'type'} eq "intranet" ) {
333 AmazonCoverImages => C4::Context->preference("AmazonCoverImages"),
334 AutoLocation => C4::Context->preference("AutoLocation"),
335 "BiblioDefaultView".C4::Context->preference("IntranetBiblioDefaultView") => 1,
336 CalendarFirstDayOfWeek => (C4::Context->preference("CalendarFirstDayOfWeek") eq "Sunday")?0:1,
337 CircAutocompl => C4::Context->preference("CircAutocompl"),
338 FRBRizeEditions => C4::Context->preference("FRBRizeEditions"),
339 IndependantBranches => C4::Context->preference("IndependantBranches"),
340 IntranetNav => C4::Context->preference("IntranetNav"),
341 IntranetmainUserblock => C4::Context->preference("IntranetmainUserblock"),
342 LibraryName => C4::Context->preference("LibraryName"),
343 LoginBranchname => (C4::Context->userenv?C4::Context->userenv->{"branchname"}:undef),
344 advancedMARCEditor => C4::Context->preference("advancedMARCEditor"),
345 canreservefromotherbranches => C4::Context->preference('canreservefromotherbranches'),
346 intranetcolorstylesheet => C4::Context->preference("intranetcolorstylesheet"),
347 IntranetFavicon => C4::Context->preference("IntranetFavicon"),
348 intranetreadinghistory => C4::Context->preference("intranetreadinghistory"),
349 intranetstylesheet => C4::Context->preference("intranetstylesheet"),
350 IntranetUserCSS => C4::Context->preference("IntranetUserCSS"),
351 intranetuserjs => C4::Context->preference("intranetuserjs"),
352 intranetbookbag => C4::Context->preference("intranetbookbag"),
353 suggestion => C4::Context->preference("suggestion"),
354 virtualshelves => C4::Context->preference("virtualshelves"),
355 StaffSerialIssueDisplayCount => C4::Context->preference("StaffSerialIssueDisplayCount"),
356 EasyAnalyticalRecords => C4::Context->preference('EasyAnalyticalRecords'),
357 LocalCoverImages => C4::Context->preference('LocalCoverImages'),
358 OPACLocalCoverImages => C4::Context->preference('OPACLocalCoverImages'),
359 AllowMultipleCovers => C4::Context->preference('AllowMultipleCovers'),
360 EnableBorrowerFiles => C4::Context->preference('EnableBorrowerFiles'),
361 UseKohaPlugins => C4::Context->preference('UseKohaPlugins'),
365 warn "template type should be OPAC, here it is=[" . $in->{'type'} . "]" unless ( $in->{'type'} eq 'opac' );
366 #TODO : replace LibraryName syspref with 'system name', and remove this html processing
367 my $LibraryNameTitle = C4::Context->preference("LibraryName");
368 $LibraryNameTitle =~ s/<(?:\/?)(?:br|p)\s*(?:\/?)>/ /sgi;
369 $LibraryNameTitle =~ s/<(?:[^<>'"]|'(?:[^']*)'|"(?:[^"]*)")*>//sg;
370 # clean up the busc param in the session if the page is not opac-detail
371 if (C4::Context->preference("OpacBrowseResults") && $in->{'template_name'} =~ /opac-(.+)\.(?:tt|tmpl)$/ && $1 !~ /^(?:MARC|ISBD)?detail$/) {
372 my $sessionSearch = get_session($sessionID || $in->{'query'}->cookie("CGISESSID"));
373 $sessionSearch->clear(["busc"]) if ($sessionSearch->param("busc"));
375 # variables passed from CGI: opac_css_override and opac_search_limits.
376 my $opac_search_limit = $ENV{'OPAC_SEARCH_LIMIT'};
377 my $opac_limit_override = $ENV{'OPAC_LIMIT_OVERRIDE'};
379 if (($opac_search_limit && $opac_search_limit =~ /branch:(\w+)/ && $opac_limit_override) || ($in->{'query'}->param('limit') && $in->{'query'}->param('limit') =~ /branch:(\w+)/)){
380 $opac_name = $1; # opac_search_limit is a branch, so we use it.
381 } elsif ( $in->{'query'}->param('multibranchlimit') ) {
382 $opac_name = $in->{'query'}->param('multibranchlimit');
383 } elsif (C4::Context->preference("SearchMyLibraryFirst") && C4::Context->userenv && C4::Context->userenv->{'branch'}) {
384 $opac_name = C4::Context->userenv->{'branch'};
387 opaccolorstylesheet => C4::Context->preference("opaccolorstylesheet"),
388 AnonSuggestions => "" . C4::Context->preference("AnonSuggestions"),
389 AuthorisedValueImages => C4::Context->preference("AuthorisedValueImages"),
390 BranchesLoop => GetBranchesLoop($opac_name),
391 BranchCategoriesLoop => GetBranchCategories( undef, undef, 1, $opac_name ),
392 CalendarFirstDayOfWeek => (C4::Context->preference("CalendarFirstDayOfWeek") eq "Sunday")?0:1,
393 LibraryName => "" . C4::Context->preference("LibraryName"),
394 LibraryNameTitle => "" . $LibraryNameTitle,
395 LoginBranchname => C4::Context->userenv?C4::Context->userenv->{"branchname"}:"",
396 OPACAmazonCoverImages => C4::Context->preference("OPACAmazonCoverImages"),
397 OPACFRBRizeEditions => C4::Context->preference("OPACFRBRizeEditions"),
398 OpacHighlightedWords => C4::Context->preference("OpacHighlightedWords"),
399 OPACItemHolds => C4::Context->preference("OPACItemHolds"),
400 OPACShelfBrowser => "". C4::Context->preference("OPACShelfBrowser"),
401 OpacShowRecentComments => C4::Context->preference("OpacShowRecentComments"),
402 OPACURLOpenInNewWindow => "" . C4::Context->preference("OPACURLOpenInNewWindow"),
403 OPACUserCSS => "". C4::Context->preference("OPACUserCSS"),
404 OPACMobileUserCSS => "". C4::Context->preference("OPACMobileUserCSS"),
405 OPACViewOthersSuggestions => "" . C4::Context->preference("OPACViewOthersSuggestions"),
406 OpacAuthorities => C4::Context->preference("OpacAuthorities"),
407 OPACBaseURL => ($in->{'query'}->https() ? "https://" : "http://") . $ENV{'SERVER_NAME'} .
408 ($ENV{'SERVER_PORT'} eq ($in->{'query'}->https() ? "443" : "80") ? '' : ":$ENV{'SERVER_PORT'}"),
409 opac_css_override => $ENV{'OPAC_CSS_OVERRIDE'},
410 opac_search_limit => $opac_search_limit,
411 opac_limit_override => $opac_limit_override,
412 OpacBrowser => C4::Context->preference("OpacBrowser"),
413 OpacCloud => C4::Context->preference("OpacCloud"),
414 OpacKohaUrl => C4::Context->preference("OpacKohaUrl"),
415 OpacMainUserBlock => "" . C4::Context->preference("OpacMainUserBlock"),
416 OpacMainUserBlockMobile => "" . C4::Context->preference("OpacMainUserBlockMobile"),
417 OpacShowFiltersPulldownMobile => C4::Context->preference("OpacShowFiltersPulldownMobile"),
418 OpacShowLibrariesPulldownMobile => C4::Context->preference("OpacShowLibrariesPulldownMobile"),
419 OpacNav => "" . C4::Context->preference("OpacNav"),
420 OpacNavRight => "" . C4::Context->preference("OpacNavRight"),
421 OpacNavBottom => "" . C4::Context->preference("OpacNavBottom"),
422 OpacPasswordChange => C4::Context->preference("OpacPasswordChange"),
423 OPACPatronDetails => C4::Context->preference("OPACPatronDetails"),
424 OPACPrivacy => C4::Context->preference("OPACPrivacy"),
425 OPACFinesTab => C4::Context->preference("OPACFinesTab"),
426 OpacTopissue => C4::Context->preference("OpacTopissue"),
427 RequestOnOpac => C4::Context->preference("RequestOnOpac"),
428 'Version' => C4::Context->preference('Version'),
429 hidelostitems => C4::Context->preference("hidelostitems"),
430 mylibraryfirst => (C4::Context->preference("SearchMyLibraryFirst") && C4::Context->userenv) ? C4::Context->userenv->{'branch'} : '',
431 opaclayoutstylesheet => "" . C4::Context->preference("opaclayoutstylesheet"),
432 opacbookbag => "" . C4::Context->preference("opacbookbag"),
433 opaccredits => "" . C4::Context->preference("opaccredits"),
434 OpacFavicon => C4::Context->preference("OpacFavicon"),
435 opacheader => "" . C4::Context->preference("opacheader"),
436 opaclanguagesdisplay => "" . C4::Context->preference("opaclanguagesdisplay"),
437 opacreadinghistory => C4::Context->preference("opacreadinghistory"),
438 opacsmallimage => "" . C4::Context->preference("opacsmallimage"),
439 opacuserjs => C4::Context->preference("opacuserjs"),
440 opacuserlogin => "" . C4::Context->preference("opacuserlogin"),
441 reviewson => C4::Context->preference("reviewson"),
442 ShowReviewer => C4::Context->preference("ShowReviewer"),
443 ShowReviewerPhoto => C4::Context->preference("ShowReviewerPhoto"),
444 suggestion => "" . C4::Context->preference("suggestion"),
445 virtualshelves => "" . C4::Context->preference("virtualshelves"),
446 OPACSerialIssueDisplayCount => C4::Context->preference("OPACSerialIssueDisplayCount"),
447 OpacAddMastheadLibraryPulldown => C4::Context->preference("OpacAddMastheadLibraryPulldown"),
448 OPACXSLTDetailsDisplay => C4::Context->preference("OPACXSLTDetailsDisplay"),
449 OPACXSLTResultsDisplay => C4::Context->preference("OPACXSLTResultsDisplay"),
450 SyndeticsClientCode => C4::Context->preference("SyndeticsClientCode"),
451 SyndeticsEnabled => C4::Context->preference("SyndeticsEnabled"),
452 SyndeticsCoverImages => C4::Context->preference("SyndeticsCoverImages"),
453 SyndeticsTOC => C4::Context->preference("SyndeticsTOC"),
454 SyndeticsSummary => C4::Context->preference("SyndeticsSummary"),
455 SyndeticsEditions => C4::Context->preference("SyndeticsEditions"),
456 SyndeticsExcerpt => C4::Context->preference("SyndeticsExcerpt"),
457 SyndeticsReviews => C4::Context->preference("SyndeticsReviews"),
458 SyndeticsAuthorNotes => C4::Context->preference("SyndeticsAuthorNotes"),
459 SyndeticsAwards => C4::Context->preference("SyndeticsAwards"),
460 SyndeticsSeries => C4::Context->preference("SyndeticsSeries"),
461 SyndeticsCoverImageSize => C4::Context->preference("SyndeticsCoverImageSize"),
462 OPACLocalCoverImages => C4::Context->preference("OPACLocalCoverImages"),
463 PatronSelfRegistration => C4::Context->preference("PatronSelfRegistration"),
464 PatronSelfRegistrationDefaultCategory => C4::Context->preference("PatronSelfRegistrationDefaultCategory"),
467 $template->param(OpacPublic => '1') if ($user || C4::Context->preference("OpacPublic"));
469 return ( $template, $borrowernumber, $cookie, $flags);
474 ($userid, $cookie, $sessionID) = &checkauth($query, $noauth, $flagsrequired, $type);
476 Verifies that the user is authorized to run this script. If
477 the user is authorized, a (userid, cookie, session-id, flags)
478 quadruple is returned. If the user is not authorized but does
479 not have the required privilege (see $flagsrequired below), it
480 displays an error page and exits. Otherwise, it displays the
481 login page and exits.
483 Note that C<&checkauth> will return if and only if the user
484 is authorized, so it should be called early on, before any
485 unfinished operations (e.g., if you've opened a file, then
486 C<&checkauth> won't close it for you).
488 C<$query> is the CGI object for the script calling C<&checkauth>.
490 The C<$noauth> argument is optional. If it is set, then no
491 authorization is required for the script.
493 C<&checkauth> fetches user and session information from C<$query> and
494 ensures that the user is authorized to run scripts that require
497 The C<$flagsrequired> argument specifies the required privileges
498 the user must have if the username and password are correct.
499 It should be specified as a reference-to-hash; keys in the hash
500 should be the "flags" for the user, as specified in the Members
501 intranet module. Any key specified must correspond to a "flag"
502 in the userflags table. E.g., { circulate => 1 } would specify
503 that the user must have the "circulate" privilege in order to
504 proceed. To make sure that access control is correct, the
505 C<$flagsrequired> parameter must be specified correctly.
507 Koha also has a concept of sub-permissions, also known as
508 granular permissions. This makes the value of each key
509 in the C<flagsrequired> hash take on an additional
514 The user must have access to all subfunctions of the module
515 specified by the hash key.
519 The user must have access to at least one subfunction of the module
520 specified by the hash key.
522 specific permission, e.g., 'export_catalog'
524 The user must have access to the specific subfunction list, which
525 must correspond to a row in the permissions table.
527 The C<$type> argument specifies whether the template should be
528 retrieved from the opac or intranet directory tree. "opac" is
529 assumed if it is not specified; however, if C<$type> is specified,
530 "intranet" is assumed if it is not "opac".
532 If C<$query> does not have a valid session ID associated with it
533 (i.e., the user has not logged in) or if the session has expired,
534 C<&checkauth> presents the user with a login page (from the point of
535 view of the original script, C<&checkauth> does not return). Once the
536 user has authenticated, C<&checkauth> restarts the original script
537 (this time, C<&checkauth> returns).
539 The login page is provided using a HTML::Template, which is set in the
540 systempreferences table or at the top of this file. The variable C<$type>
541 selects which template to use, either the opac or the intranet
542 authentification template.
544 C<&checkauth> returns a user ID, a cookie, and a session ID. The
545 cookie should be sent back to the browser; it verifies that the user
554 # If Version syspref is unavailable, it means Koha is beeing installed,
555 # and so we must redirect to OPAC maintenance page or to the WebInstaller
556 # also, if OpacMaintenance is ON, OPAC should redirect to maintenance
557 if (C4::Context->preference('OpacMaintenance') && $type eq 'opac') {
558 warn "OPAC Install required, redirecting to maintenance";
559 print $query->redirect("/cgi-bin/koha/maintenance.pl");
562 unless ( $version = C4::Context->preference('Version') ) { # assignment, not comparison
563 if ( $type ne 'opac' ) {
564 warn "Install required, redirecting to Installer";
565 print $query->redirect("/cgi-bin/koha/installer/install.pl");
567 warn "OPAC Install required, redirecting to maintenance";
568 print $query->redirect("/cgi-bin/koha/maintenance.pl");
573 # check that database and koha version are the same
574 # there is no DB version, it's a fresh install,
575 # go to web installer
576 # there is a DB version, compare it to the code version
577 my $kohaversion=C4::Context::KOHAVERSION;
578 # remove the 3 last . to have a Perl number
579 $kohaversion =~ s/(.*\..*)\.(.*)\.(.*)/$1$2$3/;
580 $debug and print STDERR "kohaversion : $kohaversion\n";
581 if ($version < $kohaversion){
582 my $warning = "Database update needed, redirecting to %s. Database is $version and Koha is $kohaversion";
583 if ($type ne 'opac'){
584 warn sprintf($warning, 'Installer');
585 print $query->redirect("/cgi-bin/koha/installer/install.pl?step=3");
587 warn sprintf("OPAC: " . $warning, 'maintenance');
588 print $query->redirect("/cgi-bin/koha/maintenance.pl");
596 open my $fh, '>>', "/tmp/sessionlog" or warn "ERROR: Cannot append to /tmp/sessionlog";
597 printf $fh join("\n",@_);
601 sub _timeout_syspref {
602 my $timeout = C4::Context->preference('timeout') || 600;
603 # value in days, convert in seconds
604 if ($timeout =~ /(\d+)[dD]/) {
605 $timeout = $1 * 86400;
612 $debug and warn "Checking Auth";
613 # $authnotrequired will be set for scripts which will run without authentication
614 my $authnotrequired = shift;
615 my $flagsrequired = shift;
618 $type = 'opac' unless $type;
620 my $dbh = C4::Context->dbh;
621 my $timeout = _timeout_syspref();
623 _version_check($type,$query);
627 my ( $userid, $cookie, $sessionID, $flags, $barshelves, $pubshelves );
628 my $logout = $query->param('logout.x');
630 # This parameter is the name of the CAS server we want to authenticate against,
631 # when using authentication against multiple CAS servers, as configured in Auth_cas_servers.yaml
632 my $casparam = $query->param('cas');
634 if ( $userid = $ENV{'REMOTE_USER'} ) {
635 # Using Basic Authentication, no cookies required
636 $cookie = $query->cookie(
637 -name => 'CGISESSID',
645 # we dont want to set a session because we are being called by a persona callback
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'),
659 $session->param('persona')
661 C4::Context::set_shelves_userenv('bar',$session->param('barshelves'));
662 C4::Context::set_shelves_userenv('pub',$session->param('pubshelves'));
663 C4::Context::set_shelves_userenv('tot',$session->param('totshelves'));
664 $debug and printf STDERR "AUTH_SESSION: (%s)\t%s %s - %s\n", map {$session->param($_)} qw(cardnumber firstname surname branch) ;
665 $ip = $session->param('ip');
666 $lasttime = $session->param('lasttime');
667 $userid = $session->param('id');
668 $sessiontype = $session->param('sessiontype') || '';
670 if ( ( ($query->param('koha_login_context')) && ($query->param('userid') ne $session->param('id')) )
671 || ( $cas && $query->param('ticket') ) ) {
672 #if a user enters an id ne to the id in the current session, we need to log them in...
673 #first we need to clear the anonymous session...
674 $debug and warn "query id = " . $query->param('userid') . " but session id = " . $session->param('id');
677 C4::Context->_unset_userenv($sessionID);
682 # voluntary logout the user
685 C4::Context->_unset_userenv($sessionID);
686 #_session_log(sprintf "%20s from %16s logged out at %30s (manually).\n", $userid,$ip,(strftime "%c",localtime));
690 if ($cas and $caslogout) {
694 elsif ( $lasttime < time() - $timeout ) {
696 $info{'timed_out'} = 1;
697 $session->delete() if $session;
698 C4::Context->_unset_userenv($sessionID);
699 #_session_log(sprintf "%20s from %16s logged out at %30s (inactivity).\n", $userid,$ip,(strftime "%c",localtime));
703 elsif ( $ip ne $ENV{'REMOTE_ADDR'} ) {
704 # Different ip than originally logged in from
705 $info{'oldip'} = $ip;
706 $info{'newip'} = $ENV{'REMOTE_ADDR'};
707 $info{'different_ip'} = 1;
709 C4::Context->_unset_userenv($sessionID);
710 #_session_log(sprintf "%20s from %16s logged out at %30s (ip changed to %16s).\n", $userid,$ip,(strftime "%c",localtime), $info{'newip'});
715 $cookie = $query->cookie(
716 -name => 'CGISESSID',
717 -value => $session->id,
720 $session->param( 'lasttime', time() );
721 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...
722 $flags = haspermission($userid, $flagsrequired);
726 $info{'nopermission'} = 1;
731 unless ($userid || $sessionID) {
733 #we initiate a session prior to checking for a username to allow for anonymous sessions...
734 my $session = get_session("") or die "Auth ERROR: Cannot get_session()";
735 my $sessionID = $session->id;
736 C4::Context->_new_userenv($sessionID);
737 $cookie = $query->cookie(
738 -name => 'CGISESSID',
739 -value => $session->id,
742 $userid = $query->param('userid');
743 if ( ( $cas && $query->param('ticket') )
745 || ( my $pki_field = C4::Context->preference('AllowPKIAuth') ) ne
748 my $password = $query->param('password');
750 my ( $return, $cardnumber );
751 if ( $cas && $query->param('ticket') ) {
753 ( $return, $cardnumber, $retuserid ) =
754 checkpw( $dbh, $userid, $password, $query );
755 $userid = $retuserid;
756 $info{'invalidCasLogin'} = 1 unless ($return);
760 my $value = $persona;
762 # If we're looking up the email, there's a chance that the person
763 # doesn't have a userid. So if there is none, we pass along the
764 # borrower number, and the bits of code that need to know the user
765 # ID will have to be smart enough to handle that.
767 my @users_info = C4::Members::GetBorrowersWithEmail($value);
770 # First the userid, then the borrowernum
771 $value = $users_info[0][1] || $users_info[0][0];
776 $return = $value ? 1 : 0;
781 ( $pki_field eq 'Common Name' && $ENV{'SSL_CLIENT_S_DN_CN'} )
782 || ( $pki_field eq 'emailAddress'
783 && $ENV{'SSL_CLIENT_S_DN_Email'} )
787 if ( $pki_field eq 'Common Name' ) {
788 $value = $ENV{'SSL_CLIENT_S_DN_CN'};
790 elsif ( $pki_field eq 'emailAddress' ) {
791 $value = $ENV{'SSL_CLIENT_S_DN_Email'};
793 # If we're looking up the email, there's a chance that the person
794 # doesn't have a userid. So if there is none, we pass along the
795 # borrower number, and the bits of code that need to know the user
796 # ID will have to be smart enough to handle that.
798 my @users_info = C4::Members::GetBorrowersWithEmail($value);
801 # First the userid, then the borrowernum
802 $value = $users_info[0][1] || $users_info[0][0];
809 $return = $value ? 1 : 0;
815 ( $return, $cardnumber, $retuserid ) =
816 checkpw( $dbh, $userid, $password, $query );
817 $userid = $retuserid if ( $retuserid ne '' );
820 #_session_log(sprintf "%20s from %16s logged in at %30s.\n", $userid,$ENV{'REMOTE_ADDR'},(strftime '%c', localtime));
821 if ( $flags = haspermission( $userid, $flagsrequired ) ) {
825 $info{'nopermission'} = 1;
826 C4::Context->_unset_userenv($sessionID);
828 my ($borrowernumber, $firstname, $surname, $userflags,
829 $branchcode, $branchname, $branchprinter, $emailaddress);
831 if ( $return == 1 ) {
833 SELECT borrowernumber, firstname, surname, flags, borrowers.branchcode,
834 branches.branchname as branchname,
835 branches.branchprinter as branchprinter,
838 LEFT JOIN branches on borrowers.branchcode=branches.branchcode
840 my $sth = $dbh->prepare("$select where userid=?");
841 $sth->execute($userid);
842 unless ($sth->rows) {
843 $debug and print STDERR "AUTH_1: no rows for userid='$userid'\n";
844 $sth = $dbh->prepare("$select where cardnumber=?");
845 $sth->execute($cardnumber);
847 unless ($sth->rows) {
848 $debug and print STDERR "AUTH_2a: no rows for cardnumber='$cardnumber'\n";
849 $sth->execute($userid);
850 unless ($sth->rows) {
851 $debug and print STDERR "AUTH_2b: no rows for userid='$userid' AS cardnumber\n";
856 ($borrowernumber, $firstname, $surname, $userflags,
857 $branchcode, $branchname, $branchprinter, $emailaddress) = $sth->fetchrow;
858 $debug and print STDERR "AUTH_3 results: " .
859 "$cardnumber,$borrowernumber,$userid,$firstname,$surname,$userflags,$branchcode,$emailaddress\n";
861 print STDERR "AUTH_3: no results for userid='$userid', cardnumber='$cardnumber'.\n";
864 # launch a sequence to check if we have a ip for the branch, i
865 # if we have one we replace the branchcode of the userenv by the branch bound in the ip.
867 my $ip = $ENV{'REMOTE_ADDR'};
868 # if they specify at login, use that
869 if ($query->param('branch')) {
870 $branchcode = $query->param('branch');
871 $branchname = GetBranchName($branchcode);
873 my $branches = GetBranches();
874 if (C4::Context->boolean_preference('IndependantBranches') && C4::Context->boolean_preference('Autolocation')){
875 # we have to check they are coming from the right ip range
876 my $domain = $branches->{$branchcode}->{'branchip'};
877 if ($ip !~ /^$domain/){
879 $info{'wrongip'} = 1;
884 foreach my $br ( keys %$branches ) {
885 # now we work with the treatment of ip
886 my $domain = $branches->{$br}->{'branchip'};
887 if ( $domain && $ip =~ /^$domain/ ) {
888 $branchcode = $branches->{$br}->{'branchcode'};
890 # new op dev : add the branchprinter and branchname in the cookie
891 $branchprinter = $branches->{$br}->{'branchprinter'};
892 $branchname = $branches->{$br}->{'branchname'};
895 $session->param('number',$borrowernumber);
896 $session->param('id',$userid);
897 $session->param('cardnumber',$cardnumber);
898 $session->param('firstname',$firstname);
899 $session->param('surname',$surname);
900 $session->param('branch',$branchcode);
901 $session->param('branchname',$branchname);
902 $session->param('flags',$userflags);
903 $session->param('emailaddress',$emailaddress);
904 $session->param('ip',$session->remote_addr());
905 $session->param('lasttime',time());
906 $debug and printf STDERR "AUTH_4: (%s)\t%s %s - %s\n", map {$session->param($_)} qw(cardnumber firstname surname branch) ;
908 elsif ( $return == 2 ) {
909 #We suppose the user is the superlibrarian
911 $session->param('number',0);
912 $session->param('id',C4::Context->config('user'));
913 $session->param('cardnumber',C4::Context->config('user'));
914 $session->param('firstname',C4::Context->config('user'));
915 $session->param('surname',C4::Context->config('user'));
916 $session->param('branch','NO_LIBRARY_SET');
917 $session->param('branchname','NO_LIBRARY_SET');
918 $session->param('flags',1);
919 $session->param('emailaddress', C4::Context->preference('KohaAdminEmailAddress'));
920 $session->param('ip',$session->remote_addr());
921 $session->param('lasttime',time());
924 $session->param('persona',1);
926 C4::Context::set_userenv(
927 $session->param('number'), $session->param('id'),
928 $session->param('cardnumber'), $session->param('firstname'),
929 $session->param('surname'), $session->param('branch'),
930 $session->param('branchname'), $session->param('flags'),
931 $session->param('emailaddress'), $session->param('branchprinter'),
932 $session->param('persona')
938 $info{'invalid_username_or_password'} = 1;
939 C4::Context->_unset_userenv($sessionID);
942 } # END if ( $userid = $query->param('userid') )
943 elsif ($type eq "opac") {
944 # if we are here this is an anonymous session; add public lists to it and a few other items...
945 # anonymous sessions are created only for the OPAC
946 $debug and warn "Initiating an anonymous session...";
948 # setting a couple of other session vars...
949 $session->param('ip',$session->remote_addr());
950 $session->param('lasttime',time());
951 $session->param('sessiontype','anon');
953 } # END unless ($userid)
955 # finished authentification, now respond
956 if ( $loggedin || $authnotrequired )
960 $cookie = $query->cookie(
961 -name => 'CGISESSID',
966 return ( $userid, $cookie, $sessionID, $flags );
971 # AUTH rejected, show the login/password template, after checking the DB.
975 # get the inputs from the incoming query
977 foreach my $name ( param $query) {
978 (next) if ( $name eq 'userid' || $name eq 'password' || $name eq 'ticket' );
979 my $value = $query->param($name);
980 push @inputs, { name => $name, value => $value };
983 my $template_name = ( $type eq 'opac' ) ? 'opac-auth.tmpl' : 'auth.tmpl';
984 my $template = C4::Templates::gettemplate($template_name, $type, $query );
986 branchloop => GetBranchesLoop(),
987 opaccolorstylesheet => C4::Context->preference("opaccolorstylesheet"),
988 opaclayoutstylesheet => C4::Context->preference("opaclayoutstylesheet"),
991 casAuthentication => C4::Context->preference("casAuthentication"),
992 suggestion => C4::Context->preference("suggestion"),
993 virtualshelves => C4::Context->preference("virtualshelves"),
994 LibraryName => C4::Context->preference("LibraryName"),
995 opacuserlogin => C4::Context->preference("opacuserlogin"),
996 OpacNav => C4::Context->preference("OpacNav"),
997 OpacNavRight => C4::Context->preference("OpacNavRight"),
998 OpacNavBottom => C4::Context->preference("OpacNavBottom"),
999 opaccredits => C4::Context->preference("opaccredits"),
1000 OpacFavicon => C4::Context->preference("OpacFavicon"),
1001 opacreadinghistory => C4::Context->preference("opacreadinghistory"),
1002 opacsmallimage => C4::Context->preference("opacsmallimage"),
1003 opaclanguagesdisplay => C4::Context->preference("opaclanguagesdisplay"),
1004 opacuserjs => C4::Context->preference("opacuserjs"),
1005 opacbookbag => "" . C4::Context->preference("opacbookbag"),
1006 OpacCloud => C4::Context->preference("OpacCloud"),
1007 OpacTopissue => C4::Context->preference("OpacTopissue"),
1008 OpacAuthorities => C4::Context->preference("OpacAuthorities"),
1009 OpacBrowser => C4::Context->preference("OpacBrowser"),
1010 opacheader => C4::Context->preference("opacheader"),
1011 TagsEnabled => C4::Context->preference("TagsEnabled"),
1012 OPACUserCSS => C4::Context->preference("OPACUserCSS"),
1013 intranetcolorstylesheet => C4::Context->preference("intranetcolorstylesheet"),
1014 intranetstylesheet => C4::Context->preference("intranetstylesheet"),
1015 intranetbookbag => C4::Context->preference("intranetbookbag"),
1016 IntranetNav => C4::Context->preference("IntranetNav"),
1017 IntranetFavicon => C4::Context->preference("IntranetFavicon"),
1018 intranetuserjs => C4::Context->preference("intranetuserjs"),
1019 IndependantBranches=> C4::Context->preference("IndependantBranches"),
1020 AutoLocation => C4::Context->preference("AutoLocation"),
1021 wrongip => $info{'wrongip'},
1022 PatronSelfRegistration => C4::Context->preference("PatronSelfRegistration"),
1023 PatronSelfRegistrationDefaultCategory => C4::Context->preference("PatronSelfRegistrationDefaultCategory"),
1024 persona => C4::Context->preference("Persona"),
1025 opac_css_override => $ENV{'OPAC_CSS_OVERRIDE'},
1028 $template->param( OpacPublic => C4::Context->preference("OpacPublic"));
1029 $template->param( loginprompt => 1 ) unless $info{'nopermission'};
1033 # Is authentication against multiple CAS servers enabled?
1034 if (C4::Auth_with_cas::multipleAuth && !$casparam) {
1035 my $casservers = C4::Auth_with_cas::getMultipleAuth();
1037 foreach my $key (keys %$casservers) {
1038 push @tmplservers, {name => $key, value => login_cas_url($query, $key) . "?cas=$key" };
1041 casServersLoop => \@tmplservers
1045 casServerUrl => login_cas_url($query),
1050 invalidCasLogin => $info{'invalidCasLogin'}
1054 my $self_url = $query->url( -absolute => 1 );
1057 LibraryName => C4::Context->preference("LibraryName"),
1059 $template->param( %info );
1060 # $cookie = $query->cookie(CGISESSID => $session->id
1062 print $query->header(
1063 -type => 'text/html',
1064 -charset => 'utf-8',
1071 =head2 check_api_auth
1073 ($status, $cookie, $sessionId) = check_api_auth($query, $userflags);
1075 Given a CGI query containing the parameters 'userid' and 'password' and/or a session
1076 cookie, determine if the user has the privileges specified by C<$userflags>.
1078 C<check_api_auth> is is meant for authenticating users of web services, and
1079 consequently will always return and will not attempt to redirect the user
1082 If a valid session cookie is already present, check_api_auth will return a status
1083 of "ok", the cookie, and the Koha session ID.
1085 If no session cookie is present, check_api_auth will check the 'userid' and 'password
1086 parameters and create a session cookie and Koha session if the supplied credentials
1089 Possible return values in C<$status> are:
1093 =item "ok" -- user authenticated; C<$cookie> and C<$sessionid> have valid values.
1095 =item "failed" -- credentials are not correct; C<$cookie> and C<$sessionid> are undef
1097 =item "maintenance" -- DB is in maintenance mode; no login possible at the moment
1099 =item "expired -- session cookie has expired; API user should resubmit userid and password
1105 sub check_api_auth {
1107 my $flagsrequired = shift;
1109 my $dbh = C4::Context->dbh;
1110 my $timeout = _timeout_syspref();
1112 unless (C4::Context->preference('Version')) {
1113 # database has not been installed yet
1114 return ("maintenance", undef, undef);
1116 my $kohaversion=C4::Context::KOHAVERSION;
1117 $kohaversion =~ s/(.*\..*)\.(.*)\.(.*)/$1$2$3/;
1118 if (C4::Context->preference('Version') < $kohaversion) {
1119 # database in need of version update; assume that
1120 # no API should be called while databsae is in
1122 return ("maintenance", undef, undef);
1125 # FIXME -- most of what follows is a copy-and-paste
1126 # of code from checkauth. There is an obvious need
1127 # for refactoring to separate the various parts of
1128 # the authentication code, but as of 2007-11-19 this
1129 # is deferred so as to not introduce bugs into the
1130 # regular authentication code for Koha 3.0.
1132 # see if we have a valid session cookie already
1133 # however, if a userid parameter is present (i.e., from
1134 # a form submission, assume that any current cookie
1136 my $sessionID = undef;
1137 unless ($query->param('userid')) {
1138 $sessionID = $query->cookie("CGISESSID");
1140 if ($sessionID && not ($cas && $query->param('PT')) ) {
1141 my $session = get_session($sessionID);
1142 C4::Context->_new_userenv($sessionID);
1144 C4::Context::set_userenv(
1145 $session->param('number'), $session->param('id'),
1146 $session->param('cardnumber'), $session->param('firstname'),
1147 $session->param('surname'), $session->param('branch'),
1148 $session->param('branchname'), $session->param('flags'),
1149 $session->param('emailaddress'), $session->param('branchprinter')
1152 my $ip = $session->param('ip');
1153 my $lasttime = $session->param('lasttime');
1154 my $userid = $session->param('id');
1155 if ( $lasttime < time() - $timeout ) {
1158 C4::Context->_unset_userenv($sessionID);
1161 return ("expired", undef, undef);
1162 } elsif ( $ip ne $ENV{'REMOTE_ADDR'} ) {
1163 # IP address changed
1165 C4::Context->_unset_userenv($sessionID);
1168 return ("expired", undef, undef);
1170 my $cookie = $query->cookie(
1171 -name => 'CGISESSID',
1172 -value => $session->id,
1175 $session->param('lasttime',time());
1176 my $flags = haspermission($userid, $flagsrequired);
1178 return ("ok", $cookie, $sessionID);
1181 C4::Context->_unset_userenv($sessionID);
1184 return ("failed", undef, undef);
1188 return ("expired", undef, undef);
1192 my $userid = $query->param('userid');
1193 my $password = $query->param('password');
1194 my ($return, $cardnumber);
1197 if ($cas && $query->param('PT')) {
1199 $debug and print STDERR "## check_api_auth - checking CAS\n";
1200 # In case of a CAS authentication, we use the ticket instead of the password
1201 my $PT = $query->param('PT');
1202 ($return,$cardnumber,$userid) = check_api_auth_cas($dbh, $PT, $query); # EXTERNAL AUTH
1204 # User / password auth
1205 unless ($userid and $password) {
1206 # caller did something wrong, fail the authenticateion
1207 return ("failed", undef, undef);
1209 ( $return, $cardnumber ) = checkpw( $dbh, $userid, $password, $query );
1212 if ($return and haspermission( $userid, $flagsrequired)) {
1213 my $session = get_session("");
1214 return ("failed", undef, undef) unless $session;
1216 my $sessionID = $session->id;
1217 C4::Context->_new_userenv($sessionID);
1218 my $cookie = $query->cookie(
1219 -name => 'CGISESSID',
1220 -value => $sessionID,
1223 if ( $return == 1 ) {
1225 $borrowernumber, $firstname, $surname,
1226 $userflags, $branchcode, $branchname,
1227 $branchprinter, $emailaddress
1231 "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=?"
1233 $sth->execute($userid);
1235 $borrowernumber, $firstname, $surname,
1236 $userflags, $branchcode, $branchname,
1237 $branchprinter, $emailaddress
1238 ) = $sth->fetchrow if ( $sth->rows );
1240 unless ($sth->rows ) {
1241 my $sth = $dbh->prepare(
1242 "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=?"
1244 $sth->execute($cardnumber);
1246 $borrowernumber, $firstname, $surname,
1247 $userflags, $branchcode, $branchname,
1248 $branchprinter, $emailaddress
1249 ) = $sth->fetchrow if ( $sth->rows );
1251 unless ( $sth->rows ) {
1252 $sth->execute($userid);
1254 $borrowernumber, $firstname, $surname, $userflags,
1255 $branchcode, $branchname, $branchprinter, $emailaddress
1256 ) = $sth->fetchrow if ( $sth->rows );
1260 my $ip = $ENV{'REMOTE_ADDR'};
1261 # if they specify at login, use that
1262 if ($query->param('branch')) {
1263 $branchcode = $query->param('branch');
1264 $branchname = GetBranchName($branchcode);
1266 my $branches = GetBranches();
1268 foreach my $br ( keys %$branches ) {
1269 # now we work with the treatment of ip
1270 my $domain = $branches->{$br}->{'branchip'};
1271 if ( $domain && $ip =~ /^$domain/ ) {
1272 $branchcode = $branches->{$br}->{'branchcode'};
1274 # new op dev : add the branchprinter and branchname in the cookie
1275 $branchprinter = $branches->{$br}->{'branchprinter'};
1276 $branchname = $branches->{$br}->{'branchname'};
1279 $session->param('number',$borrowernumber);
1280 $session->param('id',$userid);
1281 $session->param('cardnumber',$cardnumber);
1282 $session->param('firstname',$firstname);
1283 $session->param('surname',$surname);
1284 $session->param('branch',$branchcode);
1285 $session->param('branchname',$branchname);
1286 $session->param('flags',$userflags);
1287 $session->param('emailaddress',$emailaddress);
1288 $session->param('ip',$session->remote_addr());
1289 $session->param('lasttime',time());
1290 } elsif ( $return == 2 ) {
1291 #We suppose the user is the superlibrarian
1292 $session->param('number',0);
1293 $session->param('id',C4::Context->config('user'));
1294 $session->param('cardnumber',C4::Context->config('user'));
1295 $session->param('firstname',C4::Context->config('user'));
1296 $session->param('surname',C4::Context->config('user'));
1297 $session->param('branch','NO_LIBRARY_SET');
1298 $session->param('branchname','NO_LIBRARY_SET');
1299 $session->param('flags',1);
1300 $session->param('emailaddress', C4::Context->preference('KohaAdminEmailAddress'));
1301 $session->param('ip',$session->remote_addr());
1302 $session->param('lasttime',time());
1304 C4::Context::set_userenv(
1305 $session->param('number'), $session->param('id'),
1306 $session->param('cardnumber'), $session->param('firstname'),
1307 $session->param('surname'), $session->param('branch'),
1308 $session->param('branchname'), $session->param('flags'),
1309 $session->param('emailaddress'), $session->param('branchprinter')
1311 return ("ok", $cookie, $sessionID);
1313 return ("failed", undef, undef);
1318 =head2 check_cookie_auth
1320 ($status, $sessionId) = check_api_auth($cookie, $userflags);
1322 Given a CGISESSID cookie set during a previous login to Koha, determine
1323 if the user has the privileges specified by C<$userflags>.
1325 C<check_cookie_auth> is meant for authenticating special services
1326 such as tools/upload-file.pl that are invoked by other pages that
1327 have been authenticated in the usual way.
1329 Possible return values in C<$status> are:
1333 =item "ok" -- user authenticated; C<$sessionID> have valid values.
1335 =item "failed" -- credentials are not correct; C<$sessionid> are undef
1337 =item "maintenance" -- DB is in maintenance mode; no login possible at the moment
1339 =item "expired -- session cookie has expired; API user should resubmit userid and password
1345 sub check_cookie_auth {
1347 my $flagsrequired = shift;
1349 my $dbh = C4::Context->dbh;
1350 my $timeout = _timeout_syspref();
1352 unless (C4::Context->preference('Version')) {
1353 # database has not been installed yet
1354 return ("maintenance", undef);
1356 my $kohaversion=C4::Context::KOHAVERSION;
1357 $kohaversion =~ s/(.*\..*)\.(.*)\.(.*)/$1$2$3/;
1358 if (C4::Context->preference('Version') < $kohaversion) {
1359 # database in need of version update; assume that
1360 # no API should be called while databsae is in
1362 return ("maintenance", undef);
1365 # FIXME -- most of what follows is a copy-and-paste
1366 # of code from checkauth. There is an obvious need
1367 # for refactoring to separate the various parts of
1368 # the authentication code, but as of 2007-11-23 this
1369 # is deferred so as to not introduce bugs into the
1370 # regular authentication code for Koha 3.0.
1372 # see if we have a valid session cookie already
1373 # however, if a userid parameter is present (i.e., from
1374 # a form submission, assume that any current cookie
1376 unless (defined $cookie and $cookie) {
1377 return ("failed", undef);
1379 my $sessionID = $cookie;
1380 my $session = get_session($sessionID);
1381 C4::Context->_new_userenv($sessionID);
1383 C4::Context::set_userenv(
1384 $session->param('number'), $session->param('id'),
1385 $session->param('cardnumber'), $session->param('firstname'),
1386 $session->param('surname'), $session->param('branch'),
1387 $session->param('branchname'), $session->param('flags'),
1388 $session->param('emailaddress'), $session->param('branchprinter')
1391 my $ip = $session->param('ip');
1392 my $lasttime = $session->param('lasttime');
1393 my $userid = $session->param('id');
1394 if ( $lasttime < time() - $timeout ) {
1397 C4::Context->_unset_userenv($sessionID);
1400 return ("expired", undef);
1401 } elsif ( $ip ne $ENV{'REMOTE_ADDR'} ) {
1402 # IP address changed
1404 C4::Context->_unset_userenv($sessionID);
1407 return ("expired", undef);
1409 $session->param('lasttime',time());
1410 my $flags = haspermission($userid, $flagsrequired);
1412 return ("ok", $sessionID);
1415 C4::Context->_unset_userenv($sessionID);
1418 return ("failed", undef);
1422 return ("expired", undef);
1429 my $session = get_session($sessionID);
1431 Given a session ID, retrieve the CGI::Session object used to store
1432 the session's state. The session object can be used to store
1433 data that needs to be accessed by different scripts during a
1436 If the C<$sessionID> parameter is an empty string, a new session
1442 my $sessionID = shift;
1443 my $storage_method = C4::Context->preference('SessionStorage');
1444 my $dbh = C4::Context->dbh;
1446 if ($storage_method eq 'mysql'){
1447 $session = new CGI::Session("driver:MySQL;serializer:yaml;id:md5", $sessionID, {Handle=>$dbh});
1449 elsif ($storage_method eq 'Pg') {
1450 $session = new CGI::Session("driver:PostgreSQL;serializer:yaml;id:md5", $sessionID, {Handle=>$dbh});
1452 elsif ($storage_method eq 'memcached' && C4::Context->ismemcached){
1453 $session = new CGI::Session("driver:memcached;serializer:yaml;id:md5", $sessionID, { Memcached => C4::Context->memcached } );
1456 # catch all defaults to tmp should work on all systems
1457 $session = new CGI::Session("driver:File;serializer:yaml;id:md5", $sessionID, {Directory=>'/tmp'});
1464 my ( $dbh, $userid, $password, $query ) = @_;
1466 $debug and print STDERR "## checkpw - checking LDAP\n";
1467 my ($retval,$retcard,$retuserid) = checkpw_ldap(@_); # EXTERNAL AUTH
1468 ($retval) and return ($retval,$retcard,$retuserid);
1471 if ($cas && $query && $query->param('ticket')) {
1472 $debug and print STDERR "## checkpw - checking CAS\n";
1473 # In case of a CAS authentication, we use the ticket instead of the password
1474 my $ticket = $query->param('ticket');
1475 my ($retval,$retcard,$retuserid) = checkpw_cas($dbh, $ticket, $query); # EXTERNAL AUTH
1476 ($retval) and return ($retval,$retcard,$retuserid);
1483 "select password,cardnumber,borrowernumber,userid,firstname,surname,branchcode,flags from borrowers where userid=?"
1485 $sth->execute($userid);
1487 my ( $md5password, $cardnumber, $borrowernumber, $userid, $firstname,
1488 $surname, $branchcode, $flags )
1490 if ( md5_base64($password) eq $md5password and $md5password ne "!") {
1492 C4::Context->set_userenv( "$borrowernumber", $userid, $cardnumber,
1493 $firstname, $surname, $branchcode, $flags );
1494 return 1, $cardnumber, $userid;
1499 "select password,cardnumber,borrowernumber,userid, firstname,surname,branchcode,flags from borrowers where cardnumber=?"
1501 $sth->execute($userid);
1503 my ( $md5password, $cardnumber, $borrowernumber, $userid, $firstname,
1504 $surname, $branchcode, $flags )
1506 if ( md5_base64($password) eq $md5password ) {
1508 C4::Context->set_userenv( $borrowernumber, $userid, $cardnumber,
1509 $firstname, $surname, $branchcode, $flags );
1510 return 1, $cardnumber, $userid;
1513 if ( $userid && $userid eq C4::Context->config('user')
1514 && "$password" eq C4::Context->config('pass') )
1517 # Koha superuser account
1518 # C4::Context->set_userenv(0,0,C4::Context->config('user'),C4::Context->config('user'),C4::Context->config('user'),"",1);
1521 if ( $userid && $userid eq 'demo'
1522 && "$password" eq 'demo'
1523 && C4::Context->config('demo') )
1526 # DEMO => the demo user is allowed to do everything (if demo set to 1 in koha.conf
1527 # some features won't be effective : modify systempref, modify MARC structure,
1535 my $authflags = getuserflags($flags, $userid, [$dbh]);
1537 Translates integer flags into permissions strings hash.
1539 C<$flags> is the integer userflags value ( borrowers.userflags )
1540 C<$userid> is the members.userid, used for building subpermissions
1541 C<$authflags> is a hashref of permissions
1548 my $dbh = @_ ? shift : C4::Context->dbh;
1551 # I don't want to do this, but if someone logs in as the database
1552 # user, it would be preferable not to spam them to death with
1553 # numeric warnings. So, we make $flags numeric.
1554 no warnings 'numeric';
1557 my $sth = $dbh->prepare("SELECT bit, flag, defaulton FROM userflags");
1560 while ( my ( $bit, $flag, $defaulton ) = $sth->fetchrow ) {
1561 if ( ( $flags & ( 2**$bit ) ) || $defaulton ) {
1562 $userflags->{$flag} = 1;
1565 $userflags->{$flag} = 0;
1569 # get subpermissions and merge with top-level permissions
1570 my $user_subperms = get_user_subpermissions($userid);
1571 foreach my $module (keys %$user_subperms) {
1572 next if $userflags->{$module} == 1; # user already has permission for everything in this module
1573 $userflags->{$module} = $user_subperms->{$module};
1579 =head2 get_user_subpermissions
1581 $user_perm_hashref = get_user_subpermissions($userid);
1583 Given the userid (note, not the borrowernumber) of a staff user,
1584 return a hashref of hashrefs of the specific subpermissions
1585 accorded to the user. An example return is
1589 export_catalog => 1,
1590 import_patrons => 1,
1594 The top-level hash-key is a module or function code from
1595 userflags.flag, while the second-level key is a code
1598 The results of this function do not give a complete picture
1599 of the functions that a staff user can access; it is also
1600 necessary to check borrowers.flags.
1604 sub get_user_subpermissions {
1607 my $dbh = C4::Context->dbh;
1608 my $sth = $dbh->prepare("SELECT flag, user_permissions.code
1609 FROM user_permissions
1610 JOIN permissions USING (module_bit, code)
1611 JOIN userflags ON (module_bit = bit)
1612 JOIN borrowers USING (borrowernumber)
1614 $sth->execute($userid);
1616 my $user_perms = {};
1617 while (my $perm = $sth->fetchrow_hashref) {
1618 $user_perms->{$perm->{'flag'}}->{$perm->{'code'}} = 1;
1623 =head2 get_all_subpermissions
1625 my $perm_hashref = get_all_subpermissions();
1627 Returns a hashref of hashrefs defining all specific
1628 permissions currently defined. The return value
1629 has the same structure as that of C<get_user_subpermissions>,
1630 except that the innermost hash value is the description
1631 of the subpermission.
1635 sub get_all_subpermissions {
1636 my $dbh = C4::Context->dbh;
1637 my $sth = $dbh->prepare("SELECT flag, code, description
1639 JOIN userflags ON (module_bit = bit)");
1643 while (my $perm = $sth->fetchrow_hashref) {
1644 $all_perms->{$perm->{'flag'}}->{$perm->{'code'}} = $perm->{'description'};
1649 =head2 haspermission
1651 $flags = ($userid, $flagsrequired);
1653 C<$userid> the userid of the member
1654 C<$flags> is a hashref of required flags like C<$borrower-<{authflags}>
1656 Returns member's flags or 0 if a permission is not met.
1661 my ($userid, $flagsrequired) = @_;
1662 my $sth = C4::Context->dbh->prepare("SELECT flags FROM borrowers WHERE userid=?");
1663 $sth->execute($userid);
1664 my $flags = getuserflags($sth->fetchrow(), $userid);
1665 if ( $userid eq C4::Context->config('user') ) {
1666 # Super User Account from /etc/koha.conf
1667 $flags->{'superlibrarian'} = 1;
1669 elsif ( $userid eq 'demo' && C4::Context->config('demo') ) {
1670 # Demo user that can do "anything" (demo=1 in /etc/koha.conf)
1671 $flags->{'superlibrarian'} = 1;
1674 return $flags if $flags->{superlibrarian};
1676 foreach my $module ( keys %$flagsrequired ) {
1677 my $subperm = $flagsrequired->{$module};
1678 if ($subperm eq '*') {
1679 return 0 unless ( $flags->{$module} == 1 or ref($flags->{$module}) );
1681 return 0 unless ( $flags->{$module} == 1 or
1682 ( ref($flags->{$module}) and
1683 exists $flags->{$module}->{$subperm} and
1684 $flags->{$module}->{$subperm} == 1
1690 #FIXME - This fcn should return the failed permission so a suitable error msg can be delivered.
1694 sub getborrowernumber {
1696 my $userenv = C4::Context->userenv;
1697 if ( defined( $userenv ) && ref( $userenv ) eq 'HASH' && $userenv->{number} ) {
1698 return $userenv->{number};
1700 my $dbh = C4::Context->dbh;
1701 for my $field ( 'userid', 'cardnumber' ) {
1703 $dbh->prepare("select borrowernumber from borrowers where $field=?");
1704 $sth->execute($userid);
1706 my ($bnumber) = $sth->fetchrow;
1714 END { } # module clean-up code here (global destructor)