3 # Copyright 2000-2002 Katipo Communications
5 # This file is part of Koha.
7 # Koha is free software; you can redistribute it and/or modify it under the
8 # terms of the GNU General Public License as published by the Free Software
9 # Foundation; either version 2 of the License, or (at your option) any later
12 # Koha is distributed in the hope that it will be useful, but WITHOUT ANY
13 # WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
14 # A PARTICULAR PURPOSE. See the GNU General Public License for more details.
16 # You should have received a copy of the GNU General Public License along
17 # with Koha; if not, write to the Free Software Foundation, Inc.,
18 # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
22 use Digest::MD5 qw(md5_base64);
23 use Storable qw(thaw freeze);
29 use C4::Templates; # to get the template
30 use C4::Branch; # GetBranches
31 use C4::VirtualShelves;
32 use POSIX qw/strftime/;
33 use List::MoreUtils qw/ any /;
36 use vars qw($VERSION @ISA @EXPORT @EXPORT_OK %EXPORT_TAGS $debug $ldap $cas $caslogout);
39 sub psgi_env { any { /^psgi\./ } keys %ENV }
41 if ( psgi_env ) { die 'psgi:exit' }
44 $VERSION = 3.07.00.049; # set version for version checking
48 @EXPORT = qw(&checkauth &get_template_and_user &haspermission &get_user_subpermissions);
49 @EXPORT_OK = qw(&check_api_auth &get_session &check_cookie_auth &checkpw &get_all_subpermissions &get_user_subpermissions);
50 %EXPORT_TAGS = ( EditPermissions => [qw(get_all_subpermissions get_user_subpermissions)] );
51 $ldap = C4::Context->config('useldapserver') || 0;
52 $cas = C4::Context->preference('casAuthentication');
53 $caslogout = C4::Context->preference('casLogout');
54 require C4::Auth_with_cas; # no import
56 require C4::Auth_with_ldap;
57 import C4::Auth_with_ldap qw(checkpw_ldap);
60 import C4::Auth_with_cas qw(check_api_auth_cas checkpw_cas login_cas logout_cas login_cas_url);
67 C4::Auth - Authenticates Koha users
77 my ($template, $borrowernumber, $cookie)
78 = get_template_and_user(
80 template_name => "opac-main.tmpl",
84 flagsrequired => {borrow => 1, catalogue => '*', tools => 'import_patrons' },
88 output_html_with_http_headers $query, $cookie, $template->output;
92 The main function of this module is to provide
93 authentification. However the get_template_and_user function has
94 been provided so that a users login information is passed along
95 automatically. This gets loaded into the template.
99 =head2 get_template_and_user
101 my ($template, $borrowernumber, $cookie)
102 = get_template_and_user(
104 template_name => "opac-main.tmpl",
107 authnotrequired => 1,
108 flagsrequired => {borrow => 1, catalogue => '*', tools => 'import_patrons' },
112 This call passes the C<query>, C<flagsrequired> and C<authnotrequired>
113 to C<&checkauth> (in this module) to perform authentification.
114 See C<&checkauth> for an explanation of these parameters.
116 The C<template_name> is then used to find the correct template for
117 the page. The authenticated users details are loaded onto the
118 template in the HTML::Template LOOP variable C<USER_INFO>. Also the
119 C<sessionID> is passed to the template. This can be used in templates
120 if cookies are disabled. It needs to be put as and input to every
123 More information on the C<gettemplate> sub can be found in the
128 my $SEARCH_HISTORY_INSERT_SQL =<<EOQ;
129 INSERT INTO search_history(userid, sessionid, query_desc, query_cgi, total, time )
130 VALUES ( ?, ?, ?, ?, ?, FROM_UNIXTIME(?))
133 sub get_template_and_user {
136 C4::Templates::gettemplate( $in->{'template_name'}, $in->{'type'}, $in->{'query'}, $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 = @{thaw($searchcookie) || []};
257 if (@recentSearches) {
258 my $sth = $dbh->prepare($SEARCH_HISTORY_INSERT_SQL);
259 $sth->execute( $borrowernumber,
260 $in->{'query'}->cookie("CGISESSID"),
265 ) foreach @recentSearches;
267 # And then, delete the cookie's content
268 my $newsearchcookie = $in->{'query'}->cookie(
269 -name => 'KohaOpacRecentSearches',
270 -value => freeze([]),
274 $cookie = [$cookie, $newsearchcookie];
279 else { # if this is an anonymous session, setup to display public lists...
281 $template->param( sessionID => $sessionID );
283 my ($total, $pubshelves) = C4::VirtualShelves::GetSomeShelfNames(undef, 'MASTHEAD');
285 pubshelves => $total->{pubtotal},
286 pubshelvesloop => $pubshelves,
289 # Anonymous opac search history
290 # If opac search history is enabled and at least one search has already been performed
291 if (C4::Context->preference('EnableOpacSearchHistory')) {
292 my $searchcookie = $in->{'query'}->cookie('KohaOpacRecentSearches');
294 $searchcookie = uri_unescape($searchcookie);
295 my @recentSearches = @{thaw($searchcookie) || []};
296 # We show the link in opac
297 if (@recentSearches) {
298 $template->param(ShowOpacRecentSearchLink => 1);
303 if(C4::Context->preference('dateformat')){
304 $template->param(dateformat => C4::Context->preference('dateformat'))
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"),
1027 $template->param( OpacPublic => C4::Context->preference("OpacPublic"));
1028 $template->param( loginprompt => 1 ) unless $info{'nopermission'};
1032 # Is authentication against multiple CAS servers enabled?
1033 if (C4::Auth_with_cas::multipleAuth && !$casparam) {
1034 my $casservers = C4::Auth_with_cas::getMultipleAuth();
1036 foreach my $key (keys %$casservers) {
1037 push @tmplservers, {name => $key, value => login_cas_url($query, $key) . "?cas=$key" };
1040 casServersLoop => \@tmplservers
1044 casServerUrl => login_cas_url($query),
1049 invalidCasLogin => $info{'invalidCasLogin'}
1053 my $self_url = $query->url( -absolute => 1 );
1056 LibraryName => C4::Context->preference("LibraryName"),
1058 $template->param( %info );
1059 # $cookie = $query->cookie(CGISESSID => $session->id
1061 print $query->header(
1062 -type => 'text/html',
1063 -charset => 'utf-8',
1070 =head2 check_api_auth
1072 ($status, $cookie, $sessionId) = check_api_auth($query, $userflags);
1074 Given a CGI query containing the parameters 'userid' and 'password' and/or a session
1075 cookie, determine if the user has the privileges specified by C<$userflags>.
1077 C<check_api_auth> is is meant for authenticating users of web services, and
1078 consequently will always return and will not attempt to redirect the user
1081 If a valid session cookie is already present, check_api_auth will return a status
1082 of "ok", the cookie, and the Koha session ID.
1084 If no session cookie is present, check_api_auth will check the 'userid' and 'password
1085 parameters and create a session cookie and Koha session if the supplied credentials
1088 Possible return values in C<$status> are:
1092 =item "ok" -- user authenticated; C<$cookie> and C<$sessionid> have valid values.
1094 =item "failed" -- credentials are not correct; C<$cookie> and C<$sessionid> are undef
1096 =item "maintenance" -- DB is in maintenance mode; no login possible at the moment
1098 =item "expired -- session cookie has expired; API user should resubmit userid and password
1104 sub check_api_auth {
1106 my $flagsrequired = shift;
1108 my $dbh = C4::Context->dbh;
1109 my $timeout = _timeout_syspref();
1111 unless (C4::Context->preference('Version')) {
1112 # database has not been installed yet
1113 return ("maintenance", undef, undef);
1115 my $kohaversion=C4::Context::KOHAVERSION;
1116 $kohaversion =~ s/(.*\..*)\.(.*)\.(.*)/$1$2$3/;
1117 if (C4::Context->preference('Version') < $kohaversion) {
1118 # database in need of version update; assume that
1119 # no API should be called while databsae is in
1121 return ("maintenance", undef, undef);
1124 # FIXME -- most of what follows is a copy-and-paste
1125 # of code from checkauth. There is an obvious need
1126 # for refactoring to separate the various parts of
1127 # the authentication code, but as of 2007-11-19 this
1128 # is deferred so as to not introduce bugs into the
1129 # regular authentication code for Koha 3.0.
1131 # see if we have a valid session cookie already
1132 # however, if a userid parameter is present (i.e., from
1133 # a form submission, assume that any current cookie
1135 my $sessionID = undef;
1136 unless ($query->param('userid')) {
1137 $sessionID = $query->cookie("CGISESSID");
1139 if ($sessionID && not ($cas && $query->param('PT')) ) {
1140 my $session = get_session($sessionID);
1141 C4::Context->_new_userenv($sessionID);
1143 C4::Context::set_userenv(
1144 $session->param('number'), $session->param('id'),
1145 $session->param('cardnumber'), $session->param('firstname'),
1146 $session->param('surname'), $session->param('branch'),
1147 $session->param('branchname'), $session->param('flags'),
1148 $session->param('emailaddress'), $session->param('branchprinter')
1151 my $ip = $session->param('ip');
1152 my $lasttime = $session->param('lasttime');
1153 my $userid = $session->param('id');
1154 if ( $lasttime < time() - $timeout ) {
1157 C4::Context->_unset_userenv($sessionID);
1160 return ("expired", undef, undef);
1161 } elsif ( $ip ne $ENV{'REMOTE_ADDR'} ) {
1162 # IP address changed
1164 C4::Context->_unset_userenv($sessionID);
1167 return ("expired", undef, undef);
1169 my $cookie = $query->cookie(
1170 -name => 'CGISESSID',
1171 -value => $session->id,
1174 $session->param('lasttime',time());
1175 my $flags = haspermission($userid, $flagsrequired);
1177 return ("ok", $cookie, $sessionID);
1180 C4::Context->_unset_userenv($sessionID);
1183 return ("failed", undef, undef);
1187 return ("expired", undef, undef);
1191 my $userid = $query->param('userid');
1192 my $password = $query->param('password');
1193 my ($return, $cardnumber);
1196 if ($cas && $query->param('PT')) {
1198 $debug and print STDERR "## check_api_auth - checking CAS\n";
1199 # In case of a CAS authentication, we use the ticket instead of the password
1200 my $PT = $query->param('PT');
1201 ($return,$cardnumber,$userid) = check_api_auth_cas($dbh, $PT, $query); # EXTERNAL AUTH
1203 # User / password auth
1204 unless ($userid and $password) {
1205 # caller did something wrong, fail the authenticateion
1206 return ("failed", undef, undef);
1208 ( $return, $cardnumber ) = checkpw( $dbh, $userid, $password, $query );
1211 if ($return and haspermission( $userid, $flagsrequired)) {
1212 my $session = get_session("");
1213 return ("failed", undef, undef) unless $session;
1215 my $sessionID = $session->id;
1216 C4::Context->_new_userenv($sessionID);
1217 my $cookie = $query->cookie(
1218 -name => 'CGISESSID',
1219 -value => $sessionID,
1222 if ( $return == 1 ) {
1224 $borrowernumber, $firstname, $surname,
1225 $userflags, $branchcode, $branchname,
1226 $branchprinter, $emailaddress
1230 "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=?"
1232 $sth->execute($userid);
1234 $borrowernumber, $firstname, $surname,
1235 $userflags, $branchcode, $branchname,
1236 $branchprinter, $emailaddress
1237 ) = $sth->fetchrow if ( $sth->rows );
1239 unless ($sth->rows ) {
1240 my $sth = $dbh->prepare(
1241 "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=?"
1243 $sth->execute($cardnumber);
1245 $borrowernumber, $firstname, $surname,
1246 $userflags, $branchcode, $branchname,
1247 $branchprinter, $emailaddress
1248 ) = $sth->fetchrow if ( $sth->rows );
1250 unless ( $sth->rows ) {
1251 $sth->execute($userid);
1253 $borrowernumber, $firstname, $surname, $userflags,
1254 $branchcode, $branchname, $branchprinter, $emailaddress
1255 ) = $sth->fetchrow if ( $sth->rows );
1259 my $ip = $ENV{'REMOTE_ADDR'};
1260 # if they specify at login, use that
1261 if ($query->param('branch')) {
1262 $branchcode = $query->param('branch');
1263 $branchname = GetBranchName($branchcode);
1265 my $branches = GetBranches();
1267 foreach my $br ( keys %$branches ) {
1268 # now we work with the treatment of ip
1269 my $domain = $branches->{$br}->{'branchip'};
1270 if ( $domain && $ip =~ /^$domain/ ) {
1271 $branchcode = $branches->{$br}->{'branchcode'};
1273 # new op dev : add the branchprinter and branchname in the cookie
1274 $branchprinter = $branches->{$br}->{'branchprinter'};
1275 $branchname = $branches->{$br}->{'branchname'};
1278 $session->param('number',$borrowernumber);
1279 $session->param('id',$userid);
1280 $session->param('cardnumber',$cardnumber);
1281 $session->param('firstname',$firstname);
1282 $session->param('surname',$surname);
1283 $session->param('branch',$branchcode);
1284 $session->param('branchname',$branchname);
1285 $session->param('flags',$userflags);
1286 $session->param('emailaddress',$emailaddress);
1287 $session->param('ip',$session->remote_addr());
1288 $session->param('lasttime',time());
1289 } elsif ( $return == 2 ) {
1290 #We suppose the user is the superlibrarian
1291 $session->param('number',0);
1292 $session->param('id',C4::Context->config('user'));
1293 $session->param('cardnumber',C4::Context->config('user'));
1294 $session->param('firstname',C4::Context->config('user'));
1295 $session->param('surname',C4::Context->config('user'));
1296 $session->param('branch','NO_LIBRARY_SET');
1297 $session->param('branchname','NO_LIBRARY_SET');
1298 $session->param('flags',1);
1299 $session->param('emailaddress', C4::Context->preference('KohaAdminEmailAddress'));
1300 $session->param('ip',$session->remote_addr());
1301 $session->param('lasttime',time());
1303 C4::Context::set_userenv(
1304 $session->param('number'), $session->param('id'),
1305 $session->param('cardnumber'), $session->param('firstname'),
1306 $session->param('surname'), $session->param('branch'),
1307 $session->param('branchname'), $session->param('flags'),
1308 $session->param('emailaddress'), $session->param('branchprinter')
1310 return ("ok", $cookie, $sessionID);
1312 return ("failed", undef, undef);
1317 =head2 check_cookie_auth
1319 ($status, $sessionId) = check_api_auth($cookie, $userflags);
1321 Given a CGISESSID cookie set during a previous login to Koha, determine
1322 if the user has the privileges specified by C<$userflags>.
1324 C<check_cookie_auth> is meant for authenticating special services
1325 such as tools/upload-file.pl that are invoked by other pages that
1326 have been authenticated in the usual way.
1328 Possible return values in C<$status> are:
1332 =item "ok" -- user authenticated; C<$sessionID> have valid values.
1334 =item "failed" -- credentials are not correct; C<$sessionid> are undef
1336 =item "maintenance" -- DB is in maintenance mode; no login possible at the moment
1338 =item "expired -- session cookie has expired; API user should resubmit userid and password
1344 sub check_cookie_auth {
1346 my $flagsrequired = shift;
1348 my $dbh = C4::Context->dbh;
1349 my $timeout = _timeout_syspref();
1351 unless (C4::Context->preference('Version')) {
1352 # database has not been installed yet
1353 return ("maintenance", undef);
1355 my $kohaversion=C4::Context::KOHAVERSION;
1356 $kohaversion =~ s/(.*\..*)\.(.*)\.(.*)/$1$2$3/;
1357 if (C4::Context->preference('Version') < $kohaversion) {
1358 # database in need of version update; assume that
1359 # no API should be called while databsae is in
1361 return ("maintenance", undef);
1364 # FIXME -- most of what follows is a copy-and-paste
1365 # of code from checkauth. There is an obvious need
1366 # for refactoring to separate the various parts of
1367 # the authentication code, but as of 2007-11-23 this
1368 # is deferred so as to not introduce bugs into the
1369 # regular authentication code for Koha 3.0.
1371 # see if we have a valid session cookie already
1372 # however, if a userid parameter is present (i.e., from
1373 # a form submission, assume that any current cookie
1375 unless (defined $cookie and $cookie) {
1376 return ("failed", undef);
1378 my $sessionID = $cookie;
1379 my $session = get_session($sessionID);
1380 C4::Context->_new_userenv($sessionID);
1382 C4::Context::set_userenv(
1383 $session->param('number'), $session->param('id'),
1384 $session->param('cardnumber'), $session->param('firstname'),
1385 $session->param('surname'), $session->param('branch'),
1386 $session->param('branchname'), $session->param('flags'),
1387 $session->param('emailaddress'), $session->param('branchprinter')
1390 my $ip = $session->param('ip');
1391 my $lasttime = $session->param('lasttime');
1392 my $userid = $session->param('id');
1393 if ( $lasttime < time() - $timeout ) {
1396 C4::Context->_unset_userenv($sessionID);
1399 return ("expired", undef);
1400 } elsif ( $ip ne $ENV{'REMOTE_ADDR'} ) {
1401 # IP address changed
1403 C4::Context->_unset_userenv($sessionID);
1406 return ("expired", undef);
1408 $session->param('lasttime',time());
1409 my $flags = haspermission($userid, $flagsrequired);
1411 return ("ok", $sessionID);
1414 C4::Context->_unset_userenv($sessionID);
1417 return ("failed", undef);
1421 return ("expired", undef);
1428 my $session = get_session($sessionID);
1430 Given a session ID, retrieve the CGI::Session object used to store
1431 the session's state. The session object can be used to store
1432 data that needs to be accessed by different scripts during a
1435 If the C<$sessionID> parameter is an empty string, a new session
1441 my $sessionID = shift;
1442 my $storage_method = C4::Context->preference('SessionStorage');
1443 my $dbh = C4::Context->dbh;
1445 if ($storage_method eq 'mysql'){
1446 $session = new CGI::Session("driver:MySQL;serializer:yaml;id:md5", $sessionID, {Handle=>$dbh});
1448 elsif ($storage_method eq 'Pg') {
1449 $session = new CGI::Session("driver:PostgreSQL;serializer:yaml;id:md5", $sessionID, {Handle=>$dbh});
1451 elsif ($storage_method eq 'memcached' && C4::Context->ismemcached){
1452 $session = new CGI::Session("driver:memcached;serializer:yaml;id:md5", $sessionID, { Memcached => C4::Context->memcached } );
1455 # catch all defaults to tmp should work on all systems
1456 $session = new CGI::Session("driver:File;serializer:yaml;id:md5", $sessionID, {Directory=>'/tmp'});
1463 my ( $dbh, $userid, $password, $query ) = @_;
1465 $debug and print STDERR "## checkpw - checking LDAP\n";
1466 my ($retval,$retcard,$retuserid) = checkpw_ldap(@_); # EXTERNAL AUTH
1467 ($retval) and return ($retval,$retcard,$retuserid);
1470 if ($cas && $query && $query->param('ticket')) {
1471 $debug and print STDERR "## checkpw - checking CAS\n";
1472 # In case of a CAS authentication, we use the ticket instead of the password
1473 my $ticket = $query->param('ticket');
1474 my ($retval,$retcard,$retuserid) = checkpw_cas($dbh, $ticket, $query); # EXTERNAL AUTH
1475 ($retval) and return ($retval,$retcard,$retuserid);
1482 "select password,cardnumber,borrowernumber,userid,firstname,surname,branchcode,flags from borrowers where userid=?"
1484 $sth->execute($userid);
1486 my ( $md5password, $cardnumber, $borrowernumber, $userid, $firstname,
1487 $surname, $branchcode, $flags )
1489 if ( md5_base64($password) eq $md5password and $md5password ne "!") {
1491 C4::Context->set_userenv( "$borrowernumber", $userid, $cardnumber,
1492 $firstname, $surname, $branchcode, $flags );
1493 return 1, $cardnumber, $userid;
1498 "select password,cardnumber,borrowernumber,userid, firstname,surname,branchcode,flags from borrowers where cardnumber=?"
1500 $sth->execute($userid);
1502 my ( $md5password, $cardnumber, $borrowernumber, $userid, $firstname,
1503 $surname, $branchcode, $flags )
1505 if ( md5_base64($password) eq $md5password ) {
1507 C4::Context->set_userenv( $borrowernumber, $userid, $cardnumber,
1508 $firstname, $surname, $branchcode, $flags );
1509 return 1, $cardnumber, $userid;
1512 if ( $userid && $userid eq C4::Context->config('user')
1513 && "$password" eq C4::Context->config('pass') )
1516 # Koha superuser account
1517 # C4::Context->set_userenv(0,0,C4::Context->config('user'),C4::Context->config('user'),C4::Context->config('user'),"",1);
1520 if ( $userid && $userid eq 'demo'
1521 && "$password" eq 'demo'
1522 && C4::Context->config('demo') )
1525 # DEMO => the demo user is allowed to do everything (if demo set to 1 in koha.conf
1526 # some features won't be effective : modify systempref, modify MARC structure,
1534 my $authflags = getuserflags($flags, $userid, [$dbh]);
1536 Translates integer flags into permissions strings hash.
1538 C<$flags> is the integer userflags value ( borrowers.userflags )
1539 C<$userid> is the members.userid, used for building subpermissions
1540 C<$authflags> is a hashref of permissions
1547 my $dbh = @_ ? shift : C4::Context->dbh;
1550 # I don't want to do this, but if someone logs in as the database
1551 # user, it would be preferable not to spam them to death with
1552 # numeric warnings. So, we make $flags numeric.
1553 no warnings 'numeric';
1556 my $sth = $dbh->prepare("SELECT bit, flag, defaulton FROM userflags");
1559 while ( my ( $bit, $flag, $defaulton ) = $sth->fetchrow ) {
1560 if ( ( $flags & ( 2**$bit ) ) || $defaulton ) {
1561 $userflags->{$flag} = 1;
1564 $userflags->{$flag} = 0;
1568 # get subpermissions and merge with top-level permissions
1569 my $user_subperms = get_user_subpermissions($userid);
1570 foreach my $module (keys %$user_subperms) {
1571 next if $userflags->{$module} == 1; # user already has permission for everything in this module
1572 $userflags->{$module} = $user_subperms->{$module};
1578 =head2 get_user_subpermissions
1580 $user_perm_hashref = get_user_subpermissions($userid);
1582 Given the userid (note, not the borrowernumber) of a staff user,
1583 return a hashref of hashrefs of the specific subpermissions
1584 accorded to the user. An example return is
1588 export_catalog => 1,
1589 import_patrons => 1,
1593 The top-level hash-key is a module or function code from
1594 userflags.flag, while the second-level key is a code
1597 The results of this function do not give a complete picture
1598 of the functions that a staff user can access; it is also
1599 necessary to check borrowers.flags.
1603 sub get_user_subpermissions {
1606 my $dbh = C4::Context->dbh;
1607 my $sth = $dbh->prepare("SELECT flag, user_permissions.code
1608 FROM user_permissions
1609 JOIN permissions USING (module_bit, code)
1610 JOIN userflags ON (module_bit = bit)
1611 JOIN borrowers USING (borrowernumber)
1613 $sth->execute($userid);
1615 my $user_perms = {};
1616 while (my $perm = $sth->fetchrow_hashref) {
1617 $user_perms->{$perm->{'flag'}}->{$perm->{'code'}} = 1;
1622 =head2 get_all_subpermissions
1624 my $perm_hashref = get_all_subpermissions();
1626 Returns a hashref of hashrefs defining all specific
1627 permissions currently defined. The return value
1628 has the same structure as that of C<get_user_subpermissions>,
1629 except that the innermost hash value is the description
1630 of the subpermission.
1634 sub get_all_subpermissions {
1635 my $dbh = C4::Context->dbh;
1636 my $sth = $dbh->prepare("SELECT flag, code, description
1638 JOIN userflags ON (module_bit = bit)");
1642 while (my $perm = $sth->fetchrow_hashref) {
1643 $all_perms->{$perm->{'flag'}}->{$perm->{'code'}} = $perm->{'description'};
1648 =head2 haspermission
1650 $flags = ($userid, $flagsrequired);
1652 C<$userid> the userid of the member
1653 C<$flags> is a hashref of required flags like C<$borrower-<{authflags}>
1655 Returns member's flags or 0 if a permission is not met.
1660 my ($userid, $flagsrequired) = @_;
1661 my $sth = C4::Context->dbh->prepare("SELECT flags FROM borrowers WHERE userid=?");
1662 $sth->execute($userid);
1663 my $flags = getuserflags($sth->fetchrow(), $userid);
1664 if ( $userid eq C4::Context->config('user') ) {
1665 # Super User Account from /etc/koha.conf
1666 $flags->{'superlibrarian'} = 1;
1668 elsif ( $userid eq 'demo' && C4::Context->config('demo') ) {
1669 # Demo user that can do "anything" (demo=1 in /etc/koha.conf)
1670 $flags->{'superlibrarian'} = 1;
1673 return $flags if $flags->{superlibrarian};
1675 foreach my $module ( keys %$flagsrequired ) {
1676 my $subperm = $flagsrequired->{$module};
1677 if ($subperm eq '*') {
1678 return 0 unless ( $flags->{$module} == 1 or ref($flags->{$module}) );
1680 return 0 unless ( $flags->{$module} == 1 or
1681 ( ref($flags->{$module}) and
1682 exists $flags->{$module}->{$subperm} and
1683 $flags->{$module}->{$subperm} == 1
1689 #FIXME - This fcn should return the failed permission so a suitable error msg can be delivered.
1693 sub getborrowernumber {
1695 my $userenv = C4::Context->userenv;
1696 if ( defined( $userenv ) && ref( $userenv ) eq 'HASH' && $userenv->{number} ) {
1697 return $userenv->{number};
1699 my $dbh = C4::Context->dbh;
1700 for my $field ( 'userid', 'cardnumber' ) {
1702 $dbh->prepare("select borrowernumber from borrowers where $field=?");
1703 $sth->execute($userid);
1705 my ($bnumber) = $sth->fetchrow;
1713 END { } # module clean-up code here (global destructor)