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 ParseSearchHistoryCookie
52 %EXPORT_TAGS = ( EditPermissions => [qw(get_all_subpermissions get_user_subpermissions)] );
53 $ldap = C4::Context->config('useldapserver') || 0;
54 $cas = C4::Context->preference('casAuthentication');
55 $caslogout = C4::Context->preference('casLogout');
56 require C4::Auth_with_cas; # no import
58 require C4::Auth_with_ldap;
59 import C4::Auth_with_ldap qw(checkpw_ldap);
62 import C4::Auth_with_cas qw(check_api_auth_cas checkpw_cas login_cas logout_cas login_cas_url);
69 C4::Auth - Authenticates Koha users
79 my ($template, $borrowernumber, $cookie)
80 = get_template_and_user(
82 template_name => "opac-main.tmpl",
86 flagsrequired => {borrow => 1, catalogue => '*', tools => 'import_patrons' },
90 output_html_with_http_headers $query, $cookie, $template->output;
94 The main function of this module is to provide
95 authentification. However the get_template_and_user function has
96 been provided so that a users login information is passed along
97 automatically. This gets loaded into the template.
101 =head2 get_template_and_user
103 my ($template, $borrowernumber, $cookie)
104 = get_template_and_user(
106 template_name => "opac-main.tmpl",
109 authnotrequired => 0,
110 flagsrequired => {borrow => 1, catalogue => '*', tools => 'import_patrons' },
114 This call passes the C<query>, C<flagsrequired> and C<authnotrequired>
115 to C<&checkauth> (in this module) to perform authentification.
116 See C<&checkauth> for an explanation of these parameters.
118 The C<template_name> is then used to find the correct template for
119 the page. The authenticated users details are loaded onto the
120 template in the HTML::Template LOOP variable C<USER_INFO>. Also the
121 C<sessionID> is passed to the template. This can be used in templates
122 if cookies are disabled. It needs to be put as and input to every
125 More information on the C<gettemplate> sub can be found in the
130 my $SEARCH_HISTORY_INSERT_SQL =<<EOQ;
131 INSERT INTO search_history(userid, sessionid, query_desc, query_cgi, total, time )
132 VALUES ( ?, ?, ?, ?, ?, FROM_UNIXTIME(?))
135 sub get_template_and_user {
138 C4::Templates::gettemplate( $in->{'template_name'}, $in->{'type'}, $in->{'query'}, $in->{'is_plugin'} );
139 my ( $user, $cookie, $sessionID, $flags );
141 $in->{'authnotrequired'} ||= 0;
143 if ( $in->{'template_name'} !~m/maintenance/ ) {
144 ( $user, $cookie, $sessionID, $flags ) = checkauth(
146 $in->{'authnotrequired'},
147 $in->{'flagsrequired'},
155 # It's possible for $user to be the borrowernumber if they don't have a
156 # userid defined (and are logging in through some other method, such
157 # as SSL certs against an email address)
158 $borrowernumber = getborrowernumber($user) if defined($user);
159 if (!defined($borrowernumber) && defined($user)) {
160 my $borrower = C4::Members::GetMember(borrowernumber => $user);
162 $borrowernumber = $user;
163 # A bit of a hack, but I don't know there's a nicer way
165 $user = $borrower->{firstname} . ' ' . $borrower->{surname};
170 $template->param( loggedinusername => $user );
171 $template->param( sessionID => $sessionID );
173 my ($total, $pubshelves, $barshelves) = C4::VirtualShelves::GetSomeShelfNames($borrowernumber, 'MASTHEAD');
175 pubshelves => $total->{pubtotal},
176 pubshelvesloop => $pubshelves,
177 barshelves => $total->{bartotal},
178 barshelvesloop => $barshelves,
181 my ( $borr ) = C4::Members::GetMemberDetails( $borrowernumber );
184 $template->param( "USER_INFO" => \@bordat );
186 my $all_perms = get_all_subpermissions();
188 my @flagroots = qw(circulate catalogue parameters borrowers permissions reserveforothers borrow
189 editcatalogue updatecharges management tools editauthorities serials reports acquisition);
190 # We are going to use the $flags returned by checkauth
191 # to create the template's parameters that will indicate
192 # which menus the user can access.
193 if ( $flags && $flags->{superlibrarian}==1 ) {
194 $template->param( CAN_user_circulate => 1 );
195 $template->param( CAN_user_catalogue => 1 );
196 $template->param( CAN_user_parameters => 1 );
197 $template->param( CAN_user_borrowers => 1 );
198 $template->param( CAN_user_permissions => 1 );
199 $template->param( CAN_user_reserveforothers => 1 );
200 $template->param( CAN_user_borrow => 1 );
201 $template->param( CAN_user_editcatalogue => 1 );
202 $template->param( CAN_user_updatecharges => 1 );
203 $template->param( CAN_user_acquisition => 1 );
204 $template->param( CAN_user_management => 1 );
205 $template->param( CAN_user_tools => 1 );
206 $template->param( CAN_user_editauthorities => 1 );
207 $template->param( CAN_user_serials => 1 );
208 $template->param( CAN_user_reports => 1 );
209 $template->param( CAN_user_staffaccess => 1 );
210 $template->param( CAN_user_plugins => 1 );
211 foreach my $module (keys %$all_perms) {
212 foreach my $subperm (keys %{ $all_perms->{$module} }) {
213 $template->param( "CAN_user_${module}_${subperm}" => 1 );
219 foreach my $module (keys %$all_perms) {
220 if ( $flags->{$module} == 1) {
221 foreach my $subperm (keys %{ $all_perms->{$module} }) {
222 $template->param( "CAN_user_${module}_${subperm}" => 1 );
224 } elsif ( ref($flags->{$module}) ) {
225 foreach my $subperm (keys %{ $flags->{$module} } ) {
226 $template->param( "CAN_user_${module}_${subperm}" => 1 );
233 foreach my $module (keys %$flags) {
234 if ( $flags->{$module} == 1 or ref($flags->{$module}) ) {
235 $template->param( "CAN_user_$module" => 1 );
236 if ($module eq "parameters") {
237 $template->param( CAN_user_management => 1 );
242 # Logged-in opac search history
243 # If the requested template is an opac one and opac search history is enabled
244 if ($in->{type} eq 'opac' && C4::Context->preference('EnableOpacSearchHistory')) {
245 my $dbh = C4::Context->dbh;
246 my $query = "SELECT COUNT(*) FROM search_history WHERE userid=?";
247 my $sth = $dbh->prepare($query);
248 $sth->execute($borrowernumber);
250 # If at least one search has already been performed
251 if ($sth->fetchrow_array > 0) {
252 # We show the link in opac
253 $template->param(ShowOpacRecentSearchLink => 1);
256 # And if there's a cookie with searches performed when the user was not logged in,
257 # we add them to the logged-in search history
258 my @recentSearches = ParseSearchHistoryCookie($in->{'query'});
259 if (@recentSearches) {
260 my $sth = $dbh->prepare($SEARCH_HISTORY_INSERT_SQL);
261 $sth->execute( $borrowernumber,
262 $in->{'query'}->cookie("CGISESSID"),
267 ) foreach @recentSearches;
269 # And then, delete the cookie's content
270 my $newsearchcookie = $in->{'query'}->cookie(
271 -name => 'KohaOpacRecentSearches',
272 -value => encode_json([]),
276 $cookie = [$cookie, $newsearchcookie];
280 else { # if this is an anonymous session, setup to display public lists...
282 $template->param( sessionID => $sessionID );
284 my ($total, $pubshelves) = C4::VirtualShelves::GetSomeShelfNames(undef, 'MASTHEAD');
286 pubshelves => $total->{pubtotal},
287 pubshelvesloop => $pubshelves,
290 # Anonymous opac search history
291 # If opac search history is enabled and at least one search has already been performed
292 if (C4::Context->preference('EnableOpacSearchHistory')) {
293 my @recentSearches = ParseSearchHistoryCookie($in->{'query'});
294 if (@recentSearches) {
295 $template->param(ShowOpacRecentSearchLink => 1);
299 if(C4::Context->preference('dateformat')){
300 $template->param(dateformat => C4::Context->preference('dateformat'))
303 # these template parameters are set the same regardless of $in->{'type'}
305 "BiblioDefaultView".C4::Context->preference("BiblioDefaultView") => 1,
306 EnhancedMessagingPreferences => C4::Context->preference('EnhancedMessagingPreferences'),
307 GoogleJackets => C4::Context->preference("GoogleJackets"),
308 OpenLibraryCovers => C4::Context->preference("OpenLibraryCovers"),
309 KohaAdminEmailAddress => "" . C4::Context->preference("KohaAdminEmailAddress"),
310 LoginBranchcode => (C4::Context->userenv?C4::Context->userenv->{"branch"}:undef),
311 LoginFirstname => (C4::Context->userenv?C4::Context->userenv->{"firstname"}:"Bel"),
312 LoginSurname => C4::Context->userenv?C4::Context->userenv->{"surname"}:"Inconnu",
313 emailaddress => C4::Context->userenv?C4::Context->userenv->{"emailaddress"}:undef,
314 loggedinpersona => C4::Context->userenv?C4::Context->userenv->{"persona"}:undef,
315 TagsEnabled => C4::Context->preference("TagsEnabled"),
316 hide_marc => C4::Context->preference("hide_marc"),
317 item_level_itypes => C4::Context->preference('item-level_itypes'),
318 patronimages => C4::Context->preference("patronimages"),
319 singleBranchMode => C4::Context->preference("singleBranchMode"),
320 XSLTDetailsDisplay => C4::Context->preference("XSLTDetailsDisplay"),
321 XSLTResultsDisplay => C4::Context->preference("XSLTResultsDisplay"),
322 using_https => $in->{'query'}->https() ? 1 : 0,
323 noItemTypeImages => C4::Context->preference("noItemTypeImages"),
324 marcflavour => C4::Context->preference("marcflavour"),
325 persona => C4::Context->preference("persona"),
327 if ( $in->{'type'} eq "intranet" ) {
329 AmazonCoverImages => C4::Context->preference("AmazonCoverImages"),
330 AutoLocation => C4::Context->preference("AutoLocation"),
331 "BiblioDefaultView".C4::Context->preference("IntranetBiblioDefaultView") => 1,
332 CalendarFirstDayOfWeek => (C4::Context->preference("CalendarFirstDayOfWeek") eq "Sunday")?0:1,
333 CircAutocompl => C4::Context->preference("CircAutocompl"),
334 FRBRizeEditions => C4::Context->preference("FRBRizeEditions"),
335 IndependantBranches => C4::Context->preference("IndependantBranches"),
336 IntranetNav => C4::Context->preference("IntranetNav"),
337 IntranetmainUserblock => C4::Context->preference("IntranetmainUserblock"),
338 LibraryName => C4::Context->preference("LibraryName"),
339 LoginBranchname => (C4::Context->userenv?C4::Context->userenv->{"branchname"}:undef),
340 advancedMARCEditor => C4::Context->preference("advancedMARCEditor"),
341 canreservefromotherbranches => C4::Context->preference('canreservefromotherbranches'),
342 intranetcolorstylesheet => C4::Context->preference("intranetcolorstylesheet"),
343 IntranetFavicon => C4::Context->preference("IntranetFavicon"),
344 intranetreadinghistory => C4::Context->preference("intranetreadinghistory"),
345 intranetstylesheet => C4::Context->preference("intranetstylesheet"),
346 IntranetUserCSS => C4::Context->preference("IntranetUserCSS"),
347 intranetuserjs => C4::Context->preference("intranetuserjs"),
348 intranetbookbag => C4::Context->preference("intranetbookbag"),
349 suggestion => C4::Context->preference("suggestion"),
350 virtualshelves => C4::Context->preference("virtualshelves"),
351 StaffSerialIssueDisplayCount => C4::Context->preference("StaffSerialIssueDisplayCount"),
352 EasyAnalyticalRecords => C4::Context->preference('EasyAnalyticalRecords'),
353 LocalCoverImages => C4::Context->preference('LocalCoverImages'),
354 OPACLocalCoverImages => C4::Context->preference('OPACLocalCoverImages'),
355 AllowMultipleCovers => C4::Context->preference('AllowMultipleCovers'),
356 EnableBorrowerFiles => C4::Context->preference('EnableBorrowerFiles'),
357 UseKohaPlugins => C4::Context->preference('UseKohaPlugins'),
358 UseCourseReserves => C4::Context->preference("UseCourseReserves"),
362 warn "template type should be OPAC, here it is=[" . $in->{'type'} . "]" unless ( $in->{'type'} eq 'opac' );
363 #TODO : replace LibraryName syspref with 'system name', and remove this html processing
364 my $LibraryNameTitle = C4::Context->preference("LibraryName");
365 $LibraryNameTitle =~ s/<(?:\/?)(?:br|p)\s*(?:\/?)>/ /sgi;
366 $LibraryNameTitle =~ s/<(?:[^<>'"]|'(?:[^']*)'|"(?:[^"]*)")*>//sg;
367 # clean up the busc param in the session if the page is not opac-detail
368 if (C4::Context->preference("OpacBrowseResults") && $in->{'template_name'} =~ /opac-(.+)\.(?:tt|tmpl)$/ && $1 !~ /^(?:MARC|ISBD)?detail$/) {
369 my $sessionSearch = get_session($sessionID || $in->{'query'}->cookie("CGISESSID"));
370 $sessionSearch->clear(["busc"]) if ($sessionSearch->param("busc"));
372 # variables passed from CGI: opac_css_override and opac_search_limits.
373 my $opac_search_limit = $ENV{'OPAC_SEARCH_LIMIT'};
374 my $opac_limit_override = $ENV{'OPAC_LIMIT_OVERRIDE'};
376 if (($opac_search_limit && $opac_search_limit =~ /branch:(\w+)/ && $opac_limit_override) || ($in->{'query'}->param('limit') && $in->{'query'}->param('limit') =~ /branch:(\w+)/)){
377 $opac_name = $1; # opac_search_limit is a branch, so we use it.
378 } elsif ( $in->{'query'}->param('multibranchlimit') ) {
379 $opac_name = $in->{'query'}->param('multibranchlimit');
380 } elsif (C4::Context->preference("SearchMyLibraryFirst") && C4::Context->userenv && C4::Context->userenv->{'branch'}) {
381 $opac_name = C4::Context->userenv->{'branch'};
384 opaccolorstylesheet => C4::Context->preference("opaccolorstylesheet"),
385 AnonSuggestions => "" . C4::Context->preference("AnonSuggestions"),
386 AuthorisedValueImages => C4::Context->preference("AuthorisedValueImages"),
387 BranchesLoop => GetBranchesLoop($opac_name),
388 BranchCategoriesLoop => GetBranchCategories( 'searchdomain', 1, $opac_name ),
389 CalendarFirstDayOfWeek => (C4::Context->preference("CalendarFirstDayOfWeek") eq "Sunday")?0:1,
390 LibraryName => "" . C4::Context->preference("LibraryName"),
391 LibraryNameTitle => "" . $LibraryNameTitle,
392 LoginBranchname => C4::Context->userenv?C4::Context->userenv->{"branchname"}:"",
393 OPACAmazonCoverImages => C4::Context->preference("OPACAmazonCoverImages"),
394 OPACFRBRizeEditions => C4::Context->preference("OPACFRBRizeEditions"),
395 OpacHighlightedWords => C4::Context->preference("OpacHighlightedWords"),
396 OPACItemHolds => C4::Context->preference("OPACItemHolds"),
397 OPACShelfBrowser => "". C4::Context->preference("OPACShelfBrowser"),
398 OPACURLOpenInNewWindow => "" . C4::Context->preference("OPACURLOpenInNewWindow"),
399 OPACUserCSS => "". C4::Context->preference("OPACUserCSS"),
400 OPACMobileUserCSS => "". C4::Context->preference("OPACMobileUserCSS"),
401 OPACViewOthersSuggestions => "" . C4::Context->preference("OPACViewOthersSuggestions"),
402 OpacAuthorities => C4::Context->preference("OpacAuthorities"),
403 OPACBaseURL => ($in->{'query'}->https() ? "https://" : "http://") . $ENV{'SERVER_NAME'} .
404 ($ENV{'SERVER_PORT'} eq ($in->{'query'}->https() ? "443" : "80") ? '' : ":$ENV{'SERVER_PORT'}"),
405 opac_css_override => $ENV{'OPAC_CSS_OVERRIDE'},
406 opac_search_limit => $opac_search_limit,
407 opac_limit_override => $opac_limit_override,
408 OpacBrowser => C4::Context->preference("OpacBrowser"),
409 OpacCloud => C4::Context->preference("OpacCloud"),
410 OpacKohaUrl => C4::Context->preference("OpacKohaUrl"),
411 OpacMainUserBlock => "" . C4::Context->preference("OpacMainUserBlock"),
412 OpacMainUserBlockMobile => "" . C4::Context->preference("OpacMainUserBlockMobile"),
413 OpacShowFiltersPulldownMobile => C4::Context->preference("OpacShowFiltersPulldownMobile"),
414 OpacShowLibrariesPulldownMobile => C4::Context->preference("OpacShowLibrariesPulldownMobile"),
415 OpacNav => "" . C4::Context->preference("OpacNav"),
416 OpacNavRight => "" . C4::Context->preference("OpacNavRight"),
417 OpacNavBottom => "" . C4::Context->preference("OpacNavBottom"),
418 OpacPasswordChange => C4::Context->preference("OpacPasswordChange"),
419 OPACPatronDetails => C4::Context->preference("OPACPatronDetails"),
420 OPACPrivacy => C4::Context->preference("OPACPrivacy"),
421 OPACFinesTab => C4::Context->preference("OPACFinesTab"),
422 OpacTopissue => C4::Context->preference("OpacTopissue"),
423 RequestOnOpac => C4::Context->preference("RequestOnOpac"),
424 'Version' => C4::Context->preference('Version'),
425 hidelostitems => C4::Context->preference("hidelostitems"),
426 mylibraryfirst => (C4::Context->preference("SearchMyLibraryFirst") && C4::Context->userenv) ? C4::Context->userenv->{'branch'} : '',
427 opaclayoutstylesheet => "" . C4::Context->preference("opaclayoutstylesheet"),
428 opacbookbag => "" . C4::Context->preference("opacbookbag"),
429 opaccredits => "" . C4::Context->preference("opaccredits"),
430 OpacFavicon => C4::Context->preference("OpacFavicon"),
431 opacheader => "" . C4::Context->preference("opacheader"),
432 opaclanguagesdisplay => "" . C4::Context->preference("opaclanguagesdisplay"),
433 opacreadinghistory => C4::Context->preference("opacreadinghistory"),
434 opacsmallimage => "" . C4::Context->preference("opacsmallimage"),
435 opacuserjs => C4::Context->preference("opacuserjs"),
436 opacuserlogin => "" . C4::Context->preference("opacuserlogin"),
437 ShowReviewer => C4::Context->preference("ShowReviewer"),
438 ShowReviewerPhoto => C4::Context->preference("ShowReviewerPhoto"),
439 suggestion => "" . C4::Context->preference("suggestion"),
440 virtualshelves => "" . C4::Context->preference("virtualshelves"),
441 OPACSerialIssueDisplayCount => C4::Context->preference("OPACSerialIssueDisplayCount"),
442 OPACXSLTDetailsDisplay => C4::Context->preference("OPACXSLTDetailsDisplay"),
443 OPACXSLTResultsDisplay => C4::Context->preference("OPACXSLTResultsDisplay"),
444 SyndeticsClientCode => C4::Context->preference("SyndeticsClientCode"),
445 SyndeticsEnabled => C4::Context->preference("SyndeticsEnabled"),
446 SyndeticsCoverImages => C4::Context->preference("SyndeticsCoverImages"),
447 SyndeticsTOC => C4::Context->preference("SyndeticsTOC"),
448 SyndeticsSummary => C4::Context->preference("SyndeticsSummary"),
449 SyndeticsEditions => C4::Context->preference("SyndeticsEditions"),
450 SyndeticsExcerpt => C4::Context->preference("SyndeticsExcerpt"),
451 SyndeticsReviews => C4::Context->preference("SyndeticsReviews"),
452 SyndeticsAuthorNotes => C4::Context->preference("SyndeticsAuthorNotes"),
453 SyndeticsAwards => C4::Context->preference("SyndeticsAwards"),
454 SyndeticsSeries => C4::Context->preference("SyndeticsSeries"),
455 SyndeticsCoverImageSize => C4::Context->preference("SyndeticsCoverImageSize"),
456 OPACLocalCoverImages => C4::Context->preference("OPACLocalCoverImages"),
457 PatronSelfRegistration => C4::Context->preference("PatronSelfRegistration"),
458 PatronSelfRegistrationDefaultCategory => C4::Context->preference("PatronSelfRegistrationDefaultCategory"),
461 $template->param(OpacPublic => '1') if ($user || C4::Context->preference("OpacPublic"));
463 return ( $template, $borrowernumber, $cookie, $flags);
468 ($userid, $cookie, $sessionID) = &checkauth($query, $noauth, $flagsrequired, $type);
470 Verifies that the user is authorized to run this script. If
471 the user is authorized, a (userid, cookie, session-id, flags)
472 quadruple is returned. If the user is not authorized but does
473 not have the required privilege (see $flagsrequired below), it
474 displays an error page and exits. Otherwise, it displays the
475 login page and exits.
477 Note that C<&checkauth> will return if and only if the user
478 is authorized, so it should be called early on, before any
479 unfinished operations (e.g., if you've opened a file, then
480 C<&checkauth> won't close it for you).
482 C<$query> is the CGI object for the script calling C<&checkauth>.
484 The C<$noauth> argument is optional. If it is set, then no
485 authorization is required for the script.
487 C<&checkauth> fetches user and session information from C<$query> and
488 ensures that the user is authorized to run scripts that require
491 The C<$flagsrequired> argument specifies the required privileges
492 the user must have if the username and password are correct.
493 It should be specified as a reference-to-hash; keys in the hash
494 should be the "flags" for the user, as specified in the Members
495 intranet module. Any key specified must correspond to a "flag"
496 in the userflags table. E.g., { circulate => 1 } would specify
497 that the user must have the "circulate" privilege in order to
498 proceed. To make sure that access control is correct, the
499 C<$flagsrequired> parameter must be specified correctly.
501 Koha also has a concept of sub-permissions, also known as
502 granular permissions. This makes the value of each key
503 in the C<flagsrequired> hash take on an additional
508 The user must have access to all subfunctions of the module
509 specified by the hash key.
513 The user must have access to at least one subfunction of the module
514 specified by the hash key.
516 specific permission, e.g., 'export_catalog'
518 The user must have access to the specific subfunction list, which
519 must correspond to a row in the permissions table.
521 The C<$type> argument specifies whether the template should be
522 retrieved from the opac or intranet directory tree. "opac" is
523 assumed if it is not specified; however, if C<$type> is specified,
524 "intranet" is assumed if it is not "opac".
526 If C<$query> does not have a valid session ID associated with it
527 (i.e., the user has not logged in) or if the session has expired,
528 C<&checkauth> presents the user with a login page (from the point of
529 view of the original script, C<&checkauth> does not return). Once the
530 user has authenticated, C<&checkauth> restarts the original script
531 (this time, C<&checkauth> returns).
533 The login page is provided using a HTML::Template, which is set in the
534 systempreferences table or at the top of this file. The variable C<$type>
535 selects which template to use, either the opac or the intranet
536 authentification template.
538 C<&checkauth> returns a user ID, a cookie, and a session ID. The
539 cookie should be sent back to the browser; it verifies that the user
548 # If Version syspref is unavailable, it means Koha is beeing installed,
549 # and so we must redirect to OPAC maintenance page or to the WebInstaller
550 # also, if OpacMaintenance is ON, OPAC should redirect to maintenance
551 if (C4::Context->preference('OpacMaintenance') && $type eq 'opac') {
552 warn "OPAC Install required, redirecting to maintenance";
553 print $query->redirect("/cgi-bin/koha/maintenance.pl");
556 unless ( $version = C4::Context->preference('Version') ) { # assignment, not comparison
557 if ( $type ne 'opac' ) {
558 warn "Install required, redirecting to Installer";
559 print $query->redirect("/cgi-bin/koha/installer/install.pl");
561 warn "OPAC Install required, redirecting to maintenance";
562 print $query->redirect("/cgi-bin/koha/maintenance.pl");
567 # check that database and koha version are the same
568 # there is no DB version, it's a fresh install,
569 # go to web installer
570 # there is a DB version, compare it to the code version
571 my $kohaversion=C4::Context::KOHAVERSION;
572 # remove the 3 last . to have a Perl number
573 $kohaversion =~ s/(.*\..*)\.(.*)\.(.*)/$1$2$3/;
574 $debug and print STDERR "kohaversion : $kohaversion\n";
575 if ($version < $kohaversion){
576 my $warning = "Database update needed, redirecting to %s. Database is $version and Koha is $kohaversion";
577 if ($type ne 'opac'){
578 warn sprintf($warning, 'Installer');
579 print $query->redirect("/cgi-bin/koha/installer/install.pl?step=3");
581 warn sprintf("OPAC: " . $warning, 'maintenance');
582 print $query->redirect("/cgi-bin/koha/maintenance.pl");
590 open my $fh, '>>', "/tmp/sessionlog" or warn "ERROR: Cannot append to /tmp/sessionlog";
591 printf $fh join("\n",@_);
595 sub _timeout_syspref {
596 my $timeout = C4::Context->preference('timeout') || 600;
597 # value in days, convert in seconds
598 if ($timeout =~ /(\d+)[dD]/) {
599 $timeout = $1 * 86400;
606 $debug and warn "Checking Auth";
607 # $authnotrequired will be set for scripts which will run without authentication
608 my $authnotrequired = shift;
609 my $flagsrequired = shift;
612 $type = 'opac' unless $type;
614 my $dbh = C4::Context->dbh;
615 my $timeout = _timeout_syspref();
617 _version_check($type,$query);
621 my ( $userid, $cookie, $sessionID, $flags, $barshelves, $pubshelves );
622 my $logout = $query->param('logout.x');
624 # This parameter is the name of the CAS server we want to authenticate against,
625 # when using authentication against multiple CAS servers, as configured in Auth_cas_servers.yaml
626 my $casparam = $query->param('cas');
627 my $q_userid = $query->param('userid') // '';
629 if ( $userid = $ENV{'REMOTE_USER'} ) {
630 # Using Basic Authentication, no cookies required
631 $cookie = $query->cookie(
632 -name => 'CGISESSID',
640 # we dont want to set a session because we are being called by a persona callback
642 elsif ( $sessionID = $query->cookie("CGISESSID") )
643 { # assignment, not comparison
644 my $session = get_session($sessionID);
645 C4::Context->_new_userenv($sessionID);
646 my ($ip, $lasttime, $sessiontype);
649 $s_userid = $session->param('id') // '';
650 C4::Context::set_userenv(
651 $session->param('number'), $s_userid,
652 $session->param('cardnumber'), $session->param('firstname'),
653 $session->param('surname'), $session->param('branch'),
654 $session->param('branchname'), $session->param('flags'),
655 $session->param('emailaddress'), $session->param('branchprinter'),
656 $session->param('persona')
658 C4::Context::set_shelves_userenv('bar',$session->param('barshelves'));
659 C4::Context::set_shelves_userenv('pub',$session->param('pubshelves'));
660 C4::Context::set_shelves_userenv('tot',$session->param('totshelves'));
661 $debug and printf STDERR "AUTH_SESSION: (%s)\t%s %s - %s\n", map {$session->param($_)} qw(cardnumber firstname surname branch) ;
662 $ip = $session->param('ip');
663 $lasttime = $session->param('lasttime');
665 $sessiontype = $session->param('sessiontype') || '';
667 if ( ( $query->param('koha_login_context') && ($q_userid ne $s_userid) )
668 || ( $cas && $query->param('ticket') ) ) {
669 #if a user enters an id ne to the id in the current session, we need to log them in...
670 #first we need to clear the anonymous session...
671 $debug and warn "query id = $q_userid but session id = $s_userid";
674 C4::Context->_unset_userenv($sessionID);
679 # voluntary logout the user
682 C4::Context->_unset_userenv($sessionID);
683 #_session_log(sprintf "%20s from %16s logged out at %30s (manually).\n", $userid,$ip,(strftime "%c",localtime));
687 if ($cas and $caslogout) {
691 elsif ( !$lasttime || ($lasttime < time() - $timeout) ) {
693 $info{'timed_out'} = 1;
694 $session->delete() if $session;
695 C4::Context->_unset_userenv($sessionID);
696 #_session_log(sprintf "%20s from %16s logged out at %30s (inactivity).\n", $userid,$ip,(strftime "%c",localtime));
700 elsif ( $ip ne $ENV{'REMOTE_ADDR'} ) {
701 # Different ip than originally logged in from
702 $info{'oldip'} = $ip;
703 $info{'newip'} = $ENV{'REMOTE_ADDR'};
704 $info{'different_ip'} = 1;
706 C4::Context->_unset_userenv($sessionID);
707 #_session_log(sprintf "%20s from %16s logged out at %30s (ip changed to %16s).\n", $userid,$ip,(strftime "%c",localtime), $info{'newip'});
712 $cookie = $query->cookie(
713 -name => 'CGISESSID',
714 -value => $session->id,
717 $session->param( 'lasttime', time() );
718 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...
719 $flags = haspermission($userid, $flagsrequired);
723 $info{'nopermission'} = 1;
728 unless ($userid || $sessionID) {
730 #we initiate a session prior to checking for a username to allow for anonymous sessions...
731 my $session = get_session("") or die "Auth ERROR: Cannot get_session()";
732 my $sessionID = $session->id;
733 C4::Context->_new_userenv($sessionID);
734 $cookie = $query->cookie(
735 -name => 'CGISESSID',
736 -value => $session->id,
740 my $pki_field = C4::Context->preference('AllowPKIAuth');
741 if (! defined($pki_field) ) {
742 print STDERR "ERROR: Missing system preference AllowPKIAuth.\n";
745 if ( ( $cas && $query->param('ticket') )
747 || $pki_field ne 'None'
750 my $password = $query->param('password');
752 my ( $return, $cardnumber );
753 if ( $cas && $query->param('ticket') ) {
755 ( $return, $cardnumber, $retuserid ) =
756 checkpw( $dbh, $userid, $password, $query );
757 $userid = $retuserid;
758 $info{'invalidCasLogin'} = 1 unless ($return);
762 my $value = $persona;
764 # If we're looking up the email, there's a chance that the person
765 # doesn't have a userid. So if there is none, we pass along the
766 # borrower number, and the bits of code that need to know the user
767 # ID will have to be smart enough to handle that.
769 my @users_info = C4::Members::GetBorrowersWithEmail($value);
772 # First the userid, then the borrowernum
773 $value = $users_info[0][1] || $users_info[0][0];
778 $return = $value ? 1 : 0;
783 ( $pki_field eq 'Common Name' && $ENV{'SSL_CLIENT_S_DN_CN'} )
784 || ( $pki_field eq 'emailAddress'
785 && $ENV{'SSL_CLIENT_S_DN_Email'} )
789 if ( $pki_field eq 'Common Name' ) {
790 $value = $ENV{'SSL_CLIENT_S_DN_CN'};
792 elsif ( $pki_field eq 'emailAddress' ) {
793 $value = $ENV{'SSL_CLIENT_S_DN_Email'};
795 # If we're looking up the email, there's a chance that the person
796 # doesn't have a userid. So if there is none, we pass along the
797 # borrower number, and the bits of code that need to know the user
798 # ID will have to be smart enough to handle that.
800 my @users_info = C4::Members::GetBorrowersWithEmail($value);
803 # First the userid, then the borrowernum
804 $value = $users_info[0][1] || $users_info[0][0];
811 $return = $value ? 1 : 0;
817 ( $return, $cardnumber, $retuserid ) =
818 checkpw( $dbh, $userid, $password, $query );
819 $userid = $retuserid if ( $retuserid );
822 #_session_log(sprintf "%20s from %16s logged in at %30s.\n", $userid,$ENV{'REMOTE_ADDR'},(strftime '%c', localtime));
823 if ( $flags = haspermission( $userid, $flagsrequired ) ) {
827 $info{'nopermission'} = 1;
828 C4::Context->_unset_userenv($sessionID);
830 my ($borrowernumber, $firstname, $surname, $userflags,
831 $branchcode, $branchname, $branchprinter, $emailaddress);
833 if ( $return == 1 ) {
835 SELECT borrowernumber, firstname, surname, flags, borrowers.branchcode,
836 branches.branchname as branchname,
837 branches.branchprinter as branchprinter,
840 LEFT JOIN branches on borrowers.branchcode=branches.branchcode
842 my $sth = $dbh->prepare("$select where userid=?");
843 $sth->execute($userid);
844 unless ($sth->rows) {
845 $debug and print STDERR "AUTH_1: no rows for userid='$userid'\n";
846 $sth = $dbh->prepare("$select where cardnumber=?");
847 $sth->execute($cardnumber);
849 unless ($sth->rows) {
850 $debug and print STDERR "AUTH_2a: no rows for cardnumber='$cardnumber'\n";
851 $sth->execute($userid);
852 unless ($sth->rows) {
853 $debug and print STDERR "AUTH_2b: no rows for userid='$userid' AS cardnumber\n";
858 ($borrowernumber, $firstname, $surname, $userflags,
859 $branchcode, $branchname, $branchprinter, $emailaddress) = $sth->fetchrow;
860 $debug and print STDERR "AUTH_3 results: " .
861 "$cardnumber,$borrowernumber,$userid,$firstname,$surname,$userflags,$branchcode,$emailaddress\n";
863 print STDERR "AUTH_3: no results for userid='$userid', cardnumber='$cardnumber'.\n";
866 # launch a sequence to check if we have a ip for the branch, i
867 # if we have one we replace the branchcode of the userenv by the branch bound in the ip.
869 my $ip = $ENV{'REMOTE_ADDR'};
870 # if they specify at login, use that
871 if ($query->param('branch')) {
872 $branchcode = $query->param('branch');
873 $branchname = GetBranchName($branchcode);
875 my $branches = GetBranches();
876 if (C4::Context->boolean_preference('IndependantBranches') && C4::Context->boolean_preference('Autolocation')){
877 # we have to check they are coming from the right ip range
878 my $domain = $branches->{$branchcode}->{'branchip'};
879 if ($ip !~ /^$domain/){
881 $info{'wrongip'} = 1;
886 foreach my $br ( keys %$branches ) {
887 # now we work with the treatment of ip
888 my $domain = $branches->{$br}->{'branchip'};
889 if ( $domain && $ip =~ /^$domain/ ) {
890 $branchcode = $branches->{$br}->{'branchcode'};
892 # new op dev : add the branchprinter and branchname in the cookie
893 $branchprinter = $branches->{$br}->{'branchprinter'};
894 $branchname = $branches->{$br}->{'branchname'};
897 $session->param('number',$borrowernumber);
898 $session->param('id',$userid);
899 $session->param('cardnumber',$cardnumber);
900 $session->param('firstname',$firstname);
901 $session->param('surname',$surname);
902 $session->param('branch',$branchcode);
903 $session->param('branchname',$branchname);
904 $session->param('flags',$userflags);
905 $session->param('emailaddress',$emailaddress);
906 $session->param('ip',$session->remote_addr());
907 $session->param('lasttime',time());
908 $debug and printf STDERR "AUTH_4: (%s)\t%s %s - %s\n", map {$session->param($_)} qw(cardnumber firstname surname branch) ;
910 elsif ( $return == 2 ) {
911 #We suppose the user is the superlibrarian
913 $session->param('number',0);
914 $session->param('id',C4::Context->config('user'));
915 $session->param('cardnumber',C4::Context->config('user'));
916 $session->param('firstname',C4::Context->config('user'));
917 $session->param('surname',C4::Context->config('user'));
918 $session->param('branch','NO_LIBRARY_SET');
919 $session->param('branchname','NO_LIBRARY_SET');
920 $session->param('flags',1);
921 $session->param('emailaddress', C4::Context->preference('KohaAdminEmailAddress'));
922 $session->param('ip',$session->remote_addr());
923 $session->param('lasttime',time());
926 $session->param('persona',1);
928 C4::Context::set_userenv(
929 $session->param('number'), $session->param('id'),
930 $session->param('cardnumber'), $session->param('firstname'),
931 $session->param('surname'), $session->param('branch'),
932 $session->param('branchname'), $session->param('flags'),
933 $session->param('emailaddress'), $session->param('branchprinter'),
934 $session->param('persona')
940 $info{'invalid_username_or_password'} = 1;
941 C4::Context->_unset_userenv($sessionID);
944 } # END if ( $userid = $query->param('userid') )
945 elsif ($type eq "opac") {
946 # if we are here this is an anonymous session; add public lists to it and a few other items...
947 # anonymous sessions are created only for the OPAC
948 $debug and warn "Initiating an anonymous session...";
950 # setting a couple of other session vars...
951 $session->param('ip',$session->remote_addr());
952 $session->param('lasttime',time());
953 $session->param('sessiontype','anon');
955 } # END unless ($userid)
957 # finished authentification, now respond
958 if ( $loggedin || $authnotrequired )
962 $cookie = $query->cookie(
963 -name => 'CGISESSID',
968 return ( $userid, $cookie, $sessionID, $flags );
973 # AUTH rejected, show the login/password template, after checking the DB.
977 # get the inputs from the incoming query
979 foreach my $name ( param $query) {
980 (next) if ( $name eq 'userid' || $name eq 'password' || $name eq 'ticket' );
981 my $value = $query->param($name);
982 push @inputs, { name => $name, value => $value };
985 my $LibraryNameTitle = C4::Context->preference("LibraryName");
986 $LibraryNameTitle =~ s/<(?:\/?)(?:br|p)\s*(?:\/?)>/ /sgi;
987 $LibraryNameTitle =~ s/<(?:[^<>'"]|'(?:[^']*)'|"(?:[^"]*)")*>//sg;
989 my $template_name = ( $type eq 'opac' ) ? 'opac-auth.tmpl' : 'auth.tmpl';
990 my $template = C4::Templates::gettemplate($template_name, $type, $query );
992 branchloop => GetBranchesLoop(),
993 opaccolorstylesheet => C4::Context->preference("opaccolorstylesheet"),
994 opaclayoutstylesheet => C4::Context->preference("opaclayoutstylesheet"),
997 casAuthentication => C4::Context->preference("casAuthentication"),
998 suggestion => C4::Context->preference("suggestion"),
999 virtualshelves => C4::Context->preference("virtualshelves"),
1000 LibraryName => "" . C4::Context->preference("LibraryName"),
1001 LibraryNameTitle => "" . $LibraryNameTitle,
1002 opacuserlogin => C4::Context->preference("opacuserlogin"),
1003 OpacNav => C4::Context->preference("OpacNav"),
1004 OpacNavRight => C4::Context->preference("OpacNavRight"),
1005 OpacNavBottom => C4::Context->preference("OpacNavBottom"),
1006 opaccredits => C4::Context->preference("opaccredits"),
1007 OpacFavicon => C4::Context->preference("OpacFavicon"),
1008 opacreadinghistory => C4::Context->preference("opacreadinghistory"),
1009 opacsmallimage => C4::Context->preference("opacsmallimage"),
1010 opaclanguagesdisplay => C4::Context->preference("opaclanguagesdisplay"),
1011 opacuserjs => C4::Context->preference("opacuserjs"),
1012 opacbookbag => "" . C4::Context->preference("opacbookbag"),
1013 OpacCloud => C4::Context->preference("OpacCloud"),
1014 OpacTopissue => C4::Context->preference("OpacTopissue"),
1015 OpacAuthorities => C4::Context->preference("OpacAuthorities"),
1016 OpacBrowser => C4::Context->preference("OpacBrowser"),
1017 opacheader => C4::Context->preference("opacheader"),
1018 TagsEnabled => C4::Context->preference("TagsEnabled"),
1019 OPACUserCSS => C4::Context->preference("OPACUserCSS"),
1020 intranetcolorstylesheet => C4::Context->preference("intranetcolorstylesheet"),
1021 intranetstylesheet => C4::Context->preference("intranetstylesheet"),
1022 intranetbookbag => C4::Context->preference("intranetbookbag"),
1023 IntranetNav => C4::Context->preference("IntranetNav"),
1024 IntranetFavicon => C4::Context->preference("IntranetFavicon"),
1025 intranetuserjs => C4::Context->preference("intranetuserjs"),
1026 IndependantBranches=> C4::Context->preference("IndependantBranches"),
1027 AutoLocation => C4::Context->preference("AutoLocation"),
1028 wrongip => $info{'wrongip'},
1029 PatronSelfRegistration => C4::Context->preference("PatronSelfRegistration"),
1030 PatronSelfRegistrationDefaultCategory => C4::Context->preference("PatronSelfRegistrationDefaultCategory"),
1031 persona => C4::Context->preference("Persona"),
1032 opac_css_override => $ENV{'OPAC_CSS_OVERRIDE'},
1035 $template->param( OpacPublic => C4::Context->preference("OpacPublic"));
1036 $template->param( loginprompt => 1 ) unless $info{'nopermission'};
1038 if($type eq 'opac'){
1039 my ($total, $pubshelves) = C4::VirtualShelves::GetSomeShelfNames(undef, 'MASTHEAD');
1041 pubshelves => $total->{pubtotal},
1042 pubshelvesloop => $pubshelves,
1048 # Is authentication against multiple CAS servers enabled?
1049 if (C4::Auth_with_cas::multipleAuth && !$casparam) {
1050 my $casservers = C4::Auth_with_cas::getMultipleAuth();
1052 foreach my $key (keys %$casservers) {
1053 push @tmplservers, {name => $key, value => login_cas_url($query, $key) . "?cas=$key" };
1056 casServersLoop => \@tmplservers
1060 casServerUrl => login_cas_url($query),
1065 invalidCasLogin => $info{'invalidCasLogin'}
1069 my $self_url = $query->url( -absolute => 1 );
1072 LibraryName => C4::Context->preference("LibraryName"),
1074 $template->param( %info );
1075 # $cookie = $query->cookie(CGISESSID => $session->id
1077 print $query->header(
1078 -type => 'text/html',
1079 -charset => 'utf-8',
1086 =head2 check_api_auth
1088 ($status, $cookie, $sessionId) = check_api_auth($query, $userflags);
1090 Given a CGI query containing the parameters 'userid' and 'password' and/or a session
1091 cookie, determine if the user has the privileges specified by C<$userflags>.
1093 C<check_api_auth> is is meant for authenticating users of web services, and
1094 consequently will always return and will not attempt to redirect the user
1097 If a valid session cookie is already present, check_api_auth will return a status
1098 of "ok", the cookie, and the Koha session ID.
1100 If no session cookie is present, check_api_auth will check the 'userid' and 'password
1101 parameters and create a session cookie and Koha session if the supplied credentials
1104 Possible return values in C<$status> are:
1108 =item "ok" -- user authenticated; C<$cookie> and C<$sessionid> have valid values.
1110 =item "failed" -- credentials are not correct; C<$cookie> and C<$sessionid> are undef
1112 =item "maintenance" -- DB is in maintenance mode; no login possible at the moment
1114 =item "expired -- session cookie has expired; API user should resubmit userid and password
1120 sub check_api_auth {
1122 my $flagsrequired = shift;
1124 my $dbh = C4::Context->dbh;
1125 my $timeout = _timeout_syspref();
1127 unless (C4::Context->preference('Version')) {
1128 # database has not been installed yet
1129 return ("maintenance", undef, undef);
1131 my $kohaversion=C4::Context::KOHAVERSION;
1132 $kohaversion =~ s/(.*\..*)\.(.*)\.(.*)/$1$2$3/;
1133 if (C4::Context->preference('Version') < $kohaversion) {
1134 # database in need of version update; assume that
1135 # no API should be called while databsae is in
1137 return ("maintenance", undef, undef);
1140 # FIXME -- most of what follows is a copy-and-paste
1141 # of code from checkauth. There is an obvious need
1142 # for refactoring to separate the various parts of
1143 # the authentication code, but as of 2007-11-19 this
1144 # is deferred so as to not introduce bugs into the
1145 # regular authentication code for Koha 3.0.
1147 # see if we have a valid session cookie already
1148 # however, if a userid parameter is present (i.e., from
1149 # a form submission, assume that any current cookie
1151 my $sessionID = undef;
1152 unless ($query->param('userid')) {
1153 $sessionID = $query->cookie("CGISESSID");
1155 if ($sessionID && not ($cas && $query->param('PT')) ) {
1156 my $session = get_session($sessionID);
1157 C4::Context->_new_userenv($sessionID);
1159 C4::Context::set_userenv(
1160 $session->param('number'), $session->param('id'),
1161 $session->param('cardnumber'), $session->param('firstname'),
1162 $session->param('surname'), $session->param('branch'),
1163 $session->param('branchname'), $session->param('flags'),
1164 $session->param('emailaddress'), $session->param('branchprinter')
1167 my $ip = $session->param('ip');
1168 my $lasttime = $session->param('lasttime');
1169 my $userid = $session->param('id');
1170 if ( $lasttime < time() - $timeout ) {
1173 C4::Context->_unset_userenv($sessionID);
1176 return ("expired", undef, undef);
1177 } elsif ( $ip ne $ENV{'REMOTE_ADDR'} ) {
1178 # IP address changed
1180 C4::Context->_unset_userenv($sessionID);
1183 return ("expired", undef, undef);
1185 my $cookie = $query->cookie(
1186 -name => 'CGISESSID',
1187 -value => $session->id,
1190 $session->param('lasttime',time());
1191 my $flags = haspermission($userid, $flagsrequired);
1193 return ("ok", $cookie, $sessionID);
1196 C4::Context->_unset_userenv($sessionID);
1199 return ("failed", undef, undef);
1203 return ("expired", undef, undef);
1207 my $userid = $query->param('userid');
1208 my $password = $query->param('password');
1209 my ($return, $cardnumber);
1212 if ($cas && $query->param('PT')) {
1214 $debug and print STDERR "## check_api_auth - checking CAS\n";
1215 # In case of a CAS authentication, we use the ticket instead of the password
1216 my $PT = $query->param('PT');
1217 ($return,$cardnumber,$userid) = check_api_auth_cas($dbh, $PT, $query); # EXTERNAL AUTH
1219 # User / password auth
1220 unless ($userid and $password) {
1221 # caller did something wrong, fail the authenticateion
1222 return ("failed", undef, undef);
1224 ( $return, $cardnumber ) = checkpw( $dbh, $userid, $password, $query );
1227 if ($return and haspermission( $userid, $flagsrequired)) {
1228 my $session = get_session("");
1229 return ("failed", undef, undef) unless $session;
1231 my $sessionID = $session->id;
1232 C4::Context->_new_userenv($sessionID);
1233 my $cookie = $query->cookie(
1234 -name => 'CGISESSID',
1235 -value => $sessionID,
1238 if ( $return == 1 ) {
1240 $borrowernumber, $firstname, $surname,
1241 $userflags, $branchcode, $branchname,
1242 $branchprinter, $emailaddress
1246 "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=?"
1248 $sth->execute($userid);
1250 $borrowernumber, $firstname, $surname,
1251 $userflags, $branchcode, $branchname,
1252 $branchprinter, $emailaddress
1253 ) = $sth->fetchrow if ( $sth->rows );
1255 unless ($sth->rows ) {
1256 my $sth = $dbh->prepare(
1257 "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=?"
1259 $sth->execute($cardnumber);
1261 $borrowernumber, $firstname, $surname,
1262 $userflags, $branchcode, $branchname,
1263 $branchprinter, $emailaddress
1264 ) = $sth->fetchrow if ( $sth->rows );
1266 unless ( $sth->rows ) {
1267 $sth->execute($userid);
1269 $borrowernumber, $firstname, $surname, $userflags,
1270 $branchcode, $branchname, $branchprinter, $emailaddress
1271 ) = $sth->fetchrow if ( $sth->rows );
1275 my $ip = $ENV{'REMOTE_ADDR'};
1276 # if they specify at login, use that
1277 if ($query->param('branch')) {
1278 $branchcode = $query->param('branch');
1279 $branchname = GetBranchName($branchcode);
1281 my $branches = GetBranches();
1283 foreach my $br ( keys %$branches ) {
1284 # now we work with the treatment of ip
1285 my $domain = $branches->{$br}->{'branchip'};
1286 if ( $domain && $ip =~ /^$domain/ ) {
1287 $branchcode = $branches->{$br}->{'branchcode'};
1289 # new op dev : add the branchprinter and branchname in the cookie
1290 $branchprinter = $branches->{$br}->{'branchprinter'};
1291 $branchname = $branches->{$br}->{'branchname'};
1294 $session->param('number',$borrowernumber);
1295 $session->param('id',$userid);
1296 $session->param('cardnumber',$cardnumber);
1297 $session->param('firstname',$firstname);
1298 $session->param('surname',$surname);
1299 $session->param('branch',$branchcode);
1300 $session->param('branchname',$branchname);
1301 $session->param('flags',$userflags);
1302 $session->param('emailaddress',$emailaddress);
1303 $session->param('ip',$session->remote_addr());
1304 $session->param('lasttime',time());
1305 } elsif ( $return == 2 ) {
1306 #We suppose the user is the superlibrarian
1307 $session->param('number',0);
1308 $session->param('id',C4::Context->config('user'));
1309 $session->param('cardnumber',C4::Context->config('user'));
1310 $session->param('firstname',C4::Context->config('user'));
1311 $session->param('surname',C4::Context->config('user'));
1312 $session->param('branch','NO_LIBRARY_SET');
1313 $session->param('branchname','NO_LIBRARY_SET');
1314 $session->param('flags',1);
1315 $session->param('emailaddress', C4::Context->preference('KohaAdminEmailAddress'));
1316 $session->param('ip',$session->remote_addr());
1317 $session->param('lasttime',time());
1319 C4::Context::set_userenv(
1320 $session->param('number'), $session->param('id'),
1321 $session->param('cardnumber'), $session->param('firstname'),
1322 $session->param('surname'), $session->param('branch'),
1323 $session->param('branchname'), $session->param('flags'),
1324 $session->param('emailaddress'), $session->param('branchprinter')
1326 return ("ok", $cookie, $sessionID);
1328 return ("failed", undef, undef);
1333 =head2 check_cookie_auth
1335 ($status, $sessionId) = check_api_auth($cookie, $userflags);
1337 Given a CGISESSID cookie set during a previous login to Koha, determine
1338 if the user has the privileges specified by C<$userflags>.
1340 C<check_cookie_auth> is meant for authenticating special services
1341 such as tools/upload-file.pl that are invoked by other pages that
1342 have been authenticated in the usual way.
1344 Possible return values in C<$status> are:
1348 =item "ok" -- user authenticated; C<$sessionID> have valid values.
1350 =item "failed" -- credentials are not correct; C<$sessionid> are undef
1352 =item "maintenance" -- DB is in maintenance mode; no login possible at the moment
1354 =item "expired -- session cookie has expired; API user should resubmit userid and password
1360 sub check_cookie_auth {
1362 my $flagsrequired = shift;
1364 my $dbh = C4::Context->dbh;
1365 my $timeout = _timeout_syspref();
1367 unless (C4::Context->preference('Version')) {
1368 # database has not been installed yet
1369 return ("maintenance", undef);
1371 my $kohaversion=C4::Context::KOHAVERSION;
1372 $kohaversion =~ s/(.*\..*)\.(.*)\.(.*)/$1$2$3/;
1373 if (C4::Context->preference('Version') < $kohaversion) {
1374 # database in need of version update; assume that
1375 # no API should be called while databsae is in
1377 return ("maintenance", undef);
1380 # FIXME -- most of what follows is a copy-and-paste
1381 # of code from checkauth. There is an obvious need
1382 # for refactoring to separate the various parts of
1383 # the authentication code, but as of 2007-11-23 this
1384 # is deferred so as to not introduce bugs into the
1385 # regular authentication code for Koha 3.0.
1387 # see if we have a valid session cookie already
1388 # however, if a userid parameter is present (i.e., from
1389 # a form submission, assume that any current cookie
1391 unless (defined $cookie and $cookie) {
1392 return ("failed", undef);
1394 my $sessionID = $cookie;
1395 my $session = get_session($sessionID);
1396 C4::Context->_new_userenv($sessionID);
1398 C4::Context::set_userenv(
1399 $session->param('number'), $session->param('id'),
1400 $session->param('cardnumber'), $session->param('firstname'),
1401 $session->param('surname'), $session->param('branch'),
1402 $session->param('branchname'), $session->param('flags'),
1403 $session->param('emailaddress'), $session->param('branchprinter')
1406 my $ip = $session->param('ip');
1407 my $lasttime = $session->param('lasttime');
1408 my $userid = $session->param('id');
1409 if ( $lasttime < time() - $timeout ) {
1412 C4::Context->_unset_userenv($sessionID);
1415 return ("expired", undef);
1416 } elsif ( $ip ne $ENV{'REMOTE_ADDR'} ) {
1417 # IP address changed
1419 C4::Context->_unset_userenv($sessionID);
1422 return ("expired", undef);
1424 $session->param('lasttime',time());
1425 my $flags = haspermission($userid, $flagsrequired);
1427 return ("ok", $sessionID);
1430 C4::Context->_unset_userenv($sessionID);
1433 return ("failed", undef);
1437 return ("expired", undef);
1444 my $session = get_session($sessionID);
1446 Given a session ID, retrieve the CGI::Session object used to store
1447 the session's state. The session object can be used to store
1448 data that needs to be accessed by different scripts during a
1451 If the C<$sessionID> parameter is an empty string, a new session
1457 my $sessionID = shift;
1458 my $storage_method = C4::Context->preference('SessionStorage');
1459 my $dbh = C4::Context->dbh;
1461 if ($storage_method eq 'mysql'){
1462 $session = new CGI::Session("driver:MySQL;serializer:yaml;id:md5", $sessionID, {Handle=>$dbh});
1464 elsif ($storage_method eq 'Pg') {
1465 $session = new CGI::Session("driver:PostgreSQL;serializer:yaml;id:md5", $sessionID, {Handle=>$dbh});
1467 elsif ($storage_method eq 'memcached' && C4::Context->ismemcached){
1468 $session = new CGI::Session("driver:memcached;serializer:yaml;id:md5", $sessionID, { Memcached => C4::Context->memcached } );
1471 # catch all defaults to tmp should work on all systems
1472 $session = new CGI::Session("driver:File;serializer:yaml;id:md5", $sessionID, {Directory=>'/tmp'});
1479 my ( $dbh, $userid, $password, $query ) = @_;
1481 $debug and print STDERR "## checkpw - checking LDAP\n";
1482 my ($retval,$retcard,$retuserid) = checkpw_ldap(@_); # EXTERNAL AUTH
1483 ($retval) and return ($retval,$retcard,$retuserid);
1486 if ($cas && $query && $query->param('ticket')) {
1487 $debug and print STDERR "## checkpw - checking CAS\n";
1488 # In case of a CAS authentication, we use the ticket instead of the password
1489 my $ticket = $query->param('ticket');
1490 my ($retval,$retcard,$retuserid) = checkpw_cas($dbh, $ticket, $query); # EXTERNAL AUTH
1491 ($retval) and return ($retval,$retcard,$retuserid);
1498 "select password,cardnumber,borrowernumber,userid,firstname,surname,branchcode,flags from borrowers where userid=?"
1500 $sth->execute($userid);
1502 my ( $md5password, $cardnumber, $borrowernumber, $userid, $firstname,
1503 $surname, $branchcode, $flags )
1505 if ( md5_base64($password) eq $md5password and $md5password ne "!") {
1507 C4::Context->set_userenv( "$borrowernumber", $userid, $cardnumber,
1508 $firstname, $surname, $branchcode, $flags );
1509 return 1, $cardnumber, $userid;
1514 "select password,cardnumber,borrowernumber,userid, firstname,surname,branchcode,flags from borrowers where cardnumber=?"
1516 $sth->execute($userid);
1518 my ( $md5password, $cardnumber, $borrowernumber, $userid, $firstname,
1519 $surname, $branchcode, $flags )
1521 if ( md5_base64($password) eq $md5password ) {
1523 C4::Context->set_userenv( $borrowernumber, $userid, $cardnumber,
1524 $firstname, $surname, $branchcode, $flags );
1525 return 1, $cardnumber, $userid;
1528 if ( $userid && $userid eq C4::Context->config('user')
1529 && "$password" eq C4::Context->config('pass') )
1532 # Koha superuser account
1533 # C4::Context->set_userenv(0,0,C4::Context->config('user'),C4::Context->config('user'),C4::Context->config('user'),"",1);
1536 if ( $userid && $userid eq 'demo'
1537 && "$password" eq 'demo'
1538 && C4::Context->config('demo') )
1541 # DEMO => the demo user is allowed to do everything (if demo set to 1 in koha.conf
1542 # some features won't be effective : modify systempref, modify MARC structure,
1550 my $authflags = getuserflags($flags, $userid, [$dbh]);
1552 Translates integer flags into permissions strings hash.
1554 C<$flags> is the integer userflags value ( borrowers.userflags )
1555 C<$userid> is the members.userid, used for building subpermissions
1556 C<$authflags> is a hashref of permissions
1563 my $dbh = @_ ? shift : C4::Context->dbh;
1566 # I don't want to do this, but if someone logs in as the database
1567 # user, it would be preferable not to spam them to death with
1568 # numeric warnings. So, we make $flags numeric.
1569 no warnings 'numeric';
1572 my $sth = $dbh->prepare("SELECT bit, flag, defaulton FROM userflags");
1575 while ( my ( $bit, $flag, $defaulton ) = $sth->fetchrow ) {
1576 if ( ( $flags & ( 2**$bit ) ) || $defaulton ) {
1577 $userflags->{$flag} = 1;
1580 $userflags->{$flag} = 0;
1584 # get subpermissions and merge with top-level permissions
1585 my $user_subperms = get_user_subpermissions($userid);
1586 foreach my $module (keys %$user_subperms) {
1587 next if $userflags->{$module} == 1; # user already has permission for everything in this module
1588 $userflags->{$module} = $user_subperms->{$module};
1594 =head2 get_user_subpermissions
1596 $user_perm_hashref = get_user_subpermissions($userid);
1598 Given the userid (note, not the borrowernumber) of a staff user,
1599 return a hashref of hashrefs of the specific subpermissions
1600 accorded to the user. An example return is
1604 export_catalog => 1,
1605 import_patrons => 1,
1609 The top-level hash-key is a module or function code from
1610 userflags.flag, while the second-level key is a code
1613 The results of this function do not give a complete picture
1614 of the functions that a staff user can access; it is also
1615 necessary to check borrowers.flags.
1619 sub get_user_subpermissions {
1622 my $dbh = C4::Context->dbh;
1623 my $sth = $dbh->prepare("SELECT flag, user_permissions.code
1624 FROM user_permissions
1625 JOIN permissions USING (module_bit, code)
1626 JOIN userflags ON (module_bit = bit)
1627 JOIN borrowers USING (borrowernumber)
1629 $sth->execute($userid);
1631 my $user_perms = {};
1632 while (my $perm = $sth->fetchrow_hashref) {
1633 $user_perms->{$perm->{'flag'}}->{$perm->{'code'}} = 1;
1638 =head2 get_all_subpermissions
1640 my $perm_hashref = get_all_subpermissions();
1642 Returns a hashref of hashrefs defining all specific
1643 permissions currently defined. The return value
1644 has the same structure as that of C<get_user_subpermissions>,
1645 except that the innermost hash value is the description
1646 of the subpermission.
1650 sub get_all_subpermissions {
1651 my $dbh = C4::Context->dbh;
1652 my $sth = $dbh->prepare("SELECT flag, code, description
1654 JOIN userflags ON (module_bit = bit)");
1658 while (my $perm = $sth->fetchrow_hashref) {
1659 $all_perms->{$perm->{'flag'}}->{$perm->{'code'}} = $perm->{'description'};
1664 =head2 haspermission
1666 $flags = ($userid, $flagsrequired);
1668 C<$userid> the userid of the member
1669 C<$flags> is a hashref of required flags like C<$borrower-<{authflags}>
1671 Returns member's flags or 0 if a permission is not met.
1676 my ($userid, $flagsrequired) = @_;
1677 my $sth = C4::Context->dbh->prepare("SELECT flags FROM borrowers WHERE userid=?");
1678 $sth->execute($userid);
1679 my $flags = getuserflags($sth->fetchrow(), $userid);
1680 if ( $userid eq C4::Context->config('user') ) {
1681 # Super User Account from /etc/koha.conf
1682 $flags->{'superlibrarian'} = 1;
1684 elsif ( $userid eq 'demo' && C4::Context->config('demo') ) {
1685 # Demo user that can do "anything" (demo=1 in /etc/koha.conf)
1686 $flags->{'superlibrarian'} = 1;
1689 return $flags if $flags->{superlibrarian};
1691 foreach my $module ( keys %$flagsrequired ) {
1692 my $subperm = $flagsrequired->{$module};
1693 if ($subperm eq '*') {
1694 return 0 unless ( $flags->{$module} == 1 or ref($flags->{$module}) );
1696 return 0 unless ( $flags->{$module} == 1 or
1697 ( ref($flags->{$module}) and
1698 exists $flags->{$module}->{$subperm} and
1699 $flags->{$module}->{$subperm} == 1
1705 #FIXME - This fcn should return the failed permission so a suitable error msg can be delivered.
1709 sub getborrowernumber {
1711 my $userenv = C4::Context->userenv;
1712 if ( defined( $userenv ) && ref( $userenv ) eq 'HASH' && $userenv->{number} ) {
1713 return $userenv->{number};
1715 my $dbh = C4::Context->dbh;
1716 for my $field ( 'userid', 'cardnumber' ) {
1718 $dbh->prepare("select borrowernumber from borrowers where $field=?");
1719 $sth->execute($userid);
1721 my ($bnumber) = $sth->fetchrow;
1728 sub ParseSearchHistoryCookie {
1730 my $search_cookie = $input->cookie('KohaOpacRecentSearches');
1731 return () unless $search_cookie;
1732 my $obj = eval { decode_json(uri_unescape($search_cookie)) };
1733 return () unless defined $obj;
1734 return () unless ref $obj eq 'ARRAY';
1738 END { } # module clean-up code here (global destructor)