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 => 1,
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 );
140 if ( $in->{'template_name'} !~m/maintenance/ ) {
141 ( $user, $cookie, $sessionID, $flags ) = checkauth(
143 $in->{'authnotrequired'},
144 $in->{'flagsrequired'},
152 # It's possible for $user to be the borrowernumber if they don't have a
153 # userid defined (and are logging in through some other method, such
154 # as SSL certs against an email address)
155 $borrowernumber = getborrowernumber($user) if defined($user);
156 if (!defined($borrowernumber) && defined($user)) {
157 my $borrower = C4::Members::GetMember(borrowernumber => $user);
159 $borrowernumber = $user;
160 # A bit of a hack, but I don't know there's a nicer way
162 $user = $borrower->{firstname} . ' ' . $borrower->{surname};
167 $template->param( loggedinusername => $user );
168 $template->param( sessionID => $sessionID );
170 my ($total, $pubshelves, $barshelves) = C4::VirtualShelves::GetSomeShelfNames($borrowernumber, 'MASTHEAD');
172 pubshelves => $total->{pubtotal},
173 pubshelvesloop => $pubshelves,
174 barshelves => $total->{bartotal},
175 barshelvesloop => $barshelves,
178 my ( $borr ) = C4::Members::GetMemberDetails( $borrowernumber );
181 $template->param( "USER_INFO" => \@bordat );
183 my $all_perms = get_all_subpermissions();
185 my @flagroots = qw(circulate catalogue parameters borrowers permissions reserveforothers borrow
186 editcatalogue updatecharges management tools editauthorities serials reports acquisition);
187 # We are going to use the $flags returned by checkauth
188 # to create the template's parameters that will indicate
189 # which menus the user can access.
190 if ( $flags && $flags->{superlibrarian}==1 ) {
191 $template->param( CAN_user_circulate => 1 );
192 $template->param( CAN_user_catalogue => 1 );
193 $template->param( CAN_user_parameters => 1 );
194 $template->param( CAN_user_borrowers => 1 );
195 $template->param( CAN_user_permissions => 1 );
196 $template->param( CAN_user_reserveforothers => 1 );
197 $template->param( CAN_user_borrow => 1 );
198 $template->param( CAN_user_editcatalogue => 1 );
199 $template->param( CAN_user_updatecharges => 1 );
200 $template->param( CAN_user_acquisition => 1 );
201 $template->param( CAN_user_management => 1 );
202 $template->param( CAN_user_tools => 1 );
203 $template->param( CAN_user_editauthorities => 1 );
204 $template->param( CAN_user_serials => 1 );
205 $template->param( CAN_user_reports => 1 );
206 $template->param( CAN_user_staffaccess => 1 );
207 $template->param( CAN_user_plugins => 1 );
208 foreach my $module (keys %$all_perms) {
209 foreach my $subperm (keys %{ $all_perms->{$module} }) {
210 $template->param( "CAN_user_${module}_${subperm}" => 1 );
216 foreach my $module (keys %$all_perms) {
217 if ( $flags->{$module} == 1) {
218 foreach my $subperm (keys %{ $all_perms->{$module} }) {
219 $template->param( "CAN_user_${module}_${subperm}" => 1 );
221 } elsif ( ref($flags->{$module}) ) {
222 foreach my $subperm (keys %{ $flags->{$module} } ) {
223 $template->param( "CAN_user_${module}_${subperm}" => 1 );
230 foreach my $module (keys %$flags) {
231 if ( $flags->{$module} == 1 or ref($flags->{$module}) ) {
232 $template->param( "CAN_user_$module" => 1 );
233 if ($module eq "parameters") {
234 $template->param( CAN_user_management => 1 );
239 # Logged-in opac search history
240 # If the requested template is an opac one and opac search history is enabled
241 if ($in->{type} eq 'opac' && C4::Context->preference('EnableOpacSearchHistory')) {
242 my $dbh = C4::Context->dbh;
243 my $query = "SELECT COUNT(*) FROM search_history WHERE userid=?";
244 my $sth = $dbh->prepare($query);
245 $sth->execute($borrowernumber);
247 # If at least one search has already been performed
248 if ($sth->fetchrow_array > 0) {
249 # We show the link in opac
250 $template->param(ShowOpacRecentSearchLink => 1);
253 # And if there's a cookie with searches performed when the user was not logged in,
254 # we add them to the logged-in search history
255 my @recentSearches = ParseSearchHistoryCookie($in->{'query'});
256 if (@recentSearches) {
257 my $sth = $dbh->prepare($SEARCH_HISTORY_INSERT_SQL);
258 $sth->execute( $borrowernumber,
259 $in->{'query'}->cookie("CGISESSID"),
264 ) foreach @recentSearches;
266 # And then, delete the cookie's content
267 my $newsearchcookie = $in->{'query'}->cookie(
268 -name => 'KohaOpacRecentSearches',
269 -value => encode_json([]),
273 $cookie = [$cookie, $newsearchcookie];
277 else { # if this is an anonymous session, setup to display public lists...
279 $template->param( sessionID => $sessionID );
281 my ($total, $pubshelves) = C4::VirtualShelves::GetSomeShelfNames(undef, 'MASTHEAD');
283 pubshelves => $total->{pubtotal},
284 pubshelvesloop => $pubshelves,
287 # Anonymous opac search history
288 # If opac search history is enabled and at least one search has already been performed
289 if (C4::Context->preference('EnableOpacSearchHistory')) {
290 my @recentSearches = ParseSearchHistoryCookie($in->{'query'});
291 if (@recentSearches) {
292 $template->param(ShowOpacRecentSearchLink => 1);
296 if(C4::Context->preference('dateformat')){
297 $template->param(dateformat => C4::Context->preference('dateformat'))
300 # these template parameters are set the same regardless of $in->{'type'}
302 "BiblioDefaultView".C4::Context->preference("BiblioDefaultView") => 1,
303 EnhancedMessagingPreferences => C4::Context->preference('EnhancedMessagingPreferences'),
304 GoogleJackets => C4::Context->preference("GoogleJackets"),
305 OpenLibraryCovers => C4::Context->preference("OpenLibraryCovers"),
306 KohaAdminEmailAddress => "" . C4::Context->preference("KohaAdminEmailAddress"),
307 LoginBranchcode => (C4::Context->userenv?C4::Context->userenv->{"branch"}:undef),
308 LoginFirstname => (C4::Context->userenv?C4::Context->userenv->{"firstname"}:"Bel"),
309 LoginSurname => C4::Context->userenv?C4::Context->userenv->{"surname"}:"Inconnu",
310 emailaddress => C4::Context->userenv?C4::Context->userenv->{"emailaddress"}:undef,
311 loggedinpersona => C4::Context->userenv?C4::Context->userenv->{"persona"}:undef,
312 TagsEnabled => C4::Context->preference("TagsEnabled"),
313 hide_marc => C4::Context->preference("hide_marc"),
314 item_level_itypes => C4::Context->preference('item-level_itypes'),
315 patronimages => C4::Context->preference("patronimages"),
316 singleBranchMode => C4::Context->preference("singleBranchMode"),
317 XSLTDetailsDisplay => C4::Context->preference("XSLTDetailsDisplay"),
318 XSLTResultsDisplay => C4::Context->preference("XSLTResultsDisplay"),
319 using_https => $in->{'query'}->https() ? 1 : 0,
320 noItemTypeImages => C4::Context->preference("noItemTypeImages"),
321 marcflavour => C4::Context->preference("marcflavour"),
322 persona => C4::Context->preference("persona"),
324 if ( $in->{'type'} eq "intranet" ) {
326 AmazonCoverImages => C4::Context->preference("AmazonCoverImages"),
327 AutoLocation => C4::Context->preference("AutoLocation"),
328 "BiblioDefaultView".C4::Context->preference("IntranetBiblioDefaultView") => 1,
329 CalendarFirstDayOfWeek => (C4::Context->preference("CalendarFirstDayOfWeek") eq "Sunday")?0:1,
330 CircAutocompl => C4::Context->preference("CircAutocompl"),
331 FRBRizeEditions => C4::Context->preference("FRBRizeEditions"),
332 IndependantBranches => C4::Context->preference("IndependantBranches"),
333 IntranetNav => C4::Context->preference("IntranetNav"),
334 IntranetmainUserblock => C4::Context->preference("IntranetmainUserblock"),
335 LibraryName => C4::Context->preference("LibraryName"),
336 LoginBranchname => (C4::Context->userenv?C4::Context->userenv->{"branchname"}:undef),
337 advancedMARCEditor => C4::Context->preference("advancedMARCEditor"),
338 canreservefromotherbranches => C4::Context->preference('canreservefromotherbranches'),
339 intranetcolorstylesheet => C4::Context->preference("intranetcolorstylesheet"),
340 IntranetFavicon => C4::Context->preference("IntranetFavicon"),
341 intranetreadinghistory => C4::Context->preference("intranetreadinghistory"),
342 intranetstylesheet => C4::Context->preference("intranetstylesheet"),
343 IntranetUserCSS => C4::Context->preference("IntranetUserCSS"),
344 intranetuserjs => C4::Context->preference("intranetuserjs"),
345 intranetbookbag => C4::Context->preference("intranetbookbag"),
346 suggestion => C4::Context->preference("suggestion"),
347 virtualshelves => C4::Context->preference("virtualshelves"),
348 StaffSerialIssueDisplayCount => C4::Context->preference("StaffSerialIssueDisplayCount"),
349 EasyAnalyticalRecords => C4::Context->preference('EasyAnalyticalRecords'),
350 LocalCoverImages => C4::Context->preference('LocalCoverImages'),
351 OPACLocalCoverImages => C4::Context->preference('OPACLocalCoverImages'),
352 AllowMultipleCovers => C4::Context->preference('AllowMultipleCovers'),
353 EnableBorrowerFiles => C4::Context->preference('EnableBorrowerFiles'),
354 UseKohaPlugins => C4::Context->preference('UseKohaPlugins'),
355 UseCourseReserves => C4::Context->preference("UseCourseReserves"),
359 warn "template type should be OPAC, here it is=[" . $in->{'type'} . "]" unless ( $in->{'type'} eq 'opac' );
360 #TODO : replace LibraryName syspref with 'system name', and remove this html processing
361 my $LibraryNameTitle = C4::Context->preference("LibraryName");
362 $LibraryNameTitle =~ s/<(?:\/?)(?:br|p)\s*(?:\/?)>/ /sgi;
363 $LibraryNameTitle =~ s/<(?:[^<>'"]|'(?:[^']*)'|"(?:[^"]*)")*>//sg;
364 # clean up the busc param in the session if the page is not opac-detail
365 if (C4::Context->preference("OpacBrowseResults") && $in->{'template_name'} =~ /opac-(.+)\.(?:tt|tmpl)$/ && $1 !~ /^(?:MARC|ISBD)?detail$/) {
366 my $sessionSearch = get_session($sessionID || $in->{'query'}->cookie("CGISESSID"));
367 $sessionSearch->clear(["busc"]) if ($sessionSearch->param("busc"));
369 # variables passed from CGI: opac_css_override and opac_search_limits.
370 my $opac_search_limit = $ENV{'OPAC_SEARCH_LIMIT'};
371 my $opac_limit_override = $ENV{'OPAC_LIMIT_OVERRIDE'};
373 if (($opac_search_limit && $opac_search_limit =~ /branch:(\w+)/ && $opac_limit_override) || ($in->{'query'}->param('limit') && $in->{'query'}->param('limit') =~ /branch:(\w+)/)){
374 $opac_name = $1; # opac_search_limit is a branch, so we use it.
375 } elsif ( $in->{'query'}->param('multibranchlimit') ) {
376 $opac_name = $in->{'query'}->param('multibranchlimit');
377 } elsif (C4::Context->preference("SearchMyLibraryFirst") && C4::Context->userenv && C4::Context->userenv->{'branch'}) {
378 $opac_name = C4::Context->userenv->{'branch'};
381 opaccolorstylesheet => C4::Context->preference("opaccolorstylesheet"),
382 AnonSuggestions => "" . C4::Context->preference("AnonSuggestions"),
383 AuthorisedValueImages => C4::Context->preference("AuthorisedValueImages"),
384 BranchesLoop => GetBranchesLoop($opac_name),
385 BranchCategoriesLoop => GetBranchCategories( 'searchdomain', 1, $opac_name ),
386 CalendarFirstDayOfWeek => (C4::Context->preference("CalendarFirstDayOfWeek") eq "Sunday")?0:1,
387 LibraryName => "" . C4::Context->preference("LibraryName"),
388 LibraryNameTitle => "" . $LibraryNameTitle,
389 LoginBranchname => C4::Context->userenv?C4::Context->userenv->{"branchname"}:"",
390 OPACAmazonCoverImages => C4::Context->preference("OPACAmazonCoverImages"),
391 OPACFRBRizeEditions => C4::Context->preference("OPACFRBRizeEditions"),
392 OpacHighlightedWords => C4::Context->preference("OpacHighlightedWords"),
393 OPACItemHolds => C4::Context->preference("OPACItemHolds"),
394 OPACShelfBrowser => "". C4::Context->preference("OPACShelfBrowser"),
395 OPACURLOpenInNewWindow => "" . C4::Context->preference("OPACURLOpenInNewWindow"),
396 OPACUserCSS => "". C4::Context->preference("OPACUserCSS"),
397 OPACMobileUserCSS => "". C4::Context->preference("OPACMobileUserCSS"),
398 OPACViewOthersSuggestions => "" . C4::Context->preference("OPACViewOthersSuggestions"),
399 OpacAuthorities => C4::Context->preference("OpacAuthorities"),
400 OPACBaseURL => ($in->{'query'}->https() ? "https://" : "http://") . $ENV{'SERVER_NAME'} .
401 ($ENV{'SERVER_PORT'} eq ($in->{'query'}->https() ? "443" : "80") ? '' : ":$ENV{'SERVER_PORT'}"),
402 opac_css_override => $ENV{'OPAC_CSS_OVERRIDE'},
403 opac_search_limit => $opac_search_limit,
404 opac_limit_override => $opac_limit_override,
405 OpacBrowser => C4::Context->preference("OpacBrowser"),
406 OpacCloud => C4::Context->preference("OpacCloud"),
407 OpacKohaUrl => C4::Context->preference("OpacKohaUrl"),
408 OpacMainUserBlock => "" . C4::Context->preference("OpacMainUserBlock"),
409 OpacMainUserBlockMobile => "" . C4::Context->preference("OpacMainUserBlockMobile"),
410 OpacShowFiltersPulldownMobile => C4::Context->preference("OpacShowFiltersPulldownMobile"),
411 OpacShowLibrariesPulldownMobile => C4::Context->preference("OpacShowLibrariesPulldownMobile"),
412 OpacNav => "" . C4::Context->preference("OpacNav"),
413 OpacNavRight => "" . C4::Context->preference("OpacNavRight"),
414 OpacNavBottom => "" . C4::Context->preference("OpacNavBottom"),
415 OpacPasswordChange => C4::Context->preference("OpacPasswordChange"),
416 OPACPatronDetails => C4::Context->preference("OPACPatronDetails"),
417 OPACPrivacy => C4::Context->preference("OPACPrivacy"),
418 OPACFinesTab => C4::Context->preference("OPACFinesTab"),
419 OpacTopissue => C4::Context->preference("OpacTopissue"),
420 RequestOnOpac => C4::Context->preference("RequestOnOpac"),
421 'Version' => C4::Context->preference('Version'),
422 hidelostitems => C4::Context->preference("hidelostitems"),
423 mylibraryfirst => (C4::Context->preference("SearchMyLibraryFirst") && C4::Context->userenv) ? C4::Context->userenv->{'branch'} : '',
424 opaclayoutstylesheet => "" . C4::Context->preference("opaclayoutstylesheet"),
425 opacbookbag => "" . C4::Context->preference("opacbookbag"),
426 opaccredits => "" . C4::Context->preference("opaccredits"),
427 OpacFavicon => C4::Context->preference("OpacFavicon"),
428 opacheader => "" . C4::Context->preference("opacheader"),
429 opaclanguagesdisplay => "" . C4::Context->preference("opaclanguagesdisplay"),
430 opacreadinghistory => C4::Context->preference("opacreadinghistory"),
431 opacsmallimage => "" . C4::Context->preference("opacsmallimage"),
432 opacuserjs => C4::Context->preference("opacuserjs"),
433 opacuserlogin => "" . C4::Context->preference("opacuserlogin"),
434 ShowReviewer => C4::Context->preference("ShowReviewer"),
435 ShowReviewerPhoto => C4::Context->preference("ShowReviewerPhoto"),
436 suggestion => "" . C4::Context->preference("suggestion"),
437 virtualshelves => "" . C4::Context->preference("virtualshelves"),
438 OPACSerialIssueDisplayCount => C4::Context->preference("OPACSerialIssueDisplayCount"),
439 OPACXSLTDetailsDisplay => C4::Context->preference("OPACXSLTDetailsDisplay"),
440 OPACXSLTResultsDisplay => C4::Context->preference("OPACXSLTResultsDisplay"),
441 SyndeticsClientCode => C4::Context->preference("SyndeticsClientCode"),
442 SyndeticsEnabled => C4::Context->preference("SyndeticsEnabled"),
443 SyndeticsCoverImages => C4::Context->preference("SyndeticsCoverImages"),
444 SyndeticsTOC => C4::Context->preference("SyndeticsTOC"),
445 SyndeticsSummary => C4::Context->preference("SyndeticsSummary"),
446 SyndeticsEditions => C4::Context->preference("SyndeticsEditions"),
447 SyndeticsExcerpt => C4::Context->preference("SyndeticsExcerpt"),
448 SyndeticsReviews => C4::Context->preference("SyndeticsReviews"),
449 SyndeticsAuthorNotes => C4::Context->preference("SyndeticsAuthorNotes"),
450 SyndeticsAwards => C4::Context->preference("SyndeticsAwards"),
451 SyndeticsSeries => C4::Context->preference("SyndeticsSeries"),
452 SyndeticsCoverImageSize => C4::Context->preference("SyndeticsCoverImageSize"),
453 OPACLocalCoverImages => C4::Context->preference("OPACLocalCoverImages"),
454 PatronSelfRegistration => C4::Context->preference("PatronSelfRegistration"),
455 PatronSelfRegistrationDefaultCategory => C4::Context->preference("PatronSelfRegistrationDefaultCategory"),
458 $template->param(OpacPublic => '1') if ($user || C4::Context->preference("OpacPublic"));
460 return ( $template, $borrowernumber, $cookie, $flags);
465 ($userid, $cookie, $sessionID) = &checkauth($query, $noauth, $flagsrequired, $type);
467 Verifies that the user is authorized to run this script. If
468 the user is authorized, a (userid, cookie, session-id, flags)
469 quadruple is returned. If the user is not authorized but does
470 not have the required privilege (see $flagsrequired below), it
471 displays an error page and exits. Otherwise, it displays the
472 login page and exits.
474 Note that C<&checkauth> will return if and only if the user
475 is authorized, so it should be called early on, before any
476 unfinished operations (e.g., if you've opened a file, then
477 C<&checkauth> won't close it for you).
479 C<$query> is the CGI object for the script calling C<&checkauth>.
481 The C<$noauth> argument is optional. If it is set, then no
482 authorization is required for the script.
484 C<&checkauth> fetches user and session information from C<$query> and
485 ensures that the user is authorized to run scripts that require
488 The C<$flagsrequired> argument specifies the required privileges
489 the user must have if the username and password are correct.
490 It should be specified as a reference-to-hash; keys in the hash
491 should be the "flags" for the user, as specified in the Members
492 intranet module. Any key specified must correspond to a "flag"
493 in the userflags table. E.g., { circulate => 1 } would specify
494 that the user must have the "circulate" privilege in order to
495 proceed. To make sure that access control is correct, the
496 C<$flagsrequired> parameter must be specified correctly.
498 Koha also has a concept of sub-permissions, also known as
499 granular permissions. This makes the value of each key
500 in the C<flagsrequired> hash take on an additional
505 The user must have access to all subfunctions of the module
506 specified by the hash key.
510 The user must have access to at least one subfunction of the module
511 specified by the hash key.
513 specific permission, e.g., 'export_catalog'
515 The user must have access to the specific subfunction list, which
516 must correspond to a row in the permissions table.
518 The C<$type> argument specifies whether the template should be
519 retrieved from the opac or intranet directory tree. "opac" is
520 assumed if it is not specified; however, if C<$type> is specified,
521 "intranet" is assumed if it is not "opac".
523 If C<$query> does not have a valid session ID associated with it
524 (i.e., the user has not logged in) or if the session has expired,
525 C<&checkauth> presents the user with a login page (from the point of
526 view of the original script, C<&checkauth> does not return). Once the
527 user has authenticated, C<&checkauth> restarts the original script
528 (this time, C<&checkauth> returns).
530 The login page is provided using a HTML::Template, which is set in the
531 systempreferences table or at the top of this file. The variable C<$type>
532 selects which template to use, either the opac or the intranet
533 authentification template.
535 C<&checkauth> returns a user ID, a cookie, and a session ID. The
536 cookie should be sent back to the browser; it verifies that the user
545 # If Version syspref is unavailable, it means Koha is beeing installed,
546 # and so we must redirect to OPAC maintenance page or to the WebInstaller
547 # also, if OpacMaintenance is ON, OPAC should redirect to maintenance
548 if (C4::Context->preference('OpacMaintenance') && $type eq 'opac') {
549 warn "OPAC Install required, redirecting to maintenance";
550 print $query->redirect("/cgi-bin/koha/maintenance.pl");
553 unless ( $version = C4::Context->preference('Version') ) { # assignment, not comparison
554 if ( $type ne 'opac' ) {
555 warn "Install required, redirecting to Installer";
556 print $query->redirect("/cgi-bin/koha/installer/install.pl");
558 warn "OPAC Install required, redirecting to maintenance";
559 print $query->redirect("/cgi-bin/koha/maintenance.pl");
564 # check that database and koha version are the same
565 # there is no DB version, it's a fresh install,
566 # go to web installer
567 # there is a DB version, compare it to the code version
568 my $kohaversion=C4::Context::KOHAVERSION;
569 # remove the 3 last . to have a Perl number
570 $kohaversion =~ s/(.*\..*)\.(.*)\.(.*)/$1$2$3/;
571 $debug and print STDERR "kohaversion : $kohaversion\n";
572 if ($version < $kohaversion){
573 my $warning = "Database update needed, redirecting to %s. Database is $version and Koha is $kohaversion";
574 if ($type ne 'opac'){
575 warn sprintf($warning, 'Installer');
576 print $query->redirect("/cgi-bin/koha/installer/install.pl?step=3");
578 warn sprintf("OPAC: " . $warning, 'maintenance');
579 print $query->redirect("/cgi-bin/koha/maintenance.pl");
587 open my $fh, '>>', "/tmp/sessionlog" or warn "ERROR: Cannot append to /tmp/sessionlog";
588 printf $fh join("\n",@_);
592 sub _timeout_syspref {
593 my $timeout = C4::Context->preference('timeout') || 600;
594 # value in days, convert in seconds
595 if ($timeout =~ /(\d+)[dD]/) {
596 $timeout = $1 * 86400;
603 $debug and warn "Checking Auth";
604 # $authnotrequired will be set for scripts which will run without authentication
605 my $authnotrequired = shift;
606 my $flagsrequired = shift;
609 $type = 'opac' unless $type;
611 my $dbh = C4::Context->dbh;
612 my $timeout = _timeout_syspref();
614 _version_check($type,$query);
618 my ( $userid, $cookie, $sessionID, $flags, $barshelves, $pubshelves );
619 my $logout = $query->param('logout.x');
621 # This parameter is the name of the CAS server we want to authenticate against,
622 # when using authentication against multiple CAS servers, as configured in Auth_cas_servers.yaml
623 my $casparam = $query->param('cas');
625 if ( $userid = $ENV{'REMOTE_USER'} ) {
626 # Using Basic Authentication, no cookies required
627 $cookie = $query->cookie(
628 -name => 'CGISESSID',
636 # we dont want to set a session because we are being called by a persona callback
638 elsif ( $sessionID = $query->cookie("CGISESSID") )
639 { # assignment, not comparison
640 my $session = get_session($sessionID);
641 C4::Context->_new_userenv($sessionID);
642 my ($ip, $lasttime, $sessiontype);
644 C4::Context::set_userenv(
645 $session->param('number'), $session->param('id'),
646 $session->param('cardnumber'), $session->param('firstname'),
647 $session->param('surname'), $session->param('branch'),
648 $session->param('branchname'), $session->param('flags'),
649 $session->param('emailaddress'), $session->param('branchprinter'),
650 $session->param('persona')
652 C4::Context::set_shelves_userenv('bar',$session->param('barshelves'));
653 C4::Context::set_shelves_userenv('pub',$session->param('pubshelves'));
654 C4::Context::set_shelves_userenv('tot',$session->param('totshelves'));
655 $debug and printf STDERR "AUTH_SESSION: (%s)\t%s %s - %s\n", map {$session->param($_)} qw(cardnumber firstname surname branch) ;
656 $ip = $session->param('ip');
657 $lasttime = $session->param('lasttime');
658 $userid = $session->param('id');
659 $sessiontype = $session->param('sessiontype') || '';
661 if ( ( ($query->param('koha_login_context')) && ($query->param('userid') ne $session->param('id')) )
662 || ( $cas && $query->param('ticket') ) ) {
663 #if a user enters an id ne to the id in the current session, we need to log them in...
664 #first we need to clear the anonymous session...
665 $debug and warn "query id = " . $query->param('userid') . " but session id = " . $session->param('id');
668 C4::Context->_unset_userenv($sessionID);
673 # voluntary logout the user
676 C4::Context->_unset_userenv($sessionID);
677 #_session_log(sprintf "%20s from %16s logged out at %30s (manually).\n", $userid,$ip,(strftime "%c",localtime));
681 if ($cas and $caslogout) {
685 elsif ( $lasttime < time() - $timeout ) {
687 $info{'timed_out'} = 1;
688 $session->delete() if $session;
689 C4::Context->_unset_userenv($sessionID);
690 #_session_log(sprintf "%20s from %16s logged out at %30s (inactivity).\n", $userid,$ip,(strftime "%c",localtime));
694 elsif ( $ip ne $ENV{'REMOTE_ADDR'} ) {
695 # Different ip than originally logged in from
696 $info{'oldip'} = $ip;
697 $info{'newip'} = $ENV{'REMOTE_ADDR'};
698 $info{'different_ip'} = 1;
700 C4::Context->_unset_userenv($sessionID);
701 #_session_log(sprintf "%20s from %16s logged out at %30s (ip changed to %16s).\n", $userid,$ip,(strftime "%c",localtime), $info{'newip'});
706 $cookie = $query->cookie(
707 -name => 'CGISESSID',
708 -value => $session->id,
711 $session->param( 'lasttime', time() );
712 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...
713 $flags = haspermission($userid, $flagsrequired);
717 $info{'nopermission'} = 1;
722 unless ($userid || $sessionID) {
724 #we initiate a session prior to checking for a username to allow for anonymous sessions...
725 my $session = get_session("") or die "Auth ERROR: Cannot get_session()";
726 my $sessionID = $session->id;
727 C4::Context->_new_userenv($sessionID);
728 $cookie = $query->cookie(
729 -name => 'CGISESSID',
730 -value => $session->id,
733 $userid = $query->param('userid');
734 if ( ( $cas && $query->param('ticket') )
736 || ( my $pki_field = C4::Context->preference('AllowPKIAuth') ) ne
739 my $password = $query->param('password');
741 my ( $return, $cardnumber );
742 if ( $cas && $query->param('ticket') ) {
744 ( $return, $cardnumber, $retuserid ) =
745 checkpw( $dbh, $userid, $password, $query );
746 $userid = $retuserid;
747 $info{'invalidCasLogin'} = 1 unless ($return);
751 my $value = $persona;
753 # If we're looking up the email, there's a chance that the person
754 # doesn't have a userid. So if there is none, we pass along the
755 # borrower number, and the bits of code that need to know the user
756 # ID will have to be smart enough to handle that.
758 my @users_info = C4::Members::GetBorrowersWithEmail($value);
761 # First the userid, then the borrowernum
762 $value = $users_info[0][1] || $users_info[0][0];
767 $return = $value ? 1 : 0;
772 ( $pki_field eq 'Common Name' && $ENV{'SSL_CLIENT_S_DN_CN'} )
773 || ( $pki_field eq 'emailAddress'
774 && $ENV{'SSL_CLIENT_S_DN_Email'} )
778 if ( $pki_field eq 'Common Name' ) {
779 $value = $ENV{'SSL_CLIENT_S_DN_CN'};
781 elsif ( $pki_field eq 'emailAddress' ) {
782 $value = $ENV{'SSL_CLIENT_S_DN_Email'};
784 # If we're looking up the email, there's a chance that the person
785 # doesn't have a userid. So if there is none, we pass along the
786 # borrower number, and the bits of code that need to know the user
787 # ID will have to be smart enough to handle that.
789 my @users_info = C4::Members::GetBorrowersWithEmail($value);
792 # First the userid, then the borrowernum
793 $value = $users_info[0][1] || $users_info[0][0];
800 $return = $value ? 1 : 0;
806 ( $return, $cardnumber, $retuserid ) =
807 checkpw( $dbh, $userid, $password, $query );
808 $userid = $retuserid if ( $retuserid ne '' );
811 #_session_log(sprintf "%20s from %16s logged in at %30s.\n", $userid,$ENV{'REMOTE_ADDR'},(strftime '%c', localtime));
812 if ( $flags = haspermission( $userid, $flagsrequired ) ) {
816 $info{'nopermission'} = 1;
817 C4::Context->_unset_userenv($sessionID);
819 my ($borrowernumber, $firstname, $surname, $userflags,
820 $branchcode, $branchname, $branchprinter, $emailaddress);
822 if ( $return == 1 ) {
824 SELECT borrowernumber, firstname, surname, flags, borrowers.branchcode,
825 branches.branchname as branchname,
826 branches.branchprinter as branchprinter,
829 LEFT JOIN branches on borrowers.branchcode=branches.branchcode
831 my $sth = $dbh->prepare("$select where userid=?");
832 $sth->execute($userid);
833 unless ($sth->rows) {
834 $debug and print STDERR "AUTH_1: no rows for userid='$userid'\n";
835 $sth = $dbh->prepare("$select where cardnumber=?");
836 $sth->execute($cardnumber);
838 unless ($sth->rows) {
839 $debug and print STDERR "AUTH_2a: no rows for cardnumber='$cardnumber'\n";
840 $sth->execute($userid);
841 unless ($sth->rows) {
842 $debug and print STDERR "AUTH_2b: no rows for userid='$userid' AS cardnumber\n";
847 ($borrowernumber, $firstname, $surname, $userflags,
848 $branchcode, $branchname, $branchprinter, $emailaddress) = $sth->fetchrow;
849 $debug and print STDERR "AUTH_3 results: " .
850 "$cardnumber,$borrowernumber,$userid,$firstname,$surname,$userflags,$branchcode,$emailaddress\n";
852 print STDERR "AUTH_3: no results for userid='$userid', cardnumber='$cardnumber'.\n";
855 # launch a sequence to check if we have a ip for the branch, i
856 # if we have one we replace the branchcode of the userenv by the branch bound in the ip.
858 my $ip = $ENV{'REMOTE_ADDR'};
859 # if they specify at login, use that
860 if ($query->param('branch')) {
861 $branchcode = $query->param('branch');
862 $branchname = GetBranchName($branchcode);
864 my $branches = GetBranches();
865 if (C4::Context->boolean_preference('IndependantBranches') && C4::Context->boolean_preference('Autolocation')){
866 # we have to check they are coming from the right ip range
867 my $domain = $branches->{$branchcode}->{'branchip'};
868 if ($ip !~ /^$domain/){
870 $info{'wrongip'} = 1;
875 foreach my $br ( keys %$branches ) {
876 # now we work with the treatment of ip
877 my $domain = $branches->{$br}->{'branchip'};
878 if ( $domain && $ip =~ /^$domain/ ) {
879 $branchcode = $branches->{$br}->{'branchcode'};
881 # new op dev : add the branchprinter and branchname in the cookie
882 $branchprinter = $branches->{$br}->{'branchprinter'};
883 $branchname = $branches->{$br}->{'branchname'};
886 $session->param('number',$borrowernumber);
887 $session->param('id',$userid);
888 $session->param('cardnumber',$cardnumber);
889 $session->param('firstname',$firstname);
890 $session->param('surname',$surname);
891 $session->param('branch',$branchcode);
892 $session->param('branchname',$branchname);
893 $session->param('flags',$userflags);
894 $session->param('emailaddress',$emailaddress);
895 $session->param('ip',$session->remote_addr());
896 $session->param('lasttime',time());
897 $debug and printf STDERR "AUTH_4: (%s)\t%s %s - %s\n", map {$session->param($_)} qw(cardnumber firstname surname branch) ;
899 elsif ( $return == 2 ) {
900 #We suppose the user is the superlibrarian
902 $session->param('number',0);
903 $session->param('id',C4::Context->config('user'));
904 $session->param('cardnumber',C4::Context->config('user'));
905 $session->param('firstname',C4::Context->config('user'));
906 $session->param('surname',C4::Context->config('user'));
907 $session->param('branch','NO_LIBRARY_SET');
908 $session->param('branchname','NO_LIBRARY_SET');
909 $session->param('flags',1);
910 $session->param('emailaddress', C4::Context->preference('KohaAdminEmailAddress'));
911 $session->param('ip',$session->remote_addr());
912 $session->param('lasttime',time());
915 $session->param('persona',1);
917 C4::Context::set_userenv(
918 $session->param('number'), $session->param('id'),
919 $session->param('cardnumber'), $session->param('firstname'),
920 $session->param('surname'), $session->param('branch'),
921 $session->param('branchname'), $session->param('flags'),
922 $session->param('emailaddress'), $session->param('branchprinter'),
923 $session->param('persona')
929 $info{'invalid_username_or_password'} = 1;
930 C4::Context->_unset_userenv($sessionID);
933 } # END if ( $userid = $query->param('userid') )
934 elsif ($type eq "opac") {
935 # if we are here this is an anonymous session; add public lists to it and a few other items...
936 # anonymous sessions are created only for the OPAC
937 $debug and warn "Initiating an anonymous session...";
939 # setting a couple of other session vars...
940 $session->param('ip',$session->remote_addr());
941 $session->param('lasttime',time());
942 $session->param('sessiontype','anon');
944 } # END unless ($userid)
946 # finished authentification, now respond
947 if ( $loggedin || $authnotrequired )
951 $cookie = $query->cookie(
952 -name => 'CGISESSID',
957 return ( $userid, $cookie, $sessionID, $flags );
962 # AUTH rejected, show the login/password template, after checking the DB.
966 # get the inputs from the incoming query
968 foreach my $name ( param $query) {
969 (next) if ( $name eq 'userid' || $name eq 'password' || $name eq 'ticket' );
970 my $value = $query->param($name);
971 push @inputs, { name => $name, value => $value };
974 my $LibraryNameTitle = C4::Context->preference("LibraryName");
975 $LibraryNameTitle =~ s/<(?:\/?)(?:br|p)\s*(?:\/?)>/ /sgi;
976 $LibraryNameTitle =~ s/<(?:[^<>'"]|'(?:[^']*)'|"(?:[^"]*)")*>//sg;
978 my $template_name = ( $type eq 'opac' ) ? 'opac-auth.tmpl' : 'auth.tmpl';
979 my $template = C4::Templates::gettemplate($template_name, $type, $query );
981 branchloop => GetBranchesLoop(),
982 opaccolorstylesheet => C4::Context->preference("opaccolorstylesheet"),
983 opaclayoutstylesheet => C4::Context->preference("opaclayoutstylesheet"),
986 casAuthentication => C4::Context->preference("casAuthentication"),
987 suggestion => C4::Context->preference("suggestion"),
988 virtualshelves => C4::Context->preference("virtualshelves"),
989 LibraryName => "" . C4::Context->preference("LibraryName"),
990 LibraryNameTitle => "" . $LibraryNameTitle,
991 opacuserlogin => C4::Context->preference("opacuserlogin"),
992 OpacNav => C4::Context->preference("OpacNav"),
993 OpacNavRight => C4::Context->preference("OpacNavRight"),
994 OpacNavBottom => C4::Context->preference("OpacNavBottom"),
995 opaccredits => C4::Context->preference("opaccredits"),
996 OpacFavicon => C4::Context->preference("OpacFavicon"),
997 opacreadinghistory => C4::Context->preference("opacreadinghistory"),
998 opacsmallimage => C4::Context->preference("opacsmallimage"),
999 opaclanguagesdisplay => C4::Context->preference("opaclanguagesdisplay"),
1000 opacuserjs => C4::Context->preference("opacuserjs"),
1001 opacbookbag => "" . C4::Context->preference("opacbookbag"),
1002 OpacCloud => C4::Context->preference("OpacCloud"),
1003 OpacTopissue => C4::Context->preference("OpacTopissue"),
1004 OpacAuthorities => C4::Context->preference("OpacAuthorities"),
1005 OpacBrowser => C4::Context->preference("OpacBrowser"),
1006 opacheader => C4::Context->preference("opacheader"),
1007 TagsEnabled => C4::Context->preference("TagsEnabled"),
1008 OPACUserCSS => C4::Context->preference("OPACUserCSS"),
1009 intranetcolorstylesheet => C4::Context->preference("intranetcolorstylesheet"),
1010 intranetstylesheet => C4::Context->preference("intranetstylesheet"),
1011 intranetbookbag => C4::Context->preference("intranetbookbag"),
1012 IntranetNav => C4::Context->preference("IntranetNav"),
1013 IntranetFavicon => C4::Context->preference("IntranetFavicon"),
1014 intranetuserjs => C4::Context->preference("intranetuserjs"),
1015 IndependantBranches=> C4::Context->preference("IndependantBranches"),
1016 AutoLocation => C4::Context->preference("AutoLocation"),
1017 wrongip => $info{'wrongip'},
1018 PatronSelfRegistration => C4::Context->preference("PatronSelfRegistration"),
1019 PatronSelfRegistrationDefaultCategory => C4::Context->preference("PatronSelfRegistrationDefaultCategory"),
1020 persona => C4::Context->preference("Persona"),
1021 opac_css_override => $ENV{'OPAC_CSS_OVERRIDE'},
1024 $template->param( OpacPublic => C4::Context->preference("OpacPublic"));
1025 $template->param( loginprompt => 1 ) unless $info{'nopermission'};
1027 if($type eq 'opac'){
1028 my ($total, $pubshelves) = C4::VirtualShelves::GetSomeShelfNames(undef, 'MASTHEAD');
1030 pubshelves => $total->{pubtotal},
1031 pubshelvesloop => $pubshelves,
1037 # Is authentication against multiple CAS servers enabled?
1038 if (C4::Auth_with_cas::multipleAuth && !$casparam) {
1039 my $casservers = C4::Auth_with_cas::getMultipleAuth();
1041 foreach my $key (keys %$casservers) {
1042 push @tmplservers, {name => $key, value => login_cas_url($query, $key) . "?cas=$key" };
1045 casServersLoop => \@tmplservers
1049 casServerUrl => login_cas_url($query),
1054 invalidCasLogin => $info{'invalidCasLogin'}
1058 my $self_url = $query->url( -absolute => 1 );
1061 LibraryName => C4::Context->preference("LibraryName"),
1063 $template->param( %info );
1064 # $cookie = $query->cookie(CGISESSID => $session->id
1066 print $query->header(
1067 -type => 'text/html',
1068 -charset => 'utf-8',
1075 =head2 check_api_auth
1077 ($status, $cookie, $sessionId) = check_api_auth($query, $userflags);
1079 Given a CGI query containing the parameters 'userid' and 'password' and/or a session
1080 cookie, determine if the user has the privileges specified by C<$userflags>.
1082 C<check_api_auth> is is meant for authenticating users of web services, and
1083 consequently will always return and will not attempt to redirect the user
1086 If a valid session cookie is already present, check_api_auth will return a status
1087 of "ok", the cookie, and the Koha session ID.
1089 If no session cookie is present, check_api_auth will check the 'userid' and 'password
1090 parameters and create a session cookie and Koha session if the supplied credentials
1093 Possible return values in C<$status> are:
1097 =item "ok" -- user authenticated; C<$cookie> and C<$sessionid> have valid values.
1099 =item "failed" -- credentials are not correct; C<$cookie> and C<$sessionid> are undef
1101 =item "maintenance" -- DB is in maintenance mode; no login possible at the moment
1103 =item "expired -- session cookie has expired; API user should resubmit userid and password
1109 sub check_api_auth {
1111 my $flagsrequired = shift;
1113 my $dbh = C4::Context->dbh;
1114 my $timeout = _timeout_syspref();
1116 unless (C4::Context->preference('Version')) {
1117 # database has not been installed yet
1118 return ("maintenance", undef, undef);
1120 my $kohaversion=C4::Context::KOHAVERSION;
1121 $kohaversion =~ s/(.*\..*)\.(.*)\.(.*)/$1$2$3/;
1122 if (C4::Context->preference('Version') < $kohaversion) {
1123 # database in need of version update; assume that
1124 # no API should be called while databsae is in
1126 return ("maintenance", undef, undef);
1129 # FIXME -- most of what follows is a copy-and-paste
1130 # of code from checkauth. There is an obvious need
1131 # for refactoring to separate the various parts of
1132 # the authentication code, but as of 2007-11-19 this
1133 # is deferred so as to not introduce bugs into the
1134 # regular authentication code for Koha 3.0.
1136 # see if we have a valid session cookie already
1137 # however, if a userid parameter is present (i.e., from
1138 # a form submission, assume that any current cookie
1140 my $sessionID = undef;
1141 unless ($query->param('userid')) {
1142 $sessionID = $query->cookie("CGISESSID");
1144 if ($sessionID && not ($cas && $query->param('PT')) ) {
1145 my $session = get_session($sessionID);
1146 C4::Context->_new_userenv($sessionID);
1148 C4::Context::set_userenv(
1149 $session->param('number'), $session->param('id'),
1150 $session->param('cardnumber'), $session->param('firstname'),
1151 $session->param('surname'), $session->param('branch'),
1152 $session->param('branchname'), $session->param('flags'),
1153 $session->param('emailaddress'), $session->param('branchprinter')
1156 my $ip = $session->param('ip');
1157 my $lasttime = $session->param('lasttime');
1158 my $userid = $session->param('id');
1159 if ( $lasttime < time() - $timeout ) {
1162 C4::Context->_unset_userenv($sessionID);
1165 return ("expired", undef, undef);
1166 } elsif ( $ip ne $ENV{'REMOTE_ADDR'} ) {
1167 # IP address changed
1169 C4::Context->_unset_userenv($sessionID);
1172 return ("expired", undef, undef);
1174 my $cookie = $query->cookie(
1175 -name => 'CGISESSID',
1176 -value => $session->id,
1179 $session->param('lasttime',time());
1180 my $flags = haspermission($userid, $flagsrequired);
1182 return ("ok", $cookie, $sessionID);
1185 C4::Context->_unset_userenv($sessionID);
1188 return ("failed", undef, undef);
1192 return ("expired", undef, undef);
1196 my $userid = $query->param('userid');
1197 my $password = $query->param('password');
1198 my ($return, $cardnumber);
1201 if ($cas && $query->param('PT')) {
1203 $debug and print STDERR "## check_api_auth - checking CAS\n";
1204 # In case of a CAS authentication, we use the ticket instead of the password
1205 my $PT = $query->param('PT');
1206 ($return,$cardnumber,$userid) = check_api_auth_cas($dbh, $PT, $query); # EXTERNAL AUTH
1208 # User / password auth
1209 unless ($userid and $password) {
1210 # caller did something wrong, fail the authenticateion
1211 return ("failed", undef, undef);
1213 ( $return, $cardnumber ) = checkpw( $dbh, $userid, $password, $query );
1216 if ($return and haspermission( $userid, $flagsrequired)) {
1217 my $session = get_session("");
1218 return ("failed", undef, undef) unless $session;
1220 my $sessionID = $session->id;
1221 C4::Context->_new_userenv($sessionID);
1222 my $cookie = $query->cookie(
1223 -name => 'CGISESSID',
1224 -value => $sessionID,
1227 if ( $return == 1 ) {
1229 $borrowernumber, $firstname, $surname,
1230 $userflags, $branchcode, $branchname,
1231 $branchprinter, $emailaddress
1235 "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=?"
1237 $sth->execute($userid);
1239 $borrowernumber, $firstname, $surname,
1240 $userflags, $branchcode, $branchname,
1241 $branchprinter, $emailaddress
1242 ) = $sth->fetchrow if ( $sth->rows );
1244 unless ($sth->rows ) {
1245 my $sth = $dbh->prepare(
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 cardnumber=?"
1248 $sth->execute($cardnumber);
1250 $borrowernumber, $firstname, $surname,
1251 $userflags, $branchcode, $branchname,
1252 $branchprinter, $emailaddress
1253 ) = $sth->fetchrow if ( $sth->rows );
1255 unless ( $sth->rows ) {
1256 $sth->execute($userid);
1258 $borrowernumber, $firstname, $surname, $userflags,
1259 $branchcode, $branchname, $branchprinter, $emailaddress
1260 ) = $sth->fetchrow if ( $sth->rows );
1264 my $ip = $ENV{'REMOTE_ADDR'};
1265 # if they specify at login, use that
1266 if ($query->param('branch')) {
1267 $branchcode = $query->param('branch');
1268 $branchname = GetBranchName($branchcode);
1270 my $branches = GetBranches();
1272 foreach my $br ( keys %$branches ) {
1273 # now we work with the treatment of ip
1274 my $domain = $branches->{$br}->{'branchip'};
1275 if ( $domain && $ip =~ /^$domain/ ) {
1276 $branchcode = $branches->{$br}->{'branchcode'};
1278 # new op dev : add the branchprinter and branchname in the cookie
1279 $branchprinter = $branches->{$br}->{'branchprinter'};
1280 $branchname = $branches->{$br}->{'branchname'};
1283 $session->param('number',$borrowernumber);
1284 $session->param('id',$userid);
1285 $session->param('cardnumber',$cardnumber);
1286 $session->param('firstname',$firstname);
1287 $session->param('surname',$surname);
1288 $session->param('branch',$branchcode);
1289 $session->param('branchname',$branchname);
1290 $session->param('flags',$userflags);
1291 $session->param('emailaddress',$emailaddress);
1292 $session->param('ip',$session->remote_addr());
1293 $session->param('lasttime',time());
1294 } elsif ( $return == 2 ) {
1295 #We suppose the user is the superlibrarian
1296 $session->param('number',0);
1297 $session->param('id',C4::Context->config('user'));
1298 $session->param('cardnumber',C4::Context->config('user'));
1299 $session->param('firstname',C4::Context->config('user'));
1300 $session->param('surname',C4::Context->config('user'));
1301 $session->param('branch','NO_LIBRARY_SET');
1302 $session->param('branchname','NO_LIBRARY_SET');
1303 $session->param('flags',1);
1304 $session->param('emailaddress', C4::Context->preference('KohaAdminEmailAddress'));
1305 $session->param('ip',$session->remote_addr());
1306 $session->param('lasttime',time());
1308 C4::Context::set_userenv(
1309 $session->param('number'), $session->param('id'),
1310 $session->param('cardnumber'), $session->param('firstname'),
1311 $session->param('surname'), $session->param('branch'),
1312 $session->param('branchname'), $session->param('flags'),
1313 $session->param('emailaddress'), $session->param('branchprinter')
1315 return ("ok", $cookie, $sessionID);
1317 return ("failed", undef, undef);
1322 =head2 check_cookie_auth
1324 ($status, $sessionId) = check_api_auth($cookie, $userflags);
1326 Given a CGISESSID cookie set during a previous login to Koha, determine
1327 if the user has the privileges specified by C<$userflags>.
1329 C<check_cookie_auth> is meant for authenticating special services
1330 such as tools/upload-file.pl that are invoked by other pages that
1331 have been authenticated in the usual way.
1333 Possible return values in C<$status> are:
1337 =item "ok" -- user authenticated; C<$sessionID> have valid values.
1339 =item "failed" -- credentials are not correct; C<$sessionid> are undef
1341 =item "maintenance" -- DB is in maintenance mode; no login possible at the moment
1343 =item "expired -- session cookie has expired; API user should resubmit userid and password
1349 sub check_cookie_auth {
1351 my $flagsrequired = shift;
1353 my $dbh = C4::Context->dbh;
1354 my $timeout = _timeout_syspref();
1356 unless (C4::Context->preference('Version')) {
1357 # database has not been installed yet
1358 return ("maintenance", undef);
1360 my $kohaversion=C4::Context::KOHAVERSION;
1361 $kohaversion =~ s/(.*\..*)\.(.*)\.(.*)/$1$2$3/;
1362 if (C4::Context->preference('Version') < $kohaversion) {
1363 # database in need of version update; assume that
1364 # no API should be called while databsae is in
1366 return ("maintenance", undef);
1369 # FIXME -- most of what follows is a copy-and-paste
1370 # of code from checkauth. There is an obvious need
1371 # for refactoring to separate the various parts of
1372 # the authentication code, but as of 2007-11-23 this
1373 # is deferred so as to not introduce bugs into the
1374 # regular authentication code for Koha 3.0.
1376 # see if we have a valid session cookie already
1377 # however, if a userid parameter is present (i.e., from
1378 # a form submission, assume that any current cookie
1380 unless (defined $cookie and $cookie) {
1381 return ("failed", undef);
1383 my $sessionID = $cookie;
1384 my $session = get_session($sessionID);
1385 C4::Context->_new_userenv($sessionID);
1387 C4::Context::set_userenv(
1388 $session->param('number'), $session->param('id'),
1389 $session->param('cardnumber'), $session->param('firstname'),
1390 $session->param('surname'), $session->param('branch'),
1391 $session->param('branchname'), $session->param('flags'),
1392 $session->param('emailaddress'), $session->param('branchprinter')
1395 my $ip = $session->param('ip');
1396 my $lasttime = $session->param('lasttime');
1397 my $userid = $session->param('id');
1398 if ( $lasttime < time() - $timeout ) {
1401 C4::Context->_unset_userenv($sessionID);
1404 return ("expired", undef);
1405 } elsif ( $ip ne $ENV{'REMOTE_ADDR'} ) {
1406 # IP address changed
1408 C4::Context->_unset_userenv($sessionID);
1411 return ("expired", undef);
1413 $session->param('lasttime',time());
1414 my $flags = haspermission($userid, $flagsrequired);
1416 return ("ok", $sessionID);
1419 C4::Context->_unset_userenv($sessionID);
1422 return ("failed", undef);
1426 return ("expired", undef);
1433 my $session = get_session($sessionID);
1435 Given a session ID, retrieve the CGI::Session object used to store
1436 the session's state. The session object can be used to store
1437 data that needs to be accessed by different scripts during a
1440 If the C<$sessionID> parameter is an empty string, a new session
1446 my $sessionID = shift;
1447 my $storage_method = C4::Context->preference('SessionStorage');
1448 my $dbh = C4::Context->dbh;
1450 if ($storage_method eq 'mysql'){
1451 $session = new CGI::Session("driver:MySQL;serializer:yaml;id:md5", $sessionID, {Handle=>$dbh});
1453 elsif ($storage_method eq 'Pg') {
1454 $session = new CGI::Session("driver:PostgreSQL;serializer:yaml;id:md5", $sessionID, {Handle=>$dbh});
1456 elsif ($storage_method eq 'memcached' && C4::Context->ismemcached){
1457 $session = new CGI::Session("driver:memcached;serializer:yaml;id:md5", $sessionID, { Memcached => C4::Context->memcached } );
1460 # catch all defaults to tmp should work on all systems
1461 $session = new CGI::Session("driver:File;serializer:yaml;id:md5", $sessionID, {Directory=>'/tmp'});
1468 my ( $dbh, $userid, $password, $query ) = @_;
1470 $debug and print STDERR "## checkpw - checking LDAP\n";
1471 my ($retval,$retcard,$retuserid) = checkpw_ldap(@_); # EXTERNAL AUTH
1472 ($retval) and return ($retval,$retcard,$retuserid);
1475 if ($cas && $query && $query->param('ticket')) {
1476 $debug and print STDERR "## checkpw - checking CAS\n";
1477 # In case of a CAS authentication, we use the ticket instead of the password
1478 my $ticket = $query->param('ticket');
1479 my ($retval,$retcard,$retuserid) = checkpw_cas($dbh, $ticket, $query); # EXTERNAL AUTH
1480 ($retval) and return ($retval,$retcard,$retuserid);
1487 "select password,cardnumber,borrowernumber,userid,firstname,surname,branchcode,flags from borrowers where userid=?"
1489 $sth->execute($userid);
1491 my ( $md5password, $cardnumber, $borrowernumber, $userid, $firstname,
1492 $surname, $branchcode, $flags )
1494 if ( md5_base64($password) eq $md5password and $md5password ne "!") {
1496 C4::Context->set_userenv( "$borrowernumber", $userid, $cardnumber,
1497 $firstname, $surname, $branchcode, $flags );
1498 return 1, $cardnumber, $userid;
1503 "select password,cardnumber,borrowernumber,userid, firstname,surname,branchcode,flags from borrowers where cardnumber=?"
1505 $sth->execute($userid);
1507 my ( $md5password, $cardnumber, $borrowernumber, $userid, $firstname,
1508 $surname, $branchcode, $flags )
1510 if ( md5_base64($password) eq $md5password ) {
1512 C4::Context->set_userenv( $borrowernumber, $userid, $cardnumber,
1513 $firstname, $surname, $branchcode, $flags );
1514 return 1, $cardnumber, $userid;
1517 if ( $userid && $userid eq C4::Context->config('user')
1518 && "$password" eq C4::Context->config('pass') )
1521 # Koha superuser account
1522 # C4::Context->set_userenv(0,0,C4::Context->config('user'),C4::Context->config('user'),C4::Context->config('user'),"",1);
1525 if ( $userid && $userid eq 'demo'
1526 && "$password" eq 'demo'
1527 && C4::Context->config('demo') )
1530 # DEMO => the demo user is allowed to do everything (if demo set to 1 in koha.conf
1531 # some features won't be effective : modify systempref, modify MARC structure,
1539 my $authflags = getuserflags($flags, $userid, [$dbh]);
1541 Translates integer flags into permissions strings hash.
1543 C<$flags> is the integer userflags value ( borrowers.userflags )
1544 C<$userid> is the members.userid, used for building subpermissions
1545 C<$authflags> is a hashref of permissions
1552 my $dbh = @_ ? shift : C4::Context->dbh;
1555 # I don't want to do this, but if someone logs in as the database
1556 # user, it would be preferable not to spam them to death with
1557 # numeric warnings. So, we make $flags numeric.
1558 no warnings 'numeric';
1561 my $sth = $dbh->prepare("SELECT bit, flag, defaulton FROM userflags");
1564 while ( my ( $bit, $flag, $defaulton ) = $sth->fetchrow ) {
1565 if ( ( $flags & ( 2**$bit ) ) || $defaulton ) {
1566 $userflags->{$flag} = 1;
1569 $userflags->{$flag} = 0;
1573 # get subpermissions and merge with top-level permissions
1574 my $user_subperms = get_user_subpermissions($userid);
1575 foreach my $module (keys %$user_subperms) {
1576 next if $userflags->{$module} == 1; # user already has permission for everything in this module
1577 $userflags->{$module} = $user_subperms->{$module};
1583 =head2 get_user_subpermissions
1585 $user_perm_hashref = get_user_subpermissions($userid);
1587 Given the userid (note, not the borrowernumber) of a staff user,
1588 return a hashref of hashrefs of the specific subpermissions
1589 accorded to the user. An example return is
1593 export_catalog => 1,
1594 import_patrons => 1,
1598 The top-level hash-key is a module or function code from
1599 userflags.flag, while the second-level key is a code
1602 The results of this function do not give a complete picture
1603 of the functions that a staff user can access; it is also
1604 necessary to check borrowers.flags.
1608 sub get_user_subpermissions {
1611 my $dbh = C4::Context->dbh;
1612 my $sth = $dbh->prepare("SELECT flag, user_permissions.code
1613 FROM user_permissions
1614 JOIN permissions USING (module_bit, code)
1615 JOIN userflags ON (module_bit = bit)
1616 JOIN borrowers USING (borrowernumber)
1618 $sth->execute($userid);
1620 my $user_perms = {};
1621 while (my $perm = $sth->fetchrow_hashref) {
1622 $user_perms->{$perm->{'flag'}}->{$perm->{'code'}} = 1;
1627 =head2 get_all_subpermissions
1629 my $perm_hashref = get_all_subpermissions();
1631 Returns a hashref of hashrefs defining all specific
1632 permissions currently defined. The return value
1633 has the same structure as that of C<get_user_subpermissions>,
1634 except that the innermost hash value is the description
1635 of the subpermission.
1639 sub get_all_subpermissions {
1640 my $dbh = C4::Context->dbh;
1641 my $sth = $dbh->prepare("SELECT flag, code, description
1643 JOIN userflags ON (module_bit = bit)");
1647 while (my $perm = $sth->fetchrow_hashref) {
1648 $all_perms->{$perm->{'flag'}}->{$perm->{'code'}} = $perm->{'description'};
1653 =head2 haspermission
1655 $flags = ($userid, $flagsrequired);
1657 C<$userid> the userid of the member
1658 C<$flags> is a hashref of required flags like C<$borrower-<{authflags}>
1660 Returns member's flags or 0 if a permission is not met.
1665 my ($userid, $flagsrequired) = @_;
1666 my $sth = C4::Context->dbh->prepare("SELECT flags FROM borrowers WHERE userid=?");
1667 $sth->execute($userid);
1668 my $flags = getuserflags($sth->fetchrow(), $userid);
1669 if ( $userid eq C4::Context->config('user') ) {
1670 # Super User Account from /etc/koha.conf
1671 $flags->{'superlibrarian'} = 1;
1673 elsif ( $userid eq 'demo' && C4::Context->config('demo') ) {
1674 # Demo user that can do "anything" (demo=1 in /etc/koha.conf)
1675 $flags->{'superlibrarian'} = 1;
1678 return $flags if $flags->{superlibrarian};
1680 foreach my $module ( keys %$flagsrequired ) {
1681 my $subperm = $flagsrequired->{$module};
1682 if ($subperm eq '*') {
1683 return 0 unless ( $flags->{$module} == 1 or ref($flags->{$module}) );
1685 return 0 unless ( $flags->{$module} == 1 or
1686 ( ref($flags->{$module}) and
1687 exists $flags->{$module}->{$subperm} and
1688 $flags->{$module}->{$subperm} == 1
1694 #FIXME - This fcn should return the failed permission so a suitable error msg can be delivered.
1698 sub getborrowernumber {
1700 my $userenv = C4::Context->userenv;
1701 if ( defined( $userenv ) && ref( $userenv ) eq 'HASH' && $userenv->{number} ) {
1702 return $userenv->{number};
1704 my $dbh = C4::Context->dbh;
1705 for my $field ( 'userid', 'cardnumber' ) {
1707 $dbh->prepare("select borrowernumber from borrowers where $field=?");
1708 $sth->execute($userid);
1710 my ($bnumber) = $sth->fetchrow;
1717 sub ParseSearchHistoryCookie {
1719 my $search_cookie = $input->cookie('KohaOpacRecentSearches');
1720 return () unless $search_cookie;
1721 my $obj = eval { decode_json(uri_unescape($search_cookie)) };
1722 return () unless defined $obj;
1723 return () unless ref $obj eq 'ARRAY';
1727 END { } # module clean-up code here (global destructor)