Bug 15358: Fix authorities merge
[koha.git] / C4 / Auth.pm
blobe3e4cdec7b0fbc4a5e742ccbf2592908c69078e7
1 package C4::Auth;
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
8 # under the terms of the GNU General Public License as published by
9 # the Free Software Foundation; either version 3 of the License, or
10 # (at your option) any later version.
12 # Koha is distributed in the hope that it will be useful, but
13 # WITHOUT ANY WARRANTY; without even the implied warranty of
14 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 # GNU General Public License for more details.
17 # You should have received a copy of the GNU General Public License
18 # along with Koha; if not, see <http://www.gnu.org/licenses>.
20 use strict;
21 use warnings;
22 use Digest::MD5 qw(md5_base64);
23 use File::Spec;
24 use JSON qw/encode_json/;
25 use URI::Escape;
26 use CGI::Session;
28 require Exporter;
29 use C4::Context;
30 use C4::Templates; # to get the template
31 use C4::Languages;
32 use C4::Branch; # GetBranches
33 use C4::Search::History;
34 use Koha;
35 use Koha::AuthUtils qw(hash_password);
36 use POSIX qw/strftime/;
37 use List::MoreUtils qw/ any /;
38 use Encode qw( encode is_utf8);
40 # use utf8;
41 use vars qw($VERSION @ISA @EXPORT @EXPORT_OK %EXPORT_TAGS $debug $ldap $cas $caslogout $shib $shib_login);
43 BEGIN {
44 sub psgi_env { any { /^psgi\./ } keys %ENV }
46 sub safe_exit {
47 if (psgi_env) { die 'psgi:exit' }
48 else { exit }
50 $VERSION = 3.07.00.049; # set version for version checking
52 $debug = $ENV{DEBUG};
53 @ISA = qw(Exporter);
54 @EXPORT = qw(&checkauth &get_template_and_user &haspermission &get_user_subpermissions);
55 @EXPORT_OK = qw(&check_api_auth &get_session &check_cookie_auth &checkpw &checkpw_internal &checkpw_hash
56 &get_all_subpermissions &get_user_subpermissions
58 %EXPORT_TAGS = ( EditPermissions => [qw(get_all_subpermissions get_user_subpermissions)] );
59 $ldap = C4::Context->config('useldapserver') || 0;
60 $cas = C4::Context->preference('casAuthentication');
61 $shib = C4::Context->config('useshibboleth') || 0;
62 $caslogout = C4::Context->preference('casLogout');
63 require C4::Auth_with_cas; # no import
65 if ($ldap) {
66 require C4::Auth_with_ldap;
67 import C4::Auth_with_ldap qw(checkpw_ldap);
69 if ($shib) {
70 require C4::Auth_with_shibboleth;
71 import C4::Auth_with_shibboleth
72 qw(shib_ok checkpw_shib logout_shib login_shib_url get_login_shib);
74 # Check for good config
75 if ( shib_ok() ) {
77 # Get shibboleth login attribute
78 $shib_login = get_login_shib();
81 # Bad config, disable shibboleth
82 else {
83 $shib = 0;
86 if ($cas) {
87 import C4::Auth_with_cas qw(check_api_auth_cas checkpw_cas login_cas logout_cas login_cas_url);
92 =head1 NAME
94 C4::Auth - Authenticates Koha users
96 =head1 SYNOPSIS
98 use CGI qw ( -utf8 );
99 use C4::Auth;
100 use C4::Output;
102 my $query = new CGI;
104 my ($template, $borrowernumber, $cookie)
105 = get_template_and_user(
107 template_name => "opac-main.tt",
108 query => $query,
109 type => "opac",
110 authnotrequired => 0,
111 flagsrequired => { catalogue => '*', tools => 'import_patrons' },
115 output_html_with_http_headers $query, $cookie, $template->output;
117 =head1 DESCRIPTION
119 The main function of this module is to provide
120 authentification. However the get_template_and_user function has
121 been provided so that a users login information is passed along
122 automatically. This gets loaded into the template.
124 =head1 FUNCTIONS
126 =head2 get_template_and_user
128 my ($template, $borrowernumber, $cookie)
129 = get_template_and_user(
131 template_name => "opac-main.tt",
132 query => $query,
133 type => "opac",
134 authnotrequired => 0,
135 flagsrequired => { catalogue => '*', tools => 'import_patrons' },
139 This call passes the C<query>, C<flagsrequired> and C<authnotrequired>
140 to C<&checkauth> (in this module) to perform authentification.
141 See C<&checkauth> for an explanation of these parameters.
143 The C<template_name> is then used to find the correct template for
144 the page. The authenticated users details are loaded onto the
145 template in the HTML::Template LOOP variable C<USER_INFO>. Also the
146 C<sessionID> is passed to the template. This can be used in templates
147 if cookies are disabled. It needs to be put as and input to every
148 authenticated page.
150 More information on the C<gettemplate> sub can be found in the
151 Output.pm module.
153 =cut
155 sub get_template_and_user {
157 my $in = shift;
158 my ( $user, $cookie, $sessionID, $flags );
160 C4::Context->interface( $in->{type} );
162 my $safe_chars = 'a-zA-Z0-9_\-\/';
163 die "bad template path" unless $in->{'template_name'} =~ m/^[$safe_chars]+\.tt$/ig; #sanitize input
165 $in->{'authnotrequired'} ||= 0;
166 my $template = C4::Templates::gettemplate(
167 $in->{'template_name'},
168 $in->{'type'},
169 $in->{'query'},
170 $in->{'is_plugin'}
173 if ( $in->{'template_name'} !~ m/maintenance/ ) {
174 ( $user, $cookie, $sessionID, $flags ) = checkauth(
175 $in->{'query'},
176 $in->{'authnotrequired'},
177 $in->{'flagsrequired'},
178 $in->{'type'}
183 # If the user logged in is the SCO user and he tries to go out the SCO module, log the user out removing the CGISESSID cookie
184 if ( $in->{type} eq 'opac' and $in->{template_name} !~ m|sco/| ) {
185 if ( C4::Context->preference('AutoSelfCheckID') && $user eq C4::Context->preference('AutoSelfCheckID') ) {
186 $template = C4::Templates::gettemplate( 'opac-auth.tt', 'opac', $in->{query} );
187 my $cookie = $in->{query}->cookie(
188 -name => 'CGISESSID',
189 -value => '',
190 -expires => '',
191 -HttpOnly => 1,
194 $template->param( loginprompt => 1 );
195 print $in->{query}->header(
196 -type => 'text/html',
197 -charset => 'utf-8',
198 -cookie => $cookie,
200 $template->output;
201 safe_exit;
205 my $borrowernumber;
206 if ($user) {
207 require C4::Members;
209 # It's possible for $user to be the borrowernumber if they don't have a
210 # userid defined (and are logging in through some other method, such
211 # as SSL certs against an email address)
212 my $borrower;
213 $borrowernumber = getborrowernumber($user) if defined($user);
214 if ( !defined($borrowernumber) && defined($user) ) {
215 $borrower = C4::Members::GetMember( borrowernumber => $user );
216 if ($borrower) {
217 $borrowernumber = $user;
219 # A bit of a hack, but I don't know there's a nicer way
220 # to do it.
221 $user = $borrower->{firstname} . ' ' . $borrower->{surname};
223 } else {
224 $borrower = C4::Members::GetMember( borrowernumber => $borrowernumber );
227 # user info
228 $template->param( loggedinusername => $user );
229 $template->param( loggedinusernumber => $borrowernumber );
230 $template->param( sessionID => $sessionID );
232 if ( $in->{'type'} eq 'opac' ) {
233 require Koha::Virtualshelves;
234 my $some_private_shelves = Koha::Virtualshelves->get_some_shelves(
236 borrowernumber => $borrowernumber,
237 category => 1,
240 my $some_public_shelves = Koha::Virtualshelves->get_some_shelves(
242 category => 2,
245 $template->param(
246 some_private_shelves => $some_private_shelves,
247 some_public_shelves => $some_public_shelves,
251 $template->param( "USER_INFO" => $borrower );
253 my $all_perms = get_all_subpermissions();
255 my @flagroots = qw(circulate catalogue parameters borrowers permissions reserveforothers borrow
256 editcatalogue updatecharges management tools editauthorities serials reports acquisition);
258 # We are going to use the $flags returned by checkauth
259 # to create the template's parameters that will indicate
260 # which menus the user can access.
261 if ( $flags && $flags->{superlibrarian} == 1 ) {
262 $template->param( CAN_user_circulate => 1 );
263 $template->param( CAN_user_catalogue => 1 );
264 $template->param( CAN_user_parameters => 1 );
265 $template->param( CAN_user_borrowers => 1 );
266 $template->param( CAN_user_permissions => 1 );
267 $template->param( CAN_user_reserveforothers => 1 );
268 $template->param( CAN_user_editcatalogue => 1 );
269 $template->param( CAN_user_updatecharges => 1 );
270 $template->param( CAN_user_acquisition => 1 );
271 $template->param( CAN_user_management => 1 );
272 $template->param( CAN_user_tools => 1 );
273 $template->param( CAN_user_editauthorities => 1 );
274 $template->param( CAN_user_serials => 1 );
275 $template->param( CAN_user_reports => 1 );
276 $template->param( CAN_user_staffaccess => 1 );
277 $template->param( CAN_user_plugins => 1 );
278 $template->param( CAN_user_coursereserves => 1 );
279 foreach my $module ( keys %$all_perms ) {
281 foreach my $subperm ( keys %{ $all_perms->{$module} } ) {
282 $template->param( "CAN_user_${module}_${subperm}" => 1 );
287 if ($flags) {
288 foreach my $module ( keys %$all_perms ) {
289 if ( defined($flags->{$module}) && $flags->{$module} == 1 ) {
290 foreach my $subperm ( keys %{ $all_perms->{$module} } ) {
291 $template->param( "CAN_user_${module}_${subperm}" => 1 );
293 } elsif ( ref( $flags->{$module} ) ) {
294 foreach my $subperm ( keys %{ $flags->{$module} } ) {
295 $template->param( "CAN_user_${module}_${subperm}" => 1 );
301 if ($flags) {
302 foreach my $module ( keys %$flags ) {
303 if ( $flags->{$module} == 1 or ref( $flags->{$module} ) ) {
304 $template->param( "CAN_user_$module" => 1 );
305 if ( $module eq "parameters" ) {
306 $template->param( CAN_user_management => 1 );
312 # Logged-in opac search history
313 # If the requested template is an opac one and opac search history is enabled
314 if ( $in->{type} eq 'opac' && C4::Context->preference('EnableOpacSearchHistory') ) {
315 my $dbh = C4::Context->dbh;
316 my $query = "SELECT COUNT(*) FROM search_history WHERE userid=?";
317 my $sth = $dbh->prepare($query);
318 $sth->execute($borrowernumber);
320 # If at least one search has already been performed
321 if ( $sth->fetchrow_array > 0 ) {
323 # We show the link in opac
324 $template->param( EnableOpacSearchHistory => 1 );
327 # And if there are searches performed when the user was not logged in,
328 # we add them to the logged-in search history
329 my @recentSearches = C4::Search::History::get_from_session( { cgi => $in->{'query'} } );
330 if (@recentSearches) {
331 my $dbh = C4::Context->dbh;
332 my $query = q{
333 INSERT INTO search_history(userid, sessionid, query_desc, query_cgi, type, total, time )
334 VALUES (?, ?, ?, ?, ?, ?, ?)
337 my $sth = $dbh->prepare($query);
338 $sth->execute( $borrowernumber,
339 $in->{query}->cookie("CGISESSID"),
340 $_->{query_desc},
341 $_->{query_cgi},
342 $_->{type} || 'biblio',
343 $_->{total},
344 $_->{time},
345 ) foreach @recentSearches;
347 # clear out the search history from the session now that
348 # we've saved it to the database
349 C4::Search::History::set_to_session( { cgi => $in->{'query'}, search_history => [] } );
351 } elsif ( $in->{type} eq 'intranet' and C4::Context->preference('EnableSearchHistory') ) {
352 $template->param( EnableSearchHistory => 1 );
355 else { # if this is an anonymous session, setup to display public lists...
357 # If shibboleth is enabled, and we're in an anonymous session, we should allow
358 # the user to attempt login via shibboleth.
359 if ($shib) {
360 $template->param( shibbolethAuthentication => $shib,
361 shibbolethLoginUrl => login_shib_url( $in->{'query'} ),
364 # If shibboleth is enabled and we have a shibboleth login attribute,
365 # but we are in an anonymous session, then we clearly have an invalid
366 # shibboleth koha account.
367 if ($shib_login) {
368 $template->param( invalidShibLogin => '1' );
372 $template->param( sessionID => $sessionID );
374 if ( $in->{'type'} eq 'opac' ){
375 require Koha::Virtualshelves;
376 my $some_public_shelves = Koha::Virtualshelves->get_some_shelves(
378 category => 2,
381 $template->param(
382 some_public_shelves => $some_public_shelves,
387 # Anonymous opac search history
388 # If opac search history is enabled and at least one search has already been performed
389 if ( C4::Context->preference('EnableOpacSearchHistory') ) {
390 my @recentSearches = C4::Search::History::get_from_session( { cgi => $in->{'query'} } );
391 if (@recentSearches) {
392 $template->param( EnableOpacSearchHistory => 1 );
396 if ( C4::Context->preference('dateformat') ) {
397 $template->param( dateformat => C4::Context->preference('dateformat') );
400 $template->param(auth_forwarded_hash => $in->{'query'}->param('auth_forwarded_hash'));
402 # these template parameters are set the same regardless of $in->{'type'}
404 # Set the using_https variable for templates
405 # FIXME Under Plack the CGI->https method always returns 'OFF'
406 my $https = $in->{query}->https();
407 my $using_https = ( defined $https and $https ne 'OFF' ) ? 1 : 0;
409 $template->param(
410 "BiblioDefaultView" . C4::Context->preference("BiblioDefaultView") => 1,
411 EnhancedMessagingPreferences => C4::Context->preference('EnhancedMessagingPreferences'),
412 GoogleJackets => C4::Context->preference("GoogleJackets"),
413 OpenLibraryCovers => C4::Context->preference("OpenLibraryCovers"),
414 KohaAdminEmailAddress => "" . C4::Context->preference("KohaAdminEmailAddress"),
415 LoginBranchcode => ( C4::Context->userenv ? C4::Context->userenv->{"branch"} : undef ),
416 LoginFirstname => ( C4::Context->userenv ? C4::Context->userenv->{"firstname"} : "Bel" ),
417 LoginSurname => C4::Context->userenv ? C4::Context->userenv->{"surname"} : "Inconnu",
418 emailaddress => C4::Context->userenv ? C4::Context->userenv->{"emailaddress"} : undef,
419 loggedinpersona => C4::Context->userenv ? C4::Context->userenv->{"persona"} : undef,
420 TagsEnabled => C4::Context->preference("TagsEnabled"),
421 hide_marc => C4::Context->preference("hide_marc"),
422 item_level_itypes => C4::Context->preference('item-level_itypes'),
423 patronimages => C4::Context->preference("patronimages"),
424 singleBranchMode => C4::Context->preference("singleBranchMode"),
425 XSLTDetailsDisplay => C4::Context->preference("XSLTDetailsDisplay"),
426 XSLTResultsDisplay => C4::Context->preference("XSLTResultsDisplay"),
427 using_https => $using_https,
428 noItemTypeImages => C4::Context->preference("noItemTypeImages"),
429 marcflavour => C4::Context->preference("marcflavour"),
430 persona => C4::Context->preference("persona"),
431 OPACBaseURL => C4::Context->preference('OPACBaseURL'),
433 if ( $in->{'type'} eq "intranet" ) {
434 $template->param(
435 AmazonCoverImages => C4::Context->preference("AmazonCoverImages"),
436 AutoLocation => C4::Context->preference("AutoLocation"),
437 "BiblioDefaultView" . C4::Context->preference("IntranetBiblioDefaultView") => 1,
438 CircAutocompl => C4::Context->preference("CircAutocompl"),
439 FRBRizeEditions => C4::Context->preference("FRBRizeEditions"),
440 IndependentBranches => C4::Context->preference("IndependentBranches"),
441 IntranetNav => C4::Context->preference("IntranetNav"),
442 IntranetmainUserblock => C4::Context->preference("IntranetmainUserblock"),
443 LibraryName => C4::Context->preference("LibraryName"),
444 LoginBranchname => ( C4::Context->userenv ? C4::Context->userenv->{"branchname"} : undef ),
445 advancedMARCEditor => C4::Context->preference("advancedMARCEditor"),
446 canreservefromotherbranches => C4::Context->preference('canreservefromotherbranches'),
447 intranetcolorstylesheet => C4::Context->preference("intranetcolorstylesheet"),
448 IntranetFavicon => C4::Context->preference("IntranetFavicon"),
449 intranetreadinghistory => C4::Context->preference("intranetreadinghistory"),
450 intranetstylesheet => C4::Context->preference("intranetstylesheet"),
451 IntranetUserCSS => C4::Context->preference("IntranetUserCSS"),
452 IntranetUserJS => C4::Context->preference("IntranetUserJS"),
453 intranetbookbag => C4::Context->preference("intranetbookbag"),
454 suggestion => C4::Context->preference("suggestion"),
455 virtualshelves => C4::Context->preference("virtualshelves"),
456 StaffSerialIssueDisplayCount => C4::Context->preference("StaffSerialIssueDisplayCount"),
457 EasyAnalyticalRecords => C4::Context->preference('EasyAnalyticalRecords'),
458 LocalCoverImages => C4::Context->preference('LocalCoverImages'),
459 OPACLocalCoverImages => C4::Context->preference('OPACLocalCoverImages'),
460 AllowMultipleCovers => C4::Context->preference('AllowMultipleCovers'),
461 EnableBorrowerFiles => C4::Context->preference('EnableBorrowerFiles'),
462 UseKohaPlugins => C4::Context->preference('UseKohaPlugins'),
463 UseCourseReserves => C4::Context->preference("UseCourseReserves"),
464 useDischarge => C4::Context->preference('useDischarge'),
467 else {
468 warn "template type should be OPAC, here it is=[" . $in->{'type'} . "]" unless ( $in->{'type'} eq 'opac' );
470 #TODO : replace LibraryName syspref with 'system name', and remove this html processing
471 my $LibraryNameTitle = C4::Context->preference("LibraryName");
472 $LibraryNameTitle =~ s/<(?:\/?)(?:br|p)\s*(?:\/?)>/ /sgi;
473 $LibraryNameTitle =~ s/<(?:[^<>'"]|'(?:[^']*)'|"(?:[^"]*)")*>//sg;
475 # clean up the busc param in the session
476 # if the page is not opac-detail and not the "add to list" page
477 # and not the "edit comments" page
478 if ( C4::Context->preference("OpacBrowseResults")
479 && $in->{'template_name'} =~ /opac-(.+)\.(?:tt|tmpl)$/ ) {
480 my $pagename = $1;
481 unless ( $pagename =~ /^(?:MARC|ISBD)?detail$/
482 or $pagename =~ /^addbybiblionumber$/
483 or $pagename =~ /^review$/ ) {
484 my $sessionSearch = get_session( $sessionID || $in->{'query'}->cookie("CGISESSID") );
485 $sessionSearch->clear( ["busc"] ) if ( $sessionSearch->param("busc") );
489 # variables passed from CGI: opac_css_override and opac_search_limits.
490 my $opac_search_limit = $ENV{'OPAC_SEARCH_LIMIT'};
491 my $opac_limit_override = $ENV{'OPAC_LIMIT_OVERRIDE'};
492 my $opac_name = '';
493 if (
494 ( $opac_limit_override && $opac_search_limit && $opac_search_limit =~ /branch:(\w+)/ ) ||
495 ( $in->{'query'}->param('limit') && $in->{'query'}->param('limit') =~ /branch:(\w+)/ ) ||
496 ( $in->{'query'}->param('multibranchlimit') && $in->{'query'}->param('multibranchlimit') =~ /multibranchlimit-(\w+)/ )
498 $opac_name = $1; # opac_search_limit is a branch, so we use it.
499 } elsif ( $in->{'query'}->param('multibranchlimit') ) {
500 $opac_name = $in->{'query'}->param('multibranchlimit');
501 } elsif ( C4::Context->preference("SearchMyLibraryFirst") && C4::Context->userenv && C4::Context->userenv->{'branch'} ) {
502 $opac_name = C4::Context->userenv->{'branch'};
505 $template->param(
506 OpacAdditionalStylesheet => C4::Context->preference("OpacAdditionalStylesheet"),
507 AnonSuggestions => "" . C4::Context->preference("AnonSuggestions"),
508 AuthorisedValueImages => C4::Context->preference("AuthorisedValueImages"),
509 BranchesLoop => GetBranchesLoop($opac_name),
510 BranchCategoriesLoop => GetBranchCategories( 'searchdomain', 1, $opac_name ),
511 LibraryName => "" . C4::Context->preference("LibraryName"),
512 LibraryNameTitle => "" . $LibraryNameTitle,
513 LoginBranchname => C4::Context->userenv ? C4::Context->userenv->{"branchname"} : "",
514 OPACAmazonCoverImages => C4::Context->preference("OPACAmazonCoverImages"),
515 OPACFRBRizeEditions => C4::Context->preference("OPACFRBRizeEditions"),
516 OpacHighlightedWords => C4::Context->preference("OpacHighlightedWords"),
517 OPACShelfBrowser => "" . C4::Context->preference("OPACShelfBrowser"),
518 OPACURLOpenInNewWindow => "" . C4::Context->preference("OPACURLOpenInNewWindow"),
519 OPACUserCSS => "" . C4::Context->preference("OPACUserCSS"),
520 OpacAuthorities => C4::Context->preference("OpacAuthorities"),
521 opac_css_override => $ENV{'OPAC_CSS_OVERRIDE'},
522 opac_search_limit => $opac_search_limit,
523 opac_limit_override => $opac_limit_override,
524 OpacBrowser => C4::Context->preference("OpacBrowser"),
525 OpacCloud => C4::Context->preference("OpacCloud"),
526 OpacKohaUrl => C4::Context->preference("OpacKohaUrl"),
527 OpacMainUserBlock => "" . C4::Context->preference("OpacMainUserBlock"),
528 OpacNav => "" . C4::Context->preference("OpacNav"),
529 OpacNavRight => "" . C4::Context->preference("OpacNavRight"),
530 OpacNavBottom => "" . C4::Context->preference("OpacNavBottom"),
531 OpacPasswordChange => C4::Context->preference("OpacPasswordChange"),
532 OPACPatronDetails => C4::Context->preference("OPACPatronDetails"),
533 OPACPrivacy => C4::Context->preference("OPACPrivacy"),
534 OPACFinesTab => C4::Context->preference("OPACFinesTab"),
535 OpacTopissue => C4::Context->preference("OpacTopissue"),
536 RequestOnOpac => C4::Context->preference("RequestOnOpac"),
537 'Version' => C4::Context->preference('Version'),
538 hidelostitems => C4::Context->preference("hidelostitems"),
539 mylibraryfirst => ( C4::Context->preference("SearchMyLibraryFirst") && C4::Context->userenv ) ? C4::Context->userenv->{'branch'} : '',
540 opaclayoutstylesheet => "" . C4::Context->preference("opaclayoutstylesheet"),
541 opacbookbag => "" . C4::Context->preference("opacbookbag"),
542 opaccredits => "" . C4::Context->preference("opaccredits"),
543 OpacFavicon => C4::Context->preference("OpacFavicon"),
544 opacheader => "" . C4::Context->preference("opacheader"),
545 opaclanguagesdisplay => "" . C4::Context->preference("opaclanguagesdisplay"),
546 opacreadinghistory => C4::Context->preference("opacreadinghistory"),
547 OPACUserJS => C4::Context->preference("OPACUserJS"),
548 opacuserlogin => "" . C4::Context->preference("opacuserlogin"),
549 ShowReviewer => C4::Context->preference("ShowReviewer"),
550 ShowReviewerPhoto => C4::Context->preference("ShowReviewerPhoto"),
551 suggestion => "" . C4::Context->preference("suggestion"),
552 virtualshelves => "" . C4::Context->preference("virtualshelves"),
553 OPACSerialIssueDisplayCount => C4::Context->preference("OPACSerialIssueDisplayCount"),
554 OPACXSLTDetailsDisplay => C4::Context->preference("OPACXSLTDetailsDisplay"),
555 OPACXSLTResultsDisplay => C4::Context->preference("OPACXSLTResultsDisplay"),
556 SyndeticsClientCode => C4::Context->preference("SyndeticsClientCode"),
557 SyndeticsEnabled => C4::Context->preference("SyndeticsEnabled"),
558 SyndeticsCoverImages => C4::Context->preference("SyndeticsCoverImages"),
559 SyndeticsTOC => C4::Context->preference("SyndeticsTOC"),
560 SyndeticsSummary => C4::Context->preference("SyndeticsSummary"),
561 SyndeticsEditions => C4::Context->preference("SyndeticsEditions"),
562 SyndeticsExcerpt => C4::Context->preference("SyndeticsExcerpt"),
563 SyndeticsReviews => C4::Context->preference("SyndeticsReviews"),
564 SyndeticsAuthorNotes => C4::Context->preference("SyndeticsAuthorNotes"),
565 SyndeticsAwards => C4::Context->preference("SyndeticsAwards"),
566 SyndeticsSeries => C4::Context->preference("SyndeticsSeries"),
567 SyndeticsCoverImageSize => C4::Context->preference("SyndeticsCoverImageSize"),
568 OPACLocalCoverImages => C4::Context->preference("OPACLocalCoverImages"),
569 PatronSelfRegistration => C4::Context->preference("PatronSelfRegistration"),
570 PatronSelfRegistrationDefaultCategory => C4::Context->preference("PatronSelfRegistrationDefaultCategory"),
571 useDischarge => C4::Context->preference('useDischarge'),
574 $template->param( OpacPublic => '1' ) if ( $user || C4::Context->preference("OpacPublic") );
577 # Check if we were asked using parameters to force a specific language
578 if ( defined $in->{'query'}->param('language') ) {
580 # Extract the language, let C4::Languages::getlanguage choose
581 # what to do
582 my $language = C4::Languages::getlanguage( $in->{'query'} );
583 my $languagecookie = C4::Templates::getlanguagecookie( $in->{'query'}, $language );
584 if ( ref $cookie eq 'ARRAY' ) {
585 push @{$cookie}, $languagecookie;
586 } else {
587 $cookie = [ $cookie, $languagecookie ];
591 return ( $template, $borrowernumber, $cookie, $flags );
594 =head2 checkauth
596 ($userid, $cookie, $sessionID) = &checkauth($query, $noauth, $flagsrequired, $type);
598 Verifies that the user is authorized to run this script. If
599 the user is authorized, a (userid, cookie, session-id, flags)
600 quadruple is returned. If the user is not authorized but does
601 not have the required privilege (see $flagsrequired below), it
602 displays an error page and exits. Otherwise, it displays the
603 login page and exits.
605 Note that C<&checkauth> will return if and only if the user
606 is authorized, so it should be called early on, before any
607 unfinished operations (e.g., if you've opened a file, then
608 C<&checkauth> won't close it for you).
610 C<$query> is the CGI object for the script calling C<&checkauth>.
612 The C<$noauth> argument is optional. If it is set, then no
613 authorization is required for the script.
615 C<&checkauth> fetches user and session information from C<$query> and
616 ensures that the user is authorized to run scripts that require
617 authorization.
619 The C<$flagsrequired> argument specifies the required privileges
620 the user must have if the username and password are correct.
621 It should be specified as a reference-to-hash; keys in the hash
622 should be the "flags" for the user, as specified in the Members
623 intranet module. Any key specified must correspond to a "flag"
624 in the userflags table. E.g., { circulate => 1 } would specify
625 that the user must have the "circulate" privilege in order to
626 proceed. To make sure that access control is correct, the
627 C<$flagsrequired> parameter must be specified correctly.
629 Koha also has a concept of sub-permissions, also known as
630 granular permissions. This makes the value of each key
631 in the C<flagsrequired> hash take on an additional
632 meaning, i.e.,
636 The user must have access to all subfunctions of the module
637 specified by the hash key.
641 The user must have access to at least one subfunction of the module
642 specified by the hash key.
644 specific permission, e.g., 'export_catalog'
646 The user must have access to the specific subfunction list, which
647 must correspond to a row in the permissions table.
649 The C<$type> argument specifies whether the template should be
650 retrieved from the opac or intranet directory tree. "opac" is
651 assumed if it is not specified; however, if C<$type> is specified,
652 "intranet" is assumed if it is not "opac".
654 If C<$query> does not have a valid session ID associated with it
655 (i.e., the user has not logged in) or if the session has expired,
656 C<&checkauth> presents the user with a login page (from the point of
657 view of the original script, C<&checkauth> does not return). Once the
658 user has authenticated, C<&checkauth> restarts the original script
659 (this time, C<&checkauth> returns).
661 The login page is provided using a HTML::Template, which is set in the
662 systempreferences table or at the top of this file. The variable C<$type>
663 selects which template to use, either the opac or the intranet
664 authentification template.
666 C<&checkauth> returns a user ID, a cookie, and a session ID. The
667 cookie should be sent back to the browser; it verifies that the user
668 has authenticated.
670 =cut
672 sub _version_check {
673 my $type = shift;
674 my $query = shift;
675 my $version;
677 # If version syspref is unavailable, it means Koha is being installed,
678 # and so we must redirect to OPAC maintenance page or to the WebInstaller
679 # also, if OpacMaintenance is ON, OPAC should redirect to maintenance
680 if ( C4::Context->preference('OpacMaintenance') && $type eq 'opac' ) {
681 warn "OPAC Install required, redirecting to maintenance";
682 print $query->redirect("/cgi-bin/koha/maintenance.pl");
683 safe_exit;
685 unless ( $version = C4::Context->preference('Version') ) { # assignment, not comparison
686 if ( $type ne 'opac' ) {
687 warn "Install required, redirecting to Installer";
688 print $query->redirect("/cgi-bin/koha/installer/install.pl");
689 } else {
690 warn "OPAC Install required, redirecting to maintenance";
691 print $query->redirect("/cgi-bin/koha/maintenance.pl");
693 safe_exit;
696 # check that database and koha version are the same
697 # there is no DB version, it's a fresh install,
698 # go to web installer
699 # there is a DB version, compare it to the code version
700 my $kohaversion = Koha::version();
702 # remove the 3 last . to have a Perl number
703 $kohaversion =~ s/(.*\..*)\.(.*)\.(.*)/$1$2$3/;
704 $debug and print STDERR "kohaversion : $kohaversion\n";
705 if ( $version < $kohaversion ) {
706 my $warning = "Database update needed, redirecting to %s. Database is $version and Koha is $kohaversion";
707 if ( $type ne 'opac' ) {
708 warn sprintf( $warning, 'Installer' );
709 print $query->redirect("/cgi-bin/koha/installer/install.pl?step=1&op=updatestructure");
710 } else {
711 warn sprintf( "OPAC: " . $warning, 'maintenance' );
712 print $query->redirect("/cgi-bin/koha/maintenance.pl");
714 safe_exit;
718 sub _session_log {
719 (@_) or return 0;
720 open my $fh, '>>', "/tmp/sessionlog" or warn "ERROR: Cannot append to /tmp/sessionlog";
721 printf $fh join( "\n", @_ );
722 close $fh;
725 sub _timeout_syspref {
726 my $timeout = C4::Context->preference('timeout') || 600;
728 # value in days, convert in seconds
729 if ( $timeout =~ /(\d+)[dD]/ ) {
730 $timeout = $1 * 86400;
732 return $timeout;
735 sub checkauth {
736 my $query = shift;
737 $debug and warn "Checking Auth";
739 # $authnotrequired will be set for scripts which will run without authentication
740 my $authnotrequired = shift;
741 my $flagsrequired = shift;
742 my $type = shift;
743 my $persona = shift;
744 $type = 'opac' unless $type;
746 my $dbh = C4::Context->dbh;
747 my $timeout = _timeout_syspref();
749 _version_check( $type, $query );
751 # state variables
752 my $loggedin = 0;
753 my %info;
754 my ( $userid, $cookie, $sessionID, $flags );
755 my $logout = $query->param('logout.x');
757 my $anon_search_history;
759 # This parameter is the name of the CAS server we want to authenticate against,
760 # when using authentication against multiple CAS servers, as configured in Auth_cas_servers.yaml
761 my $casparam = $query->param('cas');
762 my $q_userid = $query->param('userid') // '';
764 # Basic authentication is incompatible with the use of Shibboleth,
765 # as Shibboleth may return REMOTE_USER as a Shibboleth attribute,
766 # and it may not be the attribute we want to use to match the koha login.
768 # Also, do not consider an empty REMOTE_USER.
770 # Finally, after those tests, we can assume (although if it would be better with
771 # a syspref) that if we get a REMOTE_USER, that's from basic authentication,
772 # and we can affect it to $userid.
773 if ( !$shib and defined( $ENV{'REMOTE_USER'} ) and $ENV{'REMOTE_USER'} ne '' and $userid = $ENV{'REMOTE_USER'} ) {
775 # Using Basic Authentication, no cookies required
776 $cookie = $query->cookie(
777 -name => 'CGISESSID',
778 -value => '',
779 -expires => '',
780 -HttpOnly => 1,
782 $loggedin = 1;
784 elsif ($persona) {
786 # we don't want to set a session because we are being called by a persona callback
788 elsif ( $sessionID = $query->cookie("CGISESSID") )
789 { # assignment, not comparison
790 my $session = get_session($sessionID);
791 C4::Context->_new_userenv($sessionID);
792 my ( $ip, $lasttime, $sessiontype );
793 my $s_userid = '';
794 if ($session) {
795 $s_userid = $session->param('id') // '';
796 C4::Context->set_userenv(
797 $session->param('number'), $s_userid,
798 $session->param('cardnumber'), $session->param('firstname'),
799 $session->param('surname'), $session->param('branch'),
800 $session->param('branchname'), $session->param('flags'),
801 $session->param('emailaddress'), $session->param('branchprinter'),
802 $session->param('persona'), $session->param('shibboleth')
804 C4::Context::set_shelves_userenv( 'bar', $session->param('barshelves') );
805 C4::Context::set_shelves_userenv( 'pub', $session->param('pubshelves') );
806 C4::Context::set_shelves_userenv( 'tot', $session->param('totshelves') );
807 $debug and printf STDERR "AUTH_SESSION: (%s)\t%s %s - %s\n", map { $session->param($_) } qw(cardnumber firstname surname branch);
808 $ip = $session->param('ip');
809 $lasttime = $session->param('lasttime');
810 $userid = $s_userid;
811 $sessiontype = $session->param('sessiontype') || '';
813 if ( ( $query->param('koha_login_context') && ( $q_userid ne $s_userid ) )
814 || ( $cas && $query->param('ticket') && !C4::Context->userenv->{'id'} )
815 || ( $shib && $shib_login && !$logout && !C4::Context->userenv->{'id'} )
818 #if a user enters an id ne to the id in the current session, we need to log them in...
819 #first we need to clear the anonymous session...
820 $debug and warn "query id = $q_userid but session id = $s_userid";
821 $anon_search_history = $session->param('search_history');
822 $session->delete();
823 $session->flush;
824 C4::Context->_unset_userenv($sessionID);
825 $sessionID = undef;
826 $userid = undef;
828 elsif ($logout) {
830 # voluntary logout the user
831 # check wether the user was using their shibboleth session or a local one
832 my $shibSuccess = C4::Context->userenv->{'shibboleth'};
833 $session->delete();
834 $session->flush;
835 C4::Context->_unset_userenv($sessionID);
837 #_session_log(sprintf "%20s from %16s logged out at %30s (manually).\n", $userid,$ip,(strftime "%c",localtime));
838 $sessionID = undef;
839 $userid = undef;
841 if ($cas and $caslogout) {
842 logout_cas($query, $type);
845 # If we are in a shibboleth session (shibboleth is enabled, a shibboleth match attribute is set and matches koha matchpoint)
846 if ( $shib and $shib_login and $shibSuccess and $type eq 'opac' ) {
848 # (Note: $type eq 'opac' condition should be removed when shibboleth authentication for intranet will be implemented)
849 logout_shib($query);
852 elsif ( !$lasttime || ( $lasttime < time() - $timeout ) ) {
854 # timed logout
855 $info{'timed_out'} = 1;
856 if ($session) {
857 $session->delete();
858 $session->flush;
860 C4::Context->_unset_userenv($sessionID);
862 #_session_log(sprintf "%20s from %16s logged out at %30s (inactivity).\n", $userid,$ip,(strftime "%c",localtime));
863 $userid = undef;
864 $sessionID = undef;
866 elsif ( C4::Context->preference('SessionRestrictionByIP') && $ip ne $ENV{'REMOTE_ADDR'} ) {
868 # Different ip than originally logged in from
869 $info{'oldip'} = $ip;
870 $info{'newip'} = $ENV{'REMOTE_ADDR'};
871 $info{'different_ip'} = 1;
872 $session->delete();
873 $session->flush;
874 C4::Context->_unset_userenv($sessionID);
876 #_session_log(sprintf "%20s from %16s logged out at %30s (ip changed to %16s).\n", $userid,$ip,(strftime "%c",localtime), $info{'newip'});
877 $sessionID = undef;
878 $userid = undef;
880 else {
881 $cookie = $query->cookie(
882 -name => 'CGISESSID',
883 -value => $session->id,
884 -HttpOnly => 1
886 $session->param( 'lasttime', time() );
887 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...
888 $flags = haspermission( $userid, $flagsrequired );
889 if ($flags) {
890 $loggedin = 1;
891 } else {
892 $info{'nopermission'} = 1;
897 unless ( $userid || $sessionID ) {
899 #we initiate a session prior to checking for a username to allow for anonymous sessions...
900 my $session = get_session("") or die "Auth ERROR: Cannot get_session()";
902 # Save anonymous search history in new session so it can be retrieved
903 # by get_template_and_user to store it in user's search history after
904 # a successful login.
905 if ($anon_search_history) {
906 $session->param( 'search_history', $anon_search_history );
909 my $sessionID = $session->id;
910 C4::Context->_new_userenv($sessionID);
911 $cookie = $query->cookie(
912 -name => 'CGISESSID',
913 -value => $session->id,
914 -HttpOnly => 1
916 $userid = $q_userid;
917 my $pki_field = C4::Context->preference('AllowPKIAuth');
918 if ( !defined($pki_field) ) {
919 print STDERR "ERROR: Missing system preference AllowPKIAuth.\n";
920 $pki_field = 'None';
922 if ( ( $cas && $query->param('ticket') )
923 || $userid
924 || ( $shib && $shib_login )
925 || $pki_field ne 'None'
926 || $persona )
928 my $password = $query->param('password');
929 my $shibSuccess = 0;
931 my ( $return, $cardnumber );
933 # If shib is enabled and we have a shib login, does the login match a valid koha user
934 if ( $shib && $shib_login && $type eq 'opac' ) {
935 my $retuserid;
937 # Do not pass password here, else shib will not be checked in checkpw.
938 ( $return, $cardnumber, $retuserid ) = checkpw( $dbh, $userid, undef, $query );
939 $userid = $retuserid;
940 $shibSuccess = $return;
941 $info{'invalidShibLogin'} = 1 unless ($return);
944 # If shib login and match were successful, skip further login methods
945 unless ($shibSuccess) {
946 if ( $cas && $query->param('ticket') ) {
947 my $retuserid;
948 ( $return, $cardnumber, $retuserid ) =
949 checkpw( $dbh, $userid, $password, $query, $type );
950 $userid = $retuserid;
951 $info{'invalidCasLogin'} = 1 unless ($return);
954 elsif ($persona) {
955 my $value = $persona;
957 # If we're looking up the email, there's a chance that the person
958 # doesn't have a userid. So if there is none, we pass along the
959 # borrower number, and the bits of code that need to know the user
960 # ID will have to be smart enough to handle that.
961 require C4::Members;
962 my @users_info = C4::Members::GetBorrowersWithEmail($value);
963 if (@users_info) {
965 # First the userid, then the borrowernum
966 $value = $users_info[0][1] || $users_info[0][0];
968 else {
969 undef $value;
971 $return = $value ? 1 : 0;
972 $userid = $value;
975 elsif (
976 ( $pki_field eq 'Common Name' && $ENV{'SSL_CLIENT_S_DN_CN'} )
977 || ( $pki_field eq 'emailAddress'
978 && $ENV{'SSL_CLIENT_S_DN_Email'} )
981 my $value;
982 if ( $pki_field eq 'Common Name' ) {
983 $value = $ENV{'SSL_CLIENT_S_DN_CN'};
985 elsif ( $pki_field eq 'emailAddress' ) {
986 $value = $ENV{'SSL_CLIENT_S_DN_Email'};
988 # If we're looking up the email, there's a chance that the person
989 # doesn't have a userid. So if there is none, we pass along the
990 # borrower number, and the bits of code that need to know the user
991 # ID will have to be smart enough to handle that.
992 require C4::Members;
993 my @users_info = C4::Members::GetBorrowersWithEmail($value);
994 if (@users_info) {
996 # First the userid, then the borrowernum
997 $value = $users_info[0][1] || $users_info[0][0];
998 } else {
999 undef $value;
1003 $return = $value ? 1 : 0;
1004 $userid = $value;
1007 else {
1008 my $retuserid;
1009 ( $return, $cardnumber, $retuserid ) =
1010 checkpw( $dbh, $userid, $password, $query, $type );
1011 $userid = $retuserid if ($retuserid);
1012 $info{'invalid_username_or_password'} = 1 unless ($return);
1016 # $return: 1 = valid user, 2 = superlibrarian
1017 if ($return) {
1019 #_session_log(sprintf "%20s from %16s logged in at %30s.\n", $userid,$ENV{'REMOTE_ADDR'},(strftime '%c', localtime));
1020 if ( $flags = haspermission( $userid, $flagsrequired ) ) {
1021 $loggedin = 1;
1023 else {
1024 $info{'nopermission'} = 1;
1025 C4::Context->_unset_userenv($sessionID);
1027 my ( $borrowernumber, $firstname, $surname, $userflags,
1028 $branchcode, $branchname, $branchprinter, $emailaddress );
1030 if ( $return == 1 ) {
1031 my $select = "
1032 SELECT borrowernumber, firstname, surname, flags, borrowers.branchcode,
1033 branches.branchname as branchname,
1034 branches.branchprinter as branchprinter,
1035 email
1036 FROM borrowers
1037 LEFT JOIN branches on borrowers.branchcode=branches.branchcode
1039 my $sth = $dbh->prepare("$select where userid=?");
1040 $sth->execute($userid);
1041 unless ( $sth->rows ) {
1042 $debug and print STDERR "AUTH_1: no rows for userid='$userid'\n";
1043 $sth = $dbh->prepare("$select where cardnumber=?");
1044 $sth->execute($cardnumber);
1046 unless ( $sth->rows ) {
1047 $debug and print STDERR "AUTH_2a: no rows for cardnumber='$cardnumber'\n";
1048 $sth->execute($userid);
1049 unless ( $sth->rows ) {
1050 $debug and print STDERR "AUTH_2b: no rows for userid='$userid' AS cardnumber\n";
1054 if ( $sth->rows ) {
1055 ( $borrowernumber, $firstname, $surname, $userflags,
1056 $branchcode, $branchname, $branchprinter, $emailaddress ) = $sth->fetchrow;
1057 $debug and print STDERR "AUTH_3 results: " .
1058 "$cardnumber,$borrowernumber,$userid,$firstname,$surname,$userflags,$branchcode,$emailaddress\n";
1059 } else {
1060 print STDERR "AUTH_3: no results for userid='$userid', cardnumber='$cardnumber'.\n";
1063 # launch a sequence to check if we have a ip for the branch, i
1064 # if we have one we replace the branchcode of the userenv by the branch bound in the ip.
1066 my $ip = $ENV{'REMOTE_ADDR'};
1068 # if they specify at login, use that
1069 if ( $query->param('branch') ) {
1070 $branchcode = $query->param('branch');
1071 $branchname = GetBranchName($branchcode);
1073 my $branches = GetBranches();
1074 if ( C4::Context->boolean_preference('IndependentBranches') && C4::Context->boolean_preference('Autolocation') ) {
1076 # we have to check they are coming from the right ip range
1077 my $domain = $branches->{$branchcode}->{'branchip'};
1078 if ( $ip !~ /^$domain/ ) {
1079 $loggedin = 0;
1080 $info{'wrongip'} = 1;
1084 my @branchesloop;
1085 foreach my $br ( keys %$branches ) {
1087 # now we work with the treatment of ip
1088 my $domain = $branches->{$br}->{'branchip'};
1089 if ( $domain && $ip =~ /^$domain/ ) {
1090 $branchcode = $branches->{$br}->{'branchcode'};
1092 # new op dev : add the branchprinter and branchname in the cookie
1093 $branchprinter = $branches->{$br}->{'branchprinter'};
1094 $branchname = $branches->{$br}->{'branchname'};
1097 $session->param( 'number', $borrowernumber );
1098 $session->param( 'id', $userid );
1099 $session->param( 'cardnumber', $cardnumber );
1100 $session->param( 'firstname', $firstname );
1101 $session->param( 'surname', $surname );
1102 $session->param( 'branch', $branchcode );
1103 $session->param( 'branchname', $branchname );
1104 $session->param( 'flags', $userflags );
1105 $session->param( 'emailaddress', $emailaddress );
1106 $session->param( 'ip', $session->remote_addr() );
1107 $session->param( 'lasttime', time() );
1108 $session->param( 'shibboleth', $shibSuccess );
1109 $debug and printf STDERR "AUTH_4: (%s)\t%s %s - %s\n", map { $session->param($_) } qw(cardnumber firstname surname branch);
1111 elsif ( $return == 2 ) {
1113 #We suppose the user is the superlibrarian
1114 $borrowernumber = 0;
1115 $session->param( 'number', 0 );
1116 $session->param( 'id', C4::Context->config('user') );
1117 $session->param( 'cardnumber', C4::Context->config('user') );
1118 $session->param( 'firstname', C4::Context->config('user') );
1119 $session->param( 'surname', C4::Context->config('user') );
1120 $session->param( 'branch', 'NO_LIBRARY_SET' );
1121 $session->param( 'branchname', 'NO_LIBRARY_SET' );
1122 $session->param( 'flags', 1 );
1123 $session->param( 'emailaddress', C4::Context->preference('KohaAdminEmailAddress') );
1124 $session->param( 'ip', $session->remote_addr() );
1125 $session->param( 'lasttime', time() );
1127 if ($persona) {
1128 $session->param( 'persona', 1 );
1130 C4::Context->set_userenv(
1131 $session->param('number'), $session->param('id'),
1132 $session->param('cardnumber'), $session->param('firstname'),
1133 $session->param('surname'), $session->param('branch'),
1134 $session->param('branchname'), $session->param('flags'),
1135 $session->param('emailaddress'), $session->param('branchprinter'),
1136 $session->param('persona'), $session->param('shibboleth')
1140 # $return: 0 = invalid user
1141 # reset to anonymous session
1142 else {
1143 $debug and warn "Login failed, resetting anonymous session...";
1144 if ($userid) {
1145 $info{'invalid_username_or_password'} = 1;
1146 C4::Context->_unset_userenv($sessionID);
1148 $session->param( 'lasttime', time() );
1149 $session->param( 'ip', $session->remote_addr() );
1150 $session->param( 'sessiontype', 'anon' );
1152 } # END if ( $userid = $query->param('userid') )
1153 elsif ( $type eq "opac" ) {
1155 # if we are here this is an anonymous session; add public lists to it and a few other items...
1156 # anonymous sessions are created only for the OPAC
1157 $debug and warn "Initiating an anonymous session...";
1159 # setting a couple of other session vars...
1160 $session->param( 'ip', $session->remote_addr() );
1161 $session->param( 'lasttime', time() );
1162 $session->param( 'sessiontype', 'anon' );
1164 } # END unless ($userid)
1166 # finished authentification, now respond
1167 if ( $loggedin || $authnotrequired )
1169 # successful login
1170 unless ($cookie) {
1171 $cookie = $query->cookie(
1172 -name => 'CGISESSID',
1173 -value => '',
1174 -HttpOnly => 1
1177 return ( $userid, $cookie, $sessionID, $flags );
1182 # AUTH rejected, show the login/password template, after checking the DB.
1186 # get the inputs from the incoming query
1187 my @inputs = ();
1188 foreach my $name ( param $query) {
1189 (next) if ( $name eq 'userid' || $name eq 'password' || $name eq 'ticket' );
1190 my $value = $query->param($name);
1191 push @inputs, { name => $name, value => $value };
1194 my $LibraryNameTitle = C4::Context->preference("LibraryName");
1195 $LibraryNameTitle =~ s/<(?:\/?)(?:br|p)\s*(?:\/?)>/ /sgi;
1196 $LibraryNameTitle =~ s/<(?:[^<>'"]|'(?:[^']*)'|"(?:[^"]*)")*>//sg;
1198 my $template_name = ( $type eq 'opac' ) ? 'opac-auth.tt' : 'auth.tt';
1199 my $template = C4::Templates::gettemplate( $template_name, $type, $query );
1200 $template->param(
1201 branchloop => GetBranchesLoop(),
1202 OpacAdditionalStylesheet => C4::Context->preference("OpacAdditionalStylesheet"),
1203 opaclayoutstylesheet => C4::Context->preference("opaclayoutstylesheet"),
1204 login => 1,
1205 INPUTS => \@inputs,
1206 casAuthentication => C4::Context->preference("casAuthentication"),
1207 shibbolethAuthentication => $shib,
1208 SessionRestrictionByIP => C4::Context->preference("SessionRestrictionByIP"),
1209 suggestion => C4::Context->preference("suggestion"),
1210 virtualshelves => C4::Context->preference("virtualshelves"),
1211 LibraryName => "" . C4::Context->preference("LibraryName"),
1212 LibraryNameTitle => "" . $LibraryNameTitle,
1213 opacuserlogin => C4::Context->preference("opacuserlogin"),
1214 OpacNav => C4::Context->preference("OpacNav"),
1215 OpacNavRight => C4::Context->preference("OpacNavRight"),
1216 OpacNavBottom => C4::Context->preference("OpacNavBottom"),
1217 opaccredits => C4::Context->preference("opaccredits"),
1218 OpacFavicon => C4::Context->preference("OpacFavicon"),
1219 opacreadinghistory => C4::Context->preference("opacreadinghistory"),
1220 opaclanguagesdisplay => C4::Context->preference("opaclanguagesdisplay"),
1221 OPACUserJS => C4::Context->preference("OPACUserJS"),
1222 opacbookbag => "" . C4::Context->preference("opacbookbag"),
1223 OpacCloud => C4::Context->preference("OpacCloud"),
1224 OpacTopissue => C4::Context->preference("OpacTopissue"),
1225 OpacAuthorities => C4::Context->preference("OpacAuthorities"),
1226 OpacBrowser => C4::Context->preference("OpacBrowser"),
1227 opacheader => C4::Context->preference("opacheader"),
1228 TagsEnabled => C4::Context->preference("TagsEnabled"),
1229 OPACUserCSS => C4::Context->preference("OPACUserCSS"),
1230 intranetcolorstylesheet => C4::Context->preference("intranetcolorstylesheet"),
1231 intranetstylesheet => C4::Context->preference("intranetstylesheet"),
1232 intranetbookbag => C4::Context->preference("intranetbookbag"),
1233 IntranetNav => C4::Context->preference("IntranetNav"),
1234 IntranetFavicon => C4::Context->preference("IntranetFavicon"),
1235 IntranetUserCSS => C4::Context->preference("IntranetUserCSS"),
1236 IntranetUserJS => C4::Context->preference("IntranetUserJS"),
1237 IndependentBranches => C4::Context->preference("IndependentBranches"),
1238 AutoLocation => C4::Context->preference("AutoLocation"),
1239 wrongip => $info{'wrongip'},
1240 PatronSelfRegistration => C4::Context->preference("PatronSelfRegistration"),
1241 PatronSelfRegistrationDefaultCategory => C4::Context->preference("PatronSelfRegistrationDefaultCategory"),
1242 persona => C4::Context->preference("Persona"),
1243 opac_css_override => $ENV{'OPAC_CSS_OVERRIDE'},
1246 $template->param( OpacPublic => C4::Context->preference("OpacPublic") );
1247 $template->param( loginprompt => 1 ) unless $info{'nopermission'};
1249 if ( $type eq 'opac' ) {
1250 require Koha::Virtualshelves;
1251 my $some_public_shelves = Koha::Virtualshelves->get_some_shelves(
1253 category => 2,
1256 $template->param(
1257 some_public_shelves => $some_public_shelves,
1261 if ($cas) {
1263 # Is authentication against multiple CAS servers enabled?
1264 if ( C4::Auth_with_cas::multipleAuth && !$casparam ) {
1265 my $casservers = C4::Auth_with_cas::getMultipleAuth();
1266 my @tmplservers;
1267 foreach my $key ( keys %$casservers ) {
1268 push @tmplservers, { name => $key, value => login_cas_url( $query, $key, $type ) . "?cas=$key" };
1270 $template->param(
1271 casServersLoop => \@tmplservers
1273 } else {
1274 $template->param(
1275 casServerUrl => login_cas_url($query, undef, $type),
1279 $template->param(
1280 invalidCasLogin => $info{'invalidCasLogin'}
1284 if ($shib) {
1285 $template->param(
1286 shibbolethAuthentication => $shib,
1287 shibbolethLoginUrl => login_shib_url($query),
1291 $template->param(
1292 LibraryName => C4::Context->preference("LibraryName"),
1294 $template->param(%info);
1296 # $cookie = $query->cookie(CGISESSID => $session->id
1297 # );
1298 print $query->header(
1299 -type => 'text/html',
1300 -charset => 'utf-8',
1301 -cookie => $cookie
1303 $template->output;
1304 safe_exit;
1307 =head2 check_api_auth
1309 ($status, $cookie, $sessionId) = check_api_auth($query, $userflags);
1311 Given a CGI query containing the parameters 'userid' and 'password' and/or a session
1312 cookie, determine if the user has the privileges specified by C<$userflags>.
1314 C<check_api_auth> is is meant for authenticating users of web services, and
1315 consequently will always return and will not attempt to redirect the user
1316 agent.
1318 If a valid session cookie is already present, check_api_auth will return a status
1319 of "ok", the cookie, and the Koha session ID.
1321 If no session cookie is present, check_api_auth will check the 'userid' and 'password
1322 parameters and create a session cookie and Koha session if the supplied credentials
1323 are OK.
1325 Possible return values in C<$status> are:
1327 =over
1329 =item "ok" -- user authenticated; C<$cookie> and C<$sessionid> have valid values.
1331 =item "failed" -- credentials are not correct; C<$cookie> and C<$sessionid> are undef
1333 =item "maintenance" -- DB is in maintenance mode; no login possible at the moment
1335 =item "expired -- session cookie has expired; API user should resubmit userid and password
1337 =back
1339 =cut
1341 sub check_api_auth {
1342 my $query = shift;
1343 my $flagsrequired = shift;
1345 my $dbh = C4::Context->dbh;
1346 my $timeout = _timeout_syspref();
1348 unless ( C4::Context->preference('Version') ) {
1350 # database has not been installed yet
1351 return ( "maintenance", undef, undef );
1353 my $kohaversion = Koha::version();
1354 $kohaversion =~ s/(.*\..*)\.(.*)\.(.*)/$1$2$3/;
1355 if ( C4::Context->preference('Version') < $kohaversion ) {
1357 # database in need of version update; assume that
1358 # no API should be called while databsae is in
1359 # this condition.
1360 return ( "maintenance", undef, undef );
1363 # FIXME -- most of what follows is a copy-and-paste
1364 # of code from checkauth. There is an obvious need
1365 # for refactoring to separate the various parts of
1366 # the authentication code, but as of 2007-11-19 this
1367 # is deferred so as to not introduce bugs into the
1368 # regular authentication code for Koha 3.0.
1370 # see if we have a valid session cookie already
1371 # however, if a userid parameter is present (i.e., from
1372 # a form submission, assume that any current cookie
1373 # is to be ignored
1374 my $sessionID = undef;
1375 unless ( $query->param('userid') ) {
1376 $sessionID = $query->cookie("CGISESSID");
1378 if ( $sessionID && not( $cas && $query->param('PT') ) ) {
1379 my $session = get_session($sessionID);
1380 C4::Context->_new_userenv($sessionID);
1381 if ($session) {
1382 C4::Context->set_userenv(
1383 $session->param('number'), $session->param('id'),
1384 $session->param('cardnumber'), $session->param('firstname'),
1385 $session->param('surname'), $session->param('branch'),
1386 $session->param('branchname'), $session->param('flags'),
1387 $session->param('emailaddress'), $session->param('branchprinter')
1390 my $ip = $session->param('ip');
1391 my $lasttime = $session->param('lasttime');
1392 my $userid = $session->param('id');
1393 if ( $lasttime < time() - $timeout ) {
1395 # time out
1396 $session->delete();
1397 $session->flush;
1398 C4::Context->_unset_userenv($sessionID);
1399 $userid = undef;
1400 $sessionID = undef;
1401 return ( "expired", undef, undef );
1402 } elsif ( C4::Context->preference('SessionRestrictionByIP') && $ip ne $ENV{'REMOTE_ADDR'} ) {
1404 # IP address changed
1405 $session->delete();
1406 $session->flush;
1407 C4::Context->_unset_userenv($sessionID);
1408 $userid = undef;
1409 $sessionID = undef;
1410 return ( "expired", undef, undef );
1411 } else {
1412 my $cookie = $query->cookie(
1413 -name => 'CGISESSID',
1414 -value => $session->id,
1415 -HttpOnly => 1,
1417 $session->param( 'lasttime', time() );
1418 my $flags = haspermission( $userid, $flagsrequired );
1419 if ($flags) {
1420 return ( "ok", $cookie, $sessionID );
1421 } else {
1422 $session->delete();
1423 $session->flush;
1424 C4::Context->_unset_userenv($sessionID);
1425 $userid = undef;
1426 $sessionID = undef;
1427 return ( "failed", undef, undef );
1430 } else {
1431 return ( "expired", undef, undef );
1433 } else {
1435 # new login
1436 my $userid = $query->param('userid');
1437 my $password = $query->param('password');
1438 my ( $return, $cardnumber );
1440 # Proxy CAS auth
1441 if ( $cas && $query->param('PT') ) {
1442 my $retuserid;
1443 $debug and print STDERR "## check_api_auth - checking CAS\n";
1445 # In case of a CAS authentication, we use the ticket instead of the password
1446 my $PT = $query->param('PT');
1447 ( $return, $cardnumber, $userid ) = check_api_auth_cas( $dbh, $PT, $query ); # EXTERNAL AUTH
1448 } else {
1450 # User / password auth
1451 unless ( $userid and $password ) {
1453 # caller did something wrong, fail the authenticateion
1454 return ( "failed", undef, undef );
1456 ( $return, $cardnumber ) = checkpw( $dbh, $userid, $password, $query );
1459 if ( $return and haspermission( $userid, $flagsrequired ) ) {
1460 my $session = get_session("");
1461 return ( "failed", undef, undef ) unless $session;
1463 my $sessionID = $session->id;
1464 C4::Context->_new_userenv($sessionID);
1465 my $cookie = $query->cookie(
1466 -name => 'CGISESSID',
1467 -value => $sessionID,
1468 -HttpOnly => 1,
1470 if ( $return == 1 ) {
1471 my (
1472 $borrowernumber, $firstname, $surname,
1473 $userflags, $branchcode, $branchname,
1474 $branchprinter, $emailaddress
1476 my $sth =
1477 $dbh->prepare(
1478 "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=?"
1480 $sth->execute($userid);
1482 $borrowernumber, $firstname, $surname,
1483 $userflags, $branchcode, $branchname,
1484 $branchprinter, $emailaddress
1485 ) = $sth->fetchrow if ( $sth->rows );
1487 unless ( $sth->rows ) {
1488 my $sth = $dbh->prepare(
1489 "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=?"
1491 $sth->execute($cardnumber);
1493 $borrowernumber, $firstname, $surname,
1494 $userflags, $branchcode, $branchname,
1495 $branchprinter, $emailaddress
1496 ) = $sth->fetchrow if ( $sth->rows );
1498 unless ( $sth->rows ) {
1499 $sth->execute($userid);
1501 $borrowernumber, $firstname, $surname, $userflags,
1502 $branchcode, $branchname, $branchprinter, $emailaddress
1503 ) = $sth->fetchrow if ( $sth->rows );
1507 my $ip = $ENV{'REMOTE_ADDR'};
1509 # if they specify at login, use that
1510 if ( $query->param('branch') ) {
1511 $branchcode = $query->param('branch');
1512 $branchname = GetBranchName($branchcode);
1514 my $branches = GetBranches();
1515 my @branchesloop;
1516 foreach my $br ( keys %$branches ) {
1518 # now we work with the treatment of ip
1519 my $domain = $branches->{$br}->{'branchip'};
1520 if ( $domain && $ip =~ /^$domain/ ) {
1521 $branchcode = $branches->{$br}->{'branchcode'};
1523 # new op dev : add the branchprinter and branchname in the cookie
1524 $branchprinter = $branches->{$br}->{'branchprinter'};
1525 $branchname = $branches->{$br}->{'branchname'};
1528 $session->param( 'number', $borrowernumber );
1529 $session->param( 'id', $userid );
1530 $session->param( 'cardnumber', $cardnumber );
1531 $session->param( 'firstname', $firstname );
1532 $session->param( 'surname', $surname );
1533 $session->param( 'branch', $branchcode );
1534 $session->param( 'branchname', $branchname );
1535 $session->param( 'flags', $userflags );
1536 $session->param( 'emailaddress', $emailaddress );
1537 $session->param( 'ip', $session->remote_addr() );
1538 $session->param( 'lasttime', time() );
1539 } elsif ( $return == 2 ) {
1541 #We suppose the user is the superlibrarian
1542 $session->param( 'number', 0 );
1543 $session->param( 'id', C4::Context->config('user') );
1544 $session->param( 'cardnumber', C4::Context->config('user') );
1545 $session->param( 'firstname', C4::Context->config('user') );
1546 $session->param( 'surname', C4::Context->config('user') );
1547 $session->param( 'branch', 'NO_LIBRARY_SET' );
1548 $session->param( 'branchname', 'NO_LIBRARY_SET' );
1549 $session->param( 'flags', 1 );
1550 $session->param( 'emailaddress', C4::Context->preference('KohaAdminEmailAddress') );
1551 $session->param( 'ip', $session->remote_addr() );
1552 $session->param( 'lasttime', time() );
1554 C4::Context->set_userenv(
1555 $session->param('number'), $session->param('id'),
1556 $session->param('cardnumber'), $session->param('firstname'),
1557 $session->param('surname'), $session->param('branch'),
1558 $session->param('branchname'), $session->param('flags'),
1559 $session->param('emailaddress'), $session->param('branchprinter')
1561 return ( "ok", $cookie, $sessionID );
1562 } else {
1563 return ( "failed", undef, undef );
1568 =head2 check_cookie_auth
1570 ($status, $sessionId) = check_api_auth($cookie, $userflags);
1572 Given a CGISESSID cookie set during a previous login to Koha, determine
1573 if the user has the privileges specified by C<$userflags>.
1575 C<check_cookie_auth> is meant for authenticating special services
1576 such as tools/upload-file.pl that are invoked by other pages that
1577 have been authenticated in the usual way.
1579 Possible return values in C<$status> are:
1581 =over
1583 =item "ok" -- user authenticated; C<$sessionID> have valid values.
1585 =item "failed" -- credentials are not correct; C<$sessionid> are undef
1587 =item "maintenance" -- DB is in maintenance mode; no login possible at the moment
1589 =item "expired -- session cookie has expired; API user should resubmit userid and password
1591 =back
1593 =cut
1595 sub check_cookie_auth {
1596 my $cookie = shift;
1597 my $flagsrequired = shift;
1599 my $dbh = C4::Context->dbh;
1600 my $timeout = _timeout_syspref();
1602 unless ( C4::Context->preference('Version') ) {
1604 # database has not been installed yet
1605 return ( "maintenance", undef );
1607 my $kohaversion = Koha::version();
1608 $kohaversion =~ s/(.*\..*)\.(.*)\.(.*)/$1$2$3/;
1609 if ( C4::Context->preference('Version') < $kohaversion ) {
1611 # database in need of version update; assume that
1612 # no API should be called while databsae is in
1613 # this condition.
1614 return ( "maintenance", undef );
1617 # FIXME -- most of what follows is a copy-and-paste
1618 # of code from checkauth. There is an obvious need
1619 # for refactoring to separate the various parts of
1620 # the authentication code, but as of 2007-11-23 this
1621 # is deferred so as to not introduce bugs into the
1622 # regular authentication code for Koha 3.0.
1624 # see if we have a valid session cookie already
1625 # however, if a userid parameter is present (i.e., from
1626 # a form submission, assume that any current cookie
1627 # is to be ignored
1628 unless ( defined $cookie and $cookie ) {
1629 return ( "failed", undef );
1631 my $sessionID = $cookie;
1632 my $session = get_session($sessionID);
1633 C4::Context->_new_userenv($sessionID);
1634 if ($session) {
1635 C4::Context->set_userenv(
1636 $session->param('number'), $session->param('id'),
1637 $session->param('cardnumber'), $session->param('firstname'),
1638 $session->param('surname'), $session->param('branch'),
1639 $session->param('branchname'), $session->param('flags'),
1640 $session->param('emailaddress'), $session->param('branchprinter')
1643 my $ip = $session->param('ip');
1644 my $lasttime = $session->param('lasttime');
1645 my $userid = $session->param('id');
1646 if ( $lasttime < time() - $timeout ) {
1648 # time out
1649 $session->delete();
1650 $session->flush;
1651 C4::Context->_unset_userenv($sessionID);
1652 $userid = undef;
1653 $sessionID = undef;
1654 return ("expired", undef);
1655 } elsif ( C4::Context->preference('SessionRestrictionByIP') && $ip ne $ENV{'REMOTE_ADDR'} ) {
1657 # IP address changed
1658 $session->delete();
1659 $session->flush;
1660 C4::Context->_unset_userenv($sessionID);
1661 $userid = undef;
1662 $sessionID = undef;
1663 return ( "expired", undef );
1664 } else {
1665 $session->param( 'lasttime', time() );
1666 my $flags = haspermission( $userid, $flagsrequired );
1667 if ($flags) {
1668 return ( "ok", $sessionID );
1669 } else {
1670 $session->delete();
1671 $session->flush;
1672 C4::Context->_unset_userenv($sessionID);
1673 $userid = undef;
1674 $sessionID = undef;
1675 return ( "failed", undef );
1678 } else {
1679 return ( "expired", undef );
1683 =head2 get_session
1685 use CGI::Session;
1686 my $session = get_session($sessionID);
1688 Given a session ID, retrieve the CGI::Session object used to store
1689 the session's state. The session object can be used to store
1690 data that needs to be accessed by different scripts during a
1691 user's session.
1693 If the C<$sessionID> parameter is an empty string, a new session
1694 will be created.
1696 =cut
1698 sub get_session {
1699 my $sessionID = shift;
1700 my $storage_method = C4::Context->preference('SessionStorage');
1701 my $dbh = C4::Context->dbh;
1702 my $session;
1703 if ( $storage_method eq 'mysql' ) {
1704 $session = new CGI::Session( "driver:MySQL;serializer:yaml;id:md5", $sessionID, { Handle => $dbh } );
1706 elsif ( $storage_method eq 'Pg' ) {
1707 $session = new CGI::Session( "driver:PostgreSQL;serializer:yaml;id:md5", $sessionID, { Handle => $dbh } );
1709 elsif ( $storage_method eq 'memcached' && C4::Context->ismemcached ) {
1710 $session = new CGI::Session( "driver:memcached;serializer:yaml;id:md5", $sessionID, { Memcached => C4::Context->memcached } );
1712 else {
1713 # catch all defaults to tmp should work on all systems
1714 my $dir = File::Spec->tmpdir;
1715 my $instance = C4::Context->config( 'database' ); #actually for packages not exactly the instance name, but generally safer to leave it as it is
1716 $session = new CGI::Session( "driver:File;serializer:yaml;id:md5", $sessionID, { Directory => "$dir/cgisess_$instance" } );
1718 return $session;
1721 sub checkpw {
1722 my ( $dbh, $userid, $password, $query, $type ) = @_;
1723 $type = 'opac' unless $type;
1724 if ($ldap) {
1725 $debug and print STDERR "## checkpw - checking LDAP\n";
1726 my ( $retval, $retcard, $retuserid ) = checkpw_ldap(@_); # EXTERNAL AUTH
1727 return 0 if $retval == -1; # Incorrect password for LDAP login attempt
1728 ($retval) and return ( $retval, $retcard, $retuserid );
1731 if ( $cas && $query && $query->param('ticket') ) {
1732 $debug and print STDERR "## checkpw - checking CAS\n";
1734 # In case of a CAS authentication, we use the ticket instead of the password
1735 my $ticket = $query->param('ticket');
1736 $query->delete('ticket'); # remove ticket to come back to original URL
1737 my ( $retval, $retcard, $retuserid ) = checkpw_cas( $dbh, $ticket, $query, $type ); # EXTERNAL AUTH
1738 ($retval) and return ( $retval, $retcard, $retuserid );
1739 return 0;
1742 # If we are in a shibboleth session (shibboleth is enabled, and a shibboleth match attribute is present)
1743 # Check for password to asertain whether we want to be testing against shibboleth or another method this
1744 # time around.
1745 if ( $shib && $shib_login && !$password ) {
1747 $debug and print STDERR "## checkpw - checking Shibboleth\n";
1749 # In case of a Shibboleth authentication, we expect a shibboleth user attribute
1750 # (defined under shibboleth mapping in koha-conf.xml) to contain the login of the
1751 # shibboleth-authenticated user
1753 # Then, we check if it matches a valid koha user
1754 if ($shib_login) {
1755 my ( $retval, $retcard, $retuserid ) = C4::Auth_with_shibboleth::checkpw_shib($shib_login); # EXTERNAL AUTH
1756 ($retval) and return ( $retval, $retcard, $retuserid );
1757 return 0;
1761 # INTERNAL AUTH
1762 return checkpw_internal(@_)
1765 sub checkpw_internal {
1766 my ( $dbh, $userid, $password ) = @_;
1768 $password = Encode::encode( 'UTF-8', $password )
1769 if Encode::is_utf8($password);
1771 if ( $userid && $userid eq C4::Context->config('user') ) {
1772 if ( $password && $password eq C4::Context->config('pass') ) {
1774 # Koha superuser account
1775 # C4::Context->set_userenv(0,0,C4::Context->config('user'),C4::Context->config('user'),C4::Context->config('user'),"",1);
1776 return 2;
1778 else {
1779 return 0;
1783 my $sth =
1784 $dbh->prepare(
1785 "select password,cardnumber,borrowernumber,userid,firstname,surname,borrowers.branchcode,branches.branchname,flags from borrowers join branches on borrowers.branchcode=branches.branchcode where userid=?"
1787 $sth->execute($userid);
1788 if ( $sth->rows ) {
1789 my ( $stored_hash, $cardnumber, $borrowernumber, $userid, $firstname,
1790 $surname, $branchcode, $branchname, $flags )
1791 = $sth->fetchrow;
1793 if ( checkpw_hash( $password, $stored_hash ) ) {
1795 C4::Context->set_userenv( "$borrowernumber", $userid, $cardnumber,
1796 $firstname, $surname, $branchcode, $branchname, $flags );
1797 return 1, $cardnumber, $userid;
1800 $sth =
1801 $dbh->prepare(
1802 "select password,cardnumber,borrowernumber,userid,firstname,surname,borrowers.branchcode,branches.branchname,flags from borrowers join branches on borrowers.branchcode=branches.branchcode where cardnumber=?"
1804 $sth->execute($userid);
1805 if ( $sth->rows ) {
1806 my ( $stored_hash, $cardnumber, $borrowernumber, $userid, $firstname,
1807 $surname, $branchcode, $branchname, $flags )
1808 = $sth->fetchrow;
1810 if ( checkpw_hash( $password, $stored_hash ) ) {
1812 C4::Context->set_userenv( $borrowernumber, $userid, $cardnumber,
1813 $firstname, $surname, $branchcode, $branchname, $flags );
1814 return 1, $cardnumber, $userid;
1817 if ( $userid && $userid eq 'demo'
1818 && "$password" eq 'demo'
1819 && C4::Context->config('demo') )
1822 # DEMO => the demo user is allowed to do everything (if demo set to 1 in koha.conf
1823 # some features won't be effective : modify systempref, modify MARC structure,
1824 return 2;
1826 return 0;
1829 sub checkpw_hash {
1830 my ( $password, $stored_hash ) = @_;
1832 return if $stored_hash eq '!';
1834 # check what encryption algorithm was implemented: Bcrypt - if the hash starts with '$2' it is Bcrypt else md5
1835 my $hash;
1836 if ( substr( $stored_hash, 0, 2 ) eq '$2' ) {
1837 $hash = hash_password( $password, $stored_hash );
1838 } else {
1839 $hash = md5_base64($password);
1841 return $hash eq $stored_hash;
1844 =head2 getuserflags
1846 my $authflags = getuserflags($flags, $userid, [$dbh]);
1848 Translates integer flags into permissions strings hash.
1850 C<$flags> is the integer userflags value ( borrowers.userflags )
1851 C<$userid> is the members.userid, used for building subpermissions
1852 C<$authflags> is a hashref of permissions
1854 =cut
1856 sub getuserflags {
1857 my $flags = shift;
1858 my $userid = shift;
1859 my $dbh = @_ ? shift : C4::Context->dbh;
1860 my $userflags;
1862 # I don't want to do this, but if someone logs in as the database
1863 # user, it would be preferable not to spam them to death with
1864 # numeric warnings. So, we make $flags numeric.
1865 no warnings 'numeric';
1866 $flags += 0;
1868 my $sth = $dbh->prepare("SELECT bit, flag, defaulton FROM userflags");
1869 $sth->execute;
1871 while ( my ( $bit, $flag, $defaulton ) = $sth->fetchrow ) {
1872 if ( ( $flags & ( 2**$bit ) ) || $defaulton ) {
1873 $userflags->{$flag} = 1;
1875 else {
1876 $userflags->{$flag} = 0;
1880 # get subpermissions and merge with top-level permissions
1881 my $user_subperms = get_user_subpermissions($userid);
1882 foreach my $module ( keys %$user_subperms ) {
1883 next if $userflags->{$module} == 1; # user already has permission for everything in this module
1884 $userflags->{$module} = $user_subperms->{$module};
1887 return $userflags;
1890 =head2 get_user_subpermissions
1892 $user_perm_hashref = get_user_subpermissions($userid);
1894 Given the userid (note, not the borrowernumber) of a staff user,
1895 return a hashref of hashrefs of the specific subpermissions
1896 accorded to the user. An example return is
1899 tools => {
1900 export_catalog => 1,
1901 import_patrons => 1,
1905 The top-level hash-key is a module or function code from
1906 userflags.flag, while the second-level key is a code
1907 from permissions.
1909 The results of this function do not give a complete picture
1910 of the functions that a staff user can access; it is also
1911 necessary to check borrowers.flags.
1913 =cut
1915 sub get_user_subpermissions {
1916 my $userid = shift;
1918 my $dbh = C4::Context->dbh;
1919 my $sth = $dbh->prepare( "SELECT flag, user_permissions.code
1920 FROM user_permissions
1921 JOIN permissions USING (module_bit, code)
1922 JOIN userflags ON (module_bit = bit)
1923 JOIN borrowers USING (borrowernumber)
1924 WHERE userid = ?" );
1925 $sth->execute($userid);
1927 my $user_perms = {};
1928 while ( my $perm = $sth->fetchrow_hashref ) {
1929 $user_perms->{ $perm->{'flag'} }->{ $perm->{'code'} } = 1;
1931 return $user_perms;
1934 =head2 get_all_subpermissions
1936 my $perm_hashref = get_all_subpermissions();
1938 Returns a hashref of hashrefs defining all specific
1939 permissions currently defined. The return value
1940 has the same structure as that of C<get_user_subpermissions>,
1941 except that the innermost hash value is the description
1942 of the subpermission.
1944 =cut
1946 sub get_all_subpermissions {
1947 my $dbh = C4::Context->dbh;
1948 my $sth = $dbh->prepare( "SELECT flag, code
1949 FROM permissions
1950 JOIN userflags ON (module_bit = bit)" );
1951 $sth->execute();
1953 my $all_perms = {};
1954 while ( my $perm = $sth->fetchrow_hashref ) {
1955 $all_perms->{ $perm->{'flag'} }->{ $perm->{'code'} } = 1;
1957 return $all_perms;
1960 =head2 haspermission
1962 $flags = ($userid, $flagsrequired);
1964 C<$userid> the userid of the member
1965 C<$flags> is a hashref of required flags like C<$borrower-&lt;{authflags}>
1967 Returns member's flags or 0 if a permission is not met.
1969 =cut
1971 sub haspermission {
1972 my ( $userid, $flagsrequired ) = @_;
1973 my $sth = C4::Context->dbh->prepare("SELECT flags FROM borrowers WHERE userid=?");
1974 $sth->execute($userid);
1975 my $row = $sth->fetchrow();
1976 my $flags = getuserflags( $row, $userid );
1977 if ( $userid eq C4::Context->config('user') ) {
1979 # Super User Account from /etc/koha.conf
1980 $flags->{'superlibrarian'} = 1;
1982 elsif ( $userid eq 'demo' && C4::Context->config('demo') ) {
1984 # Demo user that can do "anything" (demo=1 in /etc/koha.conf)
1985 $flags->{'superlibrarian'} = 1;
1988 return $flags if $flags->{superlibrarian};
1990 foreach my $module ( keys %$flagsrequired ) {
1991 my $subperm = $flagsrequired->{$module};
1992 if ( $subperm eq '*' ) {
1993 return 0 unless ( $flags->{$module} == 1 or ref( $flags->{$module} ) );
1994 } else {
1995 return 0 unless (
1996 ( defined $flags->{$module} and
1997 $flags->{$module} == 1 )
1999 ( ref( $flags->{$module} ) and
2000 exists $flags->{$module}->{$subperm} and
2001 $flags->{$module}->{$subperm} == 1 )
2005 return $flags;
2007 #FIXME - This fcn should return the failed permission so a suitable error msg can be delivered.
2010 sub getborrowernumber {
2011 my ($userid) = @_;
2012 my $userenv = C4::Context->userenv;
2013 if ( defined($userenv) && ref($userenv) eq 'HASH' && $userenv->{number} ) {
2014 return $userenv->{number};
2016 my $dbh = C4::Context->dbh;
2017 for my $field ( 'userid', 'cardnumber' ) {
2018 my $sth =
2019 $dbh->prepare("select borrowernumber from borrowers where $field=?");
2020 $sth->execute($userid);
2021 if ( $sth->rows ) {
2022 my ($bnumber) = $sth->fetchrow;
2023 return $bnumber;
2026 return 0;
2029 END { } # module clean-up code here (global destructor)
2031 __END__
2033 =head1 SEE ALSO
2035 CGI(3)
2037 C4::Output(3)
2039 Crypt::Eksblowfish::Bcrypt(3)
2041 Digest::MD5(3)
2043 =cut