Bug 14735: Save cache_expiry on modifying a report
[koha.git] / C4 / Auth.pm
blobef68244cf8991d3ee668ec16c3d2b773ea7355da
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 JSON qw/encode_json/;
24 use URI::Escape;
25 use CGI::Session;
27 require Exporter;
28 use C4::Context;
29 use C4::Templates; # to get the template
30 use C4::Languages;
31 use C4::Branch; # GetBranches
32 use C4::Search::History;
33 use C4::VirtualShelves;
34 use Koha::AuthUtils qw(hash_password);
35 use POSIX qw/strftime/;
36 use List::MoreUtils qw/ any /;
38 # use utf8;
39 use vars qw($VERSION @ISA @EXPORT @EXPORT_OK %EXPORT_TAGS $debug $ldap $cas $caslogout $shib $shib_login);
41 BEGIN {
42 sub psgi_env { any { /^psgi\./ } keys %ENV }
44 sub safe_exit {
45 if (psgi_env) { die 'psgi:exit' }
46 else { exit }
48 $VERSION = 3.07.00.049; # set version for version checking
50 $debug = $ENV{DEBUG};
51 @ISA = qw(Exporter);
52 @EXPORT = qw(&checkauth &get_template_and_user &haspermission &get_user_subpermissions);
53 @EXPORT_OK = qw(&check_api_auth &get_session &check_cookie_auth &checkpw &checkpw_internal &checkpw_hash
54 &get_all_subpermissions &get_user_subpermissions
56 %EXPORT_TAGS = ( EditPermissions => [qw(get_all_subpermissions get_user_subpermissions)] );
57 $ldap = C4::Context->config('useldapserver') || 0;
58 $cas = C4::Context->preference('casAuthentication');
59 $shib = C4::Context->config('useshibboleth') || 0;
60 $caslogout = C4::Context->preference('casLogout');
61 require C4::Auth_with_cas; # no import
63 if ($ldap) {
64 require C4::Auth_with_ldap;
65 import C4::Auth_with_ldap qw(checkpw_ldap);
67 if ($shib) {
68 require C4::Auth_with_shibboleth;
69 import C4::Auth_with_shibboleth
70 qw(shib_ok checkpw_shib logout_shib login_shib_url get_login_shib);
72 # Check for good config
73 if ( shib_ok() ) {
75 # Get shibboleth login attribute
76 $shib_login = get_login_shib();
79 # Bad config, disable shibboleth
80 else {
81 $shib = 0;
84 if ($cas) {
85 import C4::Auth_with_cas qw(check_api_auth_cas checkpw_cas login_cas logout_cas login_cas_url);
90 =head1 NAME
92 C4::Auth - Authenticates Koha users
94 =head1 SYNOPSIS
96 use CGI;
97 use C4::Auth;
98 use C4::Output;
100 my $query = new CGI;
102 my ($template, $borrowernumber, $cookie)
103 = get_template_and_user(
105 template_name => "opac-main.tt",
106 query => $query,
107 type => "opac",
108 authnotrequired => 0,
109 flagsrequired => {borrow => 1, catalogue => '*', tools => 'import_patrons' },
113 output_html_with_http_headers $query, $cookie, $template->output;
115 =head1 DESCRIPTION
117 The main function of this module is to provide
118 authentification. However the get_template_and_user function has
119 been provided so that a users login information is passed along
120 automatically. This gets loaded into the template.
122 =head1 FUNCTIONS
124 =head2 get_template_and_user
126 my ($template, $borrowernumber, $cookie)
127 = get_template_and_user(
129 template_name => "opac-main.tt",
130 query => $query,
131 type => "opac",
132 authnotrequired => 0,
133 flagsrequired => {borrow => 1, catalogue => '*', tools => 'import_patrons' },
137 This call passes the C<query>, C<flagsrequired> and C<authnotrequired>
138 to C<&checkauth> (in this module) to perform authentification.
139 See C<&checkauth> for an explanation of these parameters.
141 The C<template_name> is then used to find the correct template for
142 the page. The authenticated users details are loaded onto the
143 template in the HTML::Template LOOP variable C<USER_INFO>. Also the
144 C<sessionID> is passed to the template. This can be used in templates
145 if cookies are disabled. It needs to be put as and input to every
146 authenticated page.
148 More information on the C<gettemplate> sub can be found in the
149 Output.pm module.
151 =cut
153 sub get_template_and_user {
155 my $in = shift;
156 my ( $user, $cookie, $sessionID, $flags );
158 C4::Context->interface( $in->{type} );
160 my $safe_chars = 'a-zA-Z0-9_\-\/';
161 die "bad template path" unless $in->{'template_name'} =~ m/^[$safe_chars]+\.tt$/ig; #sanitize input
163 $in->{'authnotrequired'} ||= 0;
164 my $template = C4::Templates::gettemplate(
165 $in->{'template_name'},
166 $in->{'type'},
167 $in->{'query'},
168 $in->{'is_plugin'}
171 if ( $in->{'template_name'} !~ m/maintenance/ ) {
172 ( $user, $cookie, $sessionID, $flags ) = checkauth(
173 $in->{'query'},
174 $in->{'authnotrequired'},
175 $in->{'flagsrequired'},
176 $in->{'type'}
181 # 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
182 if ( $in->{type} eq 'opac' and $in->{template_name} !~ m|sco/| ) {
183 if ( C4::Context->preference('AutoSelfCheckID') && $user eq C4::Context->preference('AutoSelfCheckID') ) {
184 $template = C4::Templates::gettemplate( 'opac-auth.tt', 'opac', $in->{query} );
185 my $cookie = $in->{query}->cookie(
186 -name => 'CGISESSID',
187 -value => '',
188 -expires => '',
189 -HttpOnly => 1,
192 $template->param( loginprompt => 1 );
193 print $in->{query}->header(
194 -type => 'text/html',
195 -charset => 'utf-8',
196 -cookie => $cookie,
198 $template->output;
199 safe_exit;
203 my $borrowernumber;
204 if ($user) {
205 require C4::Members;
207 # It's possible for $user to be the borrowernumber if they don't have a
208 # userid defined (and are logging in through some other method, such
209 # as SSL certs against an email address)
210 $borrowernumber = getborrowernumber($user) if defined($user);
211 if ( !defined($borrowernumber) && defined($user) ) {
212 my $borrower = C4::Members::GetMember( borrowernumber => $user );
213 if ($borrower) {
214 $borrowernumber = $user;
216 # A bit of a hack, but I don't know there's a nicer way
217 # to do it.
218 $user = $borrower->{firstname} . ' ' . $borrower->{surname};
222 # user info
223 $template->param( loggedinusername => $user );
224 $template->param( loggedinusernumber => $borrowernumber );
225 $template->param( sessionID => $sessionID );
227 my ( $total, $pubshelves, $barshelves ) = C4::VirtualShelves::GetSomeShelfNames( $borrowernumber, 'MASTHEAD' );
228 $template->param(
229 pubshelves => $total->{pubtotal},
230 pubshelvesloop => $pubshelves,
231 barshelves => $total->{bartotal},
232 barshelvesloop => $barshelves,
235 my ($borr) = C4::Members::GetMemberDetails($borrowernumber);
236 my @bordat;
237 $bordat[0] = $borr;
238 $template->param( "USER_INFO" => \@bordat );
240 my $all_perms = get_all_subpermissions();
242 my @flagroots = qw(circulate catalogue parameters borrowers permissions reserveforothers borrow
243 editcatalogue updatecharges management tools editauthorities serials reports acquisition);
245 # We are going to use the $flags returned by checkauth
246 # to create the template's parameters that will indicate
247 # which menus the user can access.
248 if ( $flags && $flags->{superlibrarian} == 1 ) {
249 $template->param( CAN_user_circulate => 1 );
250 $template->param( CAN_user_catalogue => 1 );
251 $template->param( CAN_user_parameters => 1 );
252 $template->param( CAN_user_borrowers => 1 );
253 $template->param( CAN_user_permissions => 1 );
254 $template->param( CAN_user_reserveforothers => 1 );
255 $template->param( CAN_user_borrow => 1 );
256 $template->param( CAN_user_editcatalogue => 1 );
257 $template->param( CAN_user_updatecharges => 1 );
258 $template->param( CAN_user_acquisition => 1 );
259 $template->param( CAN_user_management => 1 );
260 $template->param( CAN_user_tools => 1 );
261 $template->param( CAN_user_editauthorities => 1 );
262 $template->param( CAN_user_serials => 1 );
263 $template->param( CAN_user_reports => 1 );
264 $template->param( CAN_user_staffaccess => 1 );
265 $template->param( CAN_user_plugins => 1 );
266 $template->param( CAN_user_coursereserves => 1 );
267 foreach my $module ( keys %$all_perms ) {
269 foreach my $subperm ( keys %{ $all_perms->{$module} } ) {
270 $template->param( "CAN_user_${module}_${subperm}" => 1 );
275 if ($flags) {
276 foreach my $module ( keys %$all_perms ) {
277 if ( $flags->{$module} == 1 ) {
278 foreach my $subperm ( keys %{ $all_perms->{$module} } ) {
279 $template->param( "CAN_user_${module}_${subperm}" => 1 );
281 } elsif ( ref( $flags->{$module} ) ) {
282 foreach my $subperm ( keys %{ $flags->{$module} } ) {
283 $template->param( "CAN_user_${module}_${subperm}" => 1 );
289 if ($flags) {
290 foreach my $module ( keys %$flags ) {
291 if ( $flags->{$module} == 1 or ref( $flags->{$module} ) ) {
292 $template->param( "CAN_user_$module" => 1 );
293 if ( $module eq "parameters" ) {
294 $template->param( CAN_user_management => 1 );
300 # Logged-in opac search history
301 # If the requested template is an opac one and opac search history is enabled
302 if ( $in->{type} eq 'opac' && C4::Context->preference('EnableOpacSearchHistory') ) {
303 my $dbh = C4::Context->dbh;
304 my $query = "SELECT COUNT(*) FROM search_history WHERE userid=?";
305 my $sth = $dbh->prepare($query);
306 $sth->execute($borrowernumber);
308 # If at least one search has already been performed
309 if ( $sth->fetchrow_array > 0 ) {
311 # We show the link in opac
312 $template->param( EnableOpacSearchHistory => 1 );
315 # And if there are searches performed when the user was not logged in,
316 # we add them to the logged-in search history
317 my @recentSearches = C4::Search::History::get_from_session( { cgi => $in->{'query'} } );
318 if (@recentSearches) {
319 my $dbh = C4::Context->dbh;
320 my $query = q{
321 INSERT INTO search_history(userid, sessionid, query_desc, query_cgi, type, total, time )
322 VALUES (?, ?, ?, ?, ?, ?, ?)
325 my $sth = $dbh->prepare($query);
326 $sth->execute( $borrowernumber,
327 $in->{query}->cookie("CGISESSID"),
328 $_->{query_desc},
329 $_->{query_cgi},
330 $_->{type} || 'biblio',
331 $_->{total},
332 $_->{time},
333 ) foreach @recentSearches;
335 # clear out the search history from the session now that
336 # we've saved it to the database
337 C4::Search::History::set_to_session( { cgi => $in->{'query'}, search_history => [] } );
339 } elsif ( $in->{type} eq 'intranet' and C4::Context->preference('EnableSearchHistory') ) {
340 $template->param( EnableSearchHistory => 1 );
343 else { # if this is an anonymous session, setup to display public lists...
345 # If shibboleth is enabled, and we're in an anonymous session, we should allow
346 # the user to attemp login via shibboleth.
347 if ($shib) {
348 $template->param( shibbolethAuthentication => $shib,
349 shibbolethLoginUrl => login_shib_url( $in->{'query'} ),
352 # If shibboleth is enabled and we have a shibboleth login attribute,
353 # but we are in an anonymous session, then we clearly have an invalid
354 # shibboleth koha account.
355 if ($shib_login) {
356 $template->param( invalidShibLogin => '1' );
360 $template->param( sessionID => $sessionID );
362 my ( $total, $pubshelves ) = C4::VirtualShelves::GetSomeShelfNames( undef, 'MASTHEAD' );
363 $template->param(
364 pubshelves => $total->{pubtotal},
365 pubshelvesloop => $pubshelves,
369 # Anonymous opac search history
370 # If opac search history is enabled and at least one search has already been performed
371 if ( C4::Context->preference('EnableOpacSearchHistory') ) {
372 my @recentSearches = C4::Search::History::get_from_session( { cgi => $in->{'query'} } );
373 if (@recentSearches) {
374 $template->param( EnableOpacSearchHistory => 1 );
378 if ( C4::Context->preference('dateformat') ) {
379 $template->param( dateformat => C4::Context->preference('dateformat') );
382 # these template parameters are set the same regardless of $in->{'type'}
384 # Set the using_https variable for templates
385 # FIXME Under Plack the CGI->https method always returns 'OFF'
386 my $https = $in->{query}->https();
387 my $using_https = ( defined $https and $https ne 'OFF' ) ? 1 : 0;
389 $template->param(
390 "BiblioDefaultView" . C4::Context->preference("BiblioDefaultView") => 1,
391 EnhancedMessagingPreferences => C4::Context->preference('EnhancedMessagingPreferences'),
392 GoogleJackets => C4::Context->preference("GoogleJackets"),
393 OpenLibraryCovers => C4::Context->preference("OpenLibraryCovers"),
394 KohaAdminEmailAddress => "" . C4::Context->preference("KohaAdminEmailAddress"),
395 LoginBranchcode => ( C4::Context->userenv ? C4::Context->userenv->{"branch"} : undef ),
396 LoginFirstname => ( C4::Context->userenv ? C4::Context->userenv->{"firstname"} : "Bel" ),
397 LoginSurname => C4::Context->userenv ? C4::Context->userenv->{"surname"} : "Inconnu",
398 emailaddress => C4::Context->userenv ? C4::Context->userenv->{"emailaddress"} : undef,
399 loggedinpersona => C4::Context->userenv ? C4::Context->userenv->{"persona"} : undef,
400 TagsEnabled => C4::Context->preference("TagsEnabled"),
401 hide_marc => C4::Context->preference("hide_marc"),
402 item_level_itypes => C4::Context->preference('item-level_itypes'),
403 patronimages => C4::Context->preference("patronimages"),
404 singleBranchMode => C4::Context->preference("singleBranchMode"),
405 XSLTDetailsDisplay => C4::Context->preference("XSLTDetailsDisplay"),
406 XSLTResultsDisplay => C4::Context->preference("XSLTResultsDisplay"),
407 using_https => $using_https,
408 noItemTypeImages => C4::Context->preference("noItemTypeImages"),
409 marcflavour => C4::Context->preference("marcflavour"),
410 persona => C4::Context->preference("persona"),
412 if ( $in->{'type'} eq "intranet" ) {
413 $template->param(
414 AmazonCoverImages => C4::Context->preference("AmazonCoverImages"),
415 AutoLocation => C4::Context->preference("AutoLocation"),
416 "BiblioDefaultView" . C4::Context->preference("IntranetBiblioDefaultView") => 1,
417 CalendarFirstDayOfWeek => ( C4::Context->preference("CalendarFirstDayOfWeek") eq "Sunday" ) ? 0 : 1,
418 CircAutocompl => C4::Context->preference("CircAutocompl"),
419 FRBRizeEditions => C4::Context->preference("FRBRizeEditions"),
420 IndependentBranches => C4::Context->preference("IndependentBranches"),
421 IntranetNav => C4::Context->preference("IntranetNav"),
422 IntranetmainUserblock => C4::Context->preference("IntranetmainUserblock"),
423 LibraryName => C4::Context->preference("LibraryName"),
424 LoginBranchname => ( C4::Context->userenv ? C4::Context->userenv->{"branchname"} : undef ),
425 advancedMARCEditor => C4::Context->preference("advancedMARCEditor"),
426 canreservefromotherbranches => C4::Context->preference('canreservefromotherbranches'),
427 intranetcolorstylesheet => C4::Context->preference("intranetcolorstylesheet"),
428 IntranetFavicon => C4::Context->preference("IntranetFavicon"),
429 intranetreadinghistory => C4::Context->preference("intranetreadinghistory"),
430 intranetstylesheet => C4::Context->preference("intranetstylesheet"),
431 IntranetUserCSS => C4::Context->preference("IntranetUserCSS"),
432 intranetuserjs => C4::Context->preference("intranetuserjs"),
433 intranetbookbag => C4::Context->preference("intranetbookbag"),
434 suggestion => C4::Context->preference("suggestion"),
435 virtualshelves => C4::Context->preference("virtualshelves"),
436 StaffSerialIssueDisplayCount => C4::Context->preference("StaffSerialIssueDisplayCount"),
437 EasyAnalyticalRecords => C4::Context->preference('EasyAnalyticalRecords'),
438 LocalCoverImages => C4::Context->preference('LocalCoverImages'),
439 OPACLocalCoverImages => C4::Context->preference('OPACLocalCoverImages'),
440 AllowMultipleCovers => C4::Context->preference('AllowMultipleCovers'),
441 EnableBorrowerFiles => C4::Context->preference('EnableBorrowerFiles'),
442 UseKohaPlugins => C4::Context->preference('UseKohaPlugins'),
443 UseCourseReserves => C4::Context->preference("UseCourseReserves"),
446 else {
447 warn "template type should be OPAC, here it is=[" . $in->{'type'} . "]" unless ( $in->{'type'} eq 'opac' );
449 #TODO : replace LibraryName syspref with 'system name', and remove this html processing
450 my $LibraryNameTitle = C4::Context->preference("LibraryName");
451 $LibraryNameTitle =~ s/<(?:\/?)(?:br|p)\s*(?:\/?)>/ /sgi;
452 $LibraryNameTitle =~ s/<(?:[^<>'"]|'(?:[^']*)'|"(?:[^"]*)")*>//sg;
454 # clean up the busc param in the session
455 # if the page is not opac-detail and not the "add to list" page
456 # and not the "edit comments" page
457 if ( C4::Context->preference("OpacBrowseResults")
458 && $in->{'template_name'} =~ /opac-(.+)\.(?:tt|tmpl)$/ ) {
459 my $pagename = $1;
460 unless ( $pagename =~ /^(?:MARC|ISBD)?detail$/
461 or $pagename =~ /^addbybiblionumber$/
462 or $pagename =~ /^review$/ ) {
463 my $sessionSearch = get_session( $sessionID || $in->{'query'}->cookie("CGISESSID") );
464 $sessionSearch->clear( ["busc"] ) if ( $sessionSearch->param("busc") );
468 # variables passed from CGI: opac_css_override and opac_search_limits.
469 my $opac_search_limit = $ENV{'OPAC_SEARCH_LIMIT'};
470 my $opac_limit_override = $ENV{'OPAC_LIMIT_OVERRIDE'};
471 my $opac_name = '';
472 if (
473 ( $opac_limit_override && $opac_search_limit && $opac_search_limit =~ /branch:(\w+)/ ) ||
474 ( $in->{'query'}->param('limit') && $in->{'query'}->param('limit') =~ /branch:(\w+)/ ) ||
475 ( $in->{'query'}->param('multibranchlimit') && $in->{'query'}->param('multibranchlimit') =~ /multibranchlimit-(\w+)/ )
477 $opac_name = $1; # opac_search_limit is a branch, so we use it.
478 } elsif ( $in->{'query'}->param('multibranchlimit') ) {
479 $opac_name = $in->{'query'}->param('multibranchlimit');
480 } elsif ( C4::Context->preference("SearchMyLibraryFirst") && C4::Context->userenv && C4::Context->userenv->{'branch'} ) {
481 $opac_name = C4::Context->userenv->{'branch'};
484 # FIXME Under Plack the CGI->https method always returns 'OFF' ($using_https will be set to 0 in this case)
485 my $opac_base_url = C4::Context->preference("OPACBaseURL"); #FIXME uses $using_https below as well
486 if ( !$opac_base_url ) {
487 $opac_base_url = $ENV{'SERVER_NAME'} . ( $ENV{'SERVER_PORT'} eq ( $using_https ? "443" : "80" ) ? '' : ":$ENV{'SERVER_PORT'}" );
489 $template->param(
490 opaccolorstylesheet => C4::Context->preference("opaccolorstylesheet"),
491 AnonSuggestions => "" . C4::Context->preference("AnonSuggestions"),
492 AuthorisedValueImages => C4::Context->preference("AuthorisedValueImages"),
493 BranchesLoop => GetBranchesLoop($opac_name),
494 BranchCategoriesLoop => GetBranchCategories( 'searchdomain', 1, $opac_name ),
495 CalendarFirstDayOfWeek => ( C4::Context->preference("CalendarFirstDayOfWeek") eq "Sunday" ) ? 0 : 1,
496 LibraryName => "" . C4::Context->preference("LibraryName"),
497 LibraryNameTitle => "" . $LibraryNameTitle,
498 LoginBranchname => C4::Context->userenv ? C4::Context->userenv->{"branchname"} : "",
499 OPACAmazonCoverImages => C4::Context->preference("OPACAmazonCoverImages"),
500 OPACFRBRizeEditions => C4::Context->preference("OPACFRBRizeEditions"),
501 OpacHighlightedWords => C4::Context->preference("OpacHighlightedWords"),
502 OPACItemHolds => C4::Context->preference("OPACItemHolds"),
503 OPACShelfBrowser => "" . C4::Context->preference("OPACShelfBrowser"),
504 OPACURLOpenInNewWindow => "" . C4::Context->preference("OPACURLOpenInNewWindow"),
505 OPACUserCSS => "" . C4::Context->preference("OPACUserCSS"),
506 OpacAuthorities => C4::Context->preference("OpacAuthorities"),
507 OPACBaseURL => ( $using_https ? "https://" : "http://" ) . $opac_base_url,
508 opac_css_override => $ENV{'OPAC_CSS_OVERRIDE'},
509 opac_search_limit => $opac_search_limit,
510 opac_limit_override => $opac_limit_override,
511 OpacBrowser => C4::Context->preference("OpacBrowser"),
512 OpacCloud => C4::Context->preference("OpacCloud"),
513 OpacKohaUrl => C4::Context->preference("OpacKohaUrl"),
514 OpacMainUserBlock => "" . C4::Context->preference("OpacMainUserBlock"),
515 OpacNav => "" . C4::Context->preference("OpacNav"),
516 OpacNavRight => "" . C4::Context->preference("OpacNavRight"),
517 OpacNavBottom => "" . C4::Context->preference("OpacNavBottom"),
518 OpacPasswordChange => C4::Context->preference("OpacPasswordChange"),
519 OPACPatronDetails => C4::Context->preference("OPACPatronDetails"),
520 OPACPrivacy => C4::Context->preference("OPACPrivacy"),
521 OPACFinesTab => C4::Context->preference("OPACFinesTab"),
522 OpacTopissue => C4::Context->preference("OpacTopissue"),
523 RequestOnOpac => C4::Context->preference("RequestOnOpac"),
524 'Version' => C4::Context->preference('Version'),
525 hidelostitems => C4::Context->preference("hidelostitems"),
526 mylibraryfirst => ( C4::Context->preference("SearchMyLibraryFirst") && C4::Context->userenv ) ? C4::Context->userenv->{'branch'} : '',
527 opaclayoutstylesheet => "" . C4::Context->preference("opaclayoutstylesheet"),
528 opacbookbag => "" . C4::Context->preference("opacbookbag"),
529 opaccredits => "" . C4::Context->preference("opaccredits"),
530 OpacFavicon => C4::Context->preference("OpacFavicon"),
531 opacheader => "" . C4::Context->preference("opacheader"),
532 opaclanguagesdisplay => "" . C4::Context->preference("opaclanguagesdisplay"),
533 opacreadinghistory => C4::Context->preference("opacreadinghistory"),
534 opacuserjs => C4::Context->preference("opacuserjs"),
535 opacuserlogin => "" . C4::Context->preference("opacuserlogin"),
536 ShowReviewer => C4::Context->preference("ShowReviewer"),
537 ShowReviewerPhoto => C4::Context->preference("ShowReviewerPhoto"),
538 suggestion => "" . C4::Context->preference("suggestion"),
539 virtualshelves => "" . C4::Context->preference("virtualshelves"),
540 OPACSerialIssueDisplayCount => C4::Context->preference("OPACSerialIssueDisplayCount"),
541 OPACXSLTDetailsDisplay => C4::Context->preference("OPACXSLTDetailsDisplay"),
542 OPACXSLTResultsDisplay => C4::Context->preference("OPACXSLTResultsDisplay"),
543 SyndeticsClientCode => C4::Context->preference("SyndeticsClientCode"),
544 SyndeticsEnabled => C4::Context->preference("SyndeticsEnabled"),
545 SyndeticsCoverImages => C4::Context->preference("SyndeticsCoverImages"),
546 SyndeticsTOC => C4::Context->preference("SyndeticsTOC"),
547 SyndeticsSummary => C4::Context->preference("SyndeticsSummary"),
548 SyndeticsEditions => C4::Context->preference("SyndeticsEditions"),
549 SyndeticsExcerpt => C4::Context->preference("SyndeticsExcerpt"),
550 SyndeticsReviews => C4::Context->preference("SyndeticsReviews"),
551 SyndeticsAuthorNotes => C4::Context->preference("SyndeticsAuthorNotes"),
552 SyndeticsAwards => C4::Context->preference("SyndeticsAwards"),
553 SyndeticsSeries => C4::Context->preference("SyndeticsSeries"),
554 SyndeticsCoverImageSize => C4::Context->preference("SyndeticsCoverImageSize"),
555 OPACLocalCoverImages => C4::Context->preference("OPACLocalCoverImages"),
556 PatronSelfRegistration => C4::Context->preference("PatronSelfRegistration"),
557 PatronSelfRegistrationDefaultCategory => C4::Context->preference("PatronSelfRegistrationDefaultCategory"),
560 $template->param( OpacPublic => '1' ) if ( $user || C4::Context->preference("OpacPublic") );
563 # Check if we were asked using parameters to force a specific language
564 if ( defined $in->{'query'}->param('language') ) {
566 # Extract the language, let C4::Languages::getlanguage choose
567 # what to do
568 my $language = C4::Languages::getlanguage( $in->{'query'} );
569 my $languagecookie = C4::Templates::getlanguagecookie( $in->{'query'}, $language );
570 if ( ref $cookie eq 'ARRAY' ) {
571 push @{$cookie}, $languagecookie;
572 } else {
573 $cookie = [ $cookie, $languagecookie ];
577 return ( $template, $borrowernumber, $cookie, $flags );
580 =head2 checkauth
582 ($userid, $cookie, $sessionID) = &checkauth($query, $noauth, $flagsrequired, $type);
584 Verifies that the user is authorized to run this script. If
585 the user is authorized, a (userid, cookie, session-id, flags)
586 quadruple is returned. If the user is not authorized but does
587 not have the required privilege (see $flagsrequired below), it
588 displays an error page and exits. Otherwise, it displays the
589 login page and exits.
591 Note that C<&checkauth> will return if and only if the user
592 is authorized, so it should be called early on, before any
593 unfinished operations (e.g., if you've opened a file, then
594 C<&checkauth> won't close it for you).
596 C<$query> is the CGI object for the script calling C<&checkauth>.
598 The C<$noauth> argument is optional. If it is set, then no
599 authorization is required for the script.
601 C<&checkauth> fetches user and session information from C<$query> and
602 ensures that the user is authorized to run scripts that require
603 authorization.
605 The C<$flagsrequired> argument specifies the required privileges
606 the user must have if the username and password are correct.
607 It should be specified as a reference-to-hash; keys in the hash
608 should be the "flags" for the user, as specified in the Members
609 intranet module. Any key specified must correspond to a "flag"
610 in the userflags table. E.g., { circulate => 1 } would specify
611 that the user must have the "circulate" privilege in order to
612 proceed. To make sure that access control is correct, the
613 C<$flagsrequired> parameter must be specified correctly.
615 Koha also has a concept of sub-permissions, also known as
616 granular permissions. This makes the value of each key
617 in the C<flagsrequired> hash take on an additional
618 meaning, i.e.,
622 The user must have access to all subfunctions of the module
623 specified by the hash key.
627 The user must have access to at least one subfunction of the module
628 specified by the hash key.
630 specific permission, e.g., 'export_catalog'
632 The user must have access to the specific subfunction list, which
633 must correspond to a row in the permissions table.
635 The C<$type> argument specifies whether the template should be
636 retrieved from the opac or intranet directory tree. "opac" is
637 assumed if it is not specified; however, if C<$type> is specified,
638 "intranet" is assumed if it is not "opac".
640 If C<$query> does not have a valid session ID associated with it
641 (i.e., the user has not logged in) or if the session has expired,
642 C<&checkauth> presents the user with a login page (from the point of
643 view of the original script, C<&checkauth> does not return). Once the
644 user has authenticated, C<&checkauth> restarts the original script
645 (this time, C<&checkauth> returns).
647 The login page is provided using a HTML::Template, which is set in the
648 systempreferences table or at the top of this file. The variable C<$type>
649 selects which template to use, either the opac or the intranet
650 authentification template.
652 C<&checkauth> returns a user ID, a cookie, and a session ID. The
653 cookie should be sent back to the browser; it verifies that the user
654 has authenticated.
656 =cut
658 sub _version_check {
659 my $type = shift;
660 my $query = shift;
661 my $version;
663 # If Version syspref is unavailable, it means Koha is beeing installed,
664 # and so we must redirect to OPAC maintenance page or to the WebInstaller
665 # also, if OpacMaintenance is ON, OPAC should redirect to maintenance
666 if ( C4::Context->preference('OpacMaintenance') && $type eq 'opac' ) {
667 warn "OPAC Install required, redirecting to maintenance";
668 print $query->redirect("/cgi-bin/koha/maintenance.pl");
669 safe_exit;
671 unless ( $version = C4::Context->preference('Version') ) { # assignment, not comparison
672 if ( $type ne 'opac' ) {
673 warn "Install required, redirecting to Installer";
674 print $query->redirect("/cgi-bin/koha/installer/install.pl");
675 } else {
676 warn "OPAC Install required, redirecting to maintenance";
677 print $query->redirect("/cgi-bin/koha/maintenance.pl");
679 safe_exit;
682 # check that database and koha version are the same
683 # there is no DB version, it's a fresh install,
684 # go to web installer
685 # there is a DB version, compare it to the code version
686 my $kohaversion = C4::Context::KOHAVERSION;
688 # remove the 3 last . to have a Perl number
689 $kohaversion =~ s/(.*\..*)\.(.*)\.(.*)/$1$2$3/;
690 $debug and print STDERR "kohaversion : $kohaversion\n";
691 if ( $version < $kohaversion ) {
692 my $warning = "Database update needed, redirecting to %s. Database is $version and Koha is $kohaversion";
693 if ( $type ne 'opac' ) {
694 warn sprintf( $warning, 'Installer' );
695 print $query->redirect("/cgi-bin/koha/installer/install.pl?step=3");
696 } else {
697 warn sprintf( "OPAC: " . $warning, 'maintenance' );
698 print $query->redirect("/cgi-bin/koha/maintenance.pl");
700 safe_exit;
704 sub _session_log {
705 (@_) or return 0;
706 open my $fh, '>>', "/tmp/sessionlog" or warn "ERROR: Cannot append to /tmp/sessionlog";
707 printf $fh join( "\n", @_ );
708 close $fh;
711 sub _timeout_syspref {
712 my $timeout = C4::Context->preference('timeout') || 600;
714 # value in days, convert in seconds
715 if ( $timeout =~ /(\d+)[dD]/ ) {
716 $timeout = $1 * 86400;
718 return $timeout;
721 sub checkauth {
722 my $query = shift;
723 $debug and warn "Checking Auth";
725 # $authnotrequired will be set for scripts which will run without authentication
726 my $authnotrequired = shift;
727 my $flagsrequired = shift;
728 my $type = shift;
729 my $persona = shift;
730 $type = 'opac' unless $type;
732 my $dbh = C4::Context->dbh;
733 my $timeout = _timeout_syspref();
735 _version_check( $type, $query );
737 # state variables
738 my $loggedin = 0;
739 my %info;
740 my ( $userid, $cookie, $sessionID, $flags, $barshelves, $pubshelves );
741 my $logout = $query->param('logout.x');
743 my $anon_search_history;
745 # This parameter is the name of the CAS server we want to authenticate against,
746 # when using authentication against multiple CAS servers, as configured in Auth_cas_servers.yaml
747 my $casparam = $query->param('cas');
748 my $q_userid = $query->param('userid') // '';
750 # Basic authentication is incompatible with the use of Shibboleth,
751 # as Shibboleth may return REMOTE_USER as a Shibboleth attribute,
752 # and it may not be the attribute we want to use to match the koha login.
754 # Also, do not consider an empty REMOTE_USER.
756 # Finally, after those tests, we can assume (although if it would be better with
757 # a syspref) that if we get a REMOTE_USER, that's from basic authentication,
758 # and we can affect it to $userid.
759 if ( !$shib and defined( $ENV{'REMOTE_USER'} ) and $ENV{'REMOTE_USER'} ne '' and $userid = $ENV{'REMOTE_USER'} ) {
761 # Using Basic Authentication, no cookies required
762 $cookie = $query->cookie(
763 -name => 'CGISESSID',
764 -value => '',
765 -expires => '',
766 -HttpOnly => 1,
768 $loggedin = 1;
770 elsif ($persona) {
772 # we dont want to set a session because we are being called by a persona callback
774 elsif ( $sessionID = $query->cookie("CGISESSID") )
775 { # assignment, not comparison
776 my $session = get_session($sessionID);
777 C4::Context->_new_userenv($sessionID);
778 my ( $ip, $lasttime, $sessiontype );
779 my $s_userid = '';
780 if ($session) {
781 $s_userid = $session->param('id') // '';
782 C4::Context::set_userenv(
783 $session->param('number'), $s_userid,
784 $session->param('cardnumber'), $session->param('firstname'),
785 $session->param('surname'), $session->param('branch'),
786 $session->param('branchname'), $session->param('flags'),
787 $session->param('emailaddress'), $session->param('branchprinter'),
788 $session->param('persona'), $session->param('shibboleth')
790 C4::Context::set_shelves_userenv( 'bar', $session->param('barshelves') );
791 C4::Context::set_shelves_userenv( 'pub', $session->param('pubshelves') );
792 C4::Context::set_shelves_userenv( 'tot', $session->param('totshelves') );
793 $debug and printf STDERR "AUTH_SESSION: (%s)\t%s %s - %s\n", map { $session->param($_) } qw(cardnumber firstname surname branch);
794 $ip = $session->param('ip');
795 $lasttime = $session->param('lasttime');
796 $userid = $s_userid;
797 $sessiontype = $session->param('sessiontype') || '';
799 if ( ( $query->param('koha_login_context') && ( $q_userid ne $s_userid ) )
800 || ( $cas && $query->param('ticket') && !C4::Context->userenv->{'id'} ) || ( $shib && $shib_login && !$logout ) ) {
802 #if a user enters an id ne to the id in the current session, we need to log them in...
803 #first we need to clear the anonymous session...
804 $debug and warn "query id = $q_userid but session id = $s_userid";
805 $anon_search_history = $session->param('search_history');
806 $session->delete();
807 $session->flush;
808 C4::Context->_unset_userenv($sessionID);
809 $sessionID = undef;
810 $userid = undef;
812 elsif ($logout) {
814 # voluntary logout the user
815 # check wether the user was using their shibboleth session or a local one
816 my $shibSuccess = C4::Context->userenv->{'shibboleth'};
817 $session->delete();
818 $session->flush;
819 C4::Context->_unset_userenv($sessionID);
821 #_session_log(sprintf "%20s from %16s logged out at %30s (manually).\n", $userid,$ip,(strftime "%c",localtime));
822 $sessionID = undef;
823 $userid = undef;
825 if ( $cas and $caslogout ) {
826 logout_cas($query);
829 # If we are in a shibboleth session (shibboleth is enabled, a shibboleth match attribute is set and matches koha matchpoint)
830 if ( $shib and $shib_login and $shibSuccess and $type eq 'opac' ) {
832 # (Note: $type eq 'opac' condition should be removed when shibboleth authentication for intranet will be implemented)
833 logout_shib($query);
836 elsif ( !$lasttime || ( $lasttime < time() - $timeout ) ) {
838 # timed logout
839 $info{'timed_out'} = 1;
840 if ($session) {
841 $session->delete();
842 $session->flush;
844 C4::Context->_unset_userenv($sessionID);
846 #_session_log(sprintf "%20s from %16s logged out at %30s (inactivity).\n", $userid,$ip,(strftime "%c",localtime));
847 $userid = undef;
848 $sessionID = undef;
850 elsif ( C4::Context->preference('SessionRestrictionByIP') && $ip ne $ENV{'REMOTE_ADDR'} ) {
852 # Different ip than originally logged in from
853 $info{'oldip'} = $ip;
854 $info{'newip'} = $ENV{'REMOTE_ADDR'};
855 $info{'different_ip'} = 1;
856 $session->delete();
857 $session->flush;
858 C4::Context->_unset_userenv($sessionID);
860 #_session_log(sprintf "%20s from %16s logged out at %30s (ip changed to %16s).\n", $userid,$ip,(strftime "%c",localtime), $info{'newip'});
861 $sessionID = undef;
862 $userid = undef;
864 else {
865 $cookie = $query->cookie(
866 -name => 'CGISESSID',
867 -value => $session->id,
868 -HttpOnly => 1
870 $session->param( 'lasttime', time() );
871 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...
872 $flags = haspermission( $userid, $flagsrequired );
873 if ($flags) {
874 $loggedin = 1;
875 } else {
876 $info{'nopermission'} = 1;
881 unless ( $userid || $sessionID ) {
883 #we initiate a session prior to checking for a username to allow for anonymous sessions...
884 my $session = get_session("") or die "Auth ERROR: Cannot get_session()";
886 # Save anonymous search history in new session so it can be retrieved
887 # by get_template_and_user to store it in user's search history after
888 # a successful login.
889 if ($anon_search_history) {
890 $session->param( 'search_history', $anon_search_history );
893 my $sessionID = $session->id;
894 C4::Context->_new_userenv($sessionID);
895 $cookie = $query->cookie(
896 -name => 'CGISESSID',
897 -value => $session->id,
898 -HttpOnly => 1
900 $userid = $q_userid;
901 my $pki_field = C4::Context->preference('AllowPKIAuth');
902 if ( !defined($pki_field) ) {
903 print STDERR "ERROR: Missing system preference AllowPKIAuth.\n";
904 $pki_field = 'None';
906 if ( ( $cas && $query->param('ticket') )
907 || $userid
908 || ( $shib && $shib_login )
909 || $pki_field ne 'None'
910 || $persona )
912 my $password = $query->param('password');
913 my $shibSuccess = 0;
915 my ( $return, $cardnumber );
917 # If shib is enabled and we have a shib login, does the login match a valid koha user
918 if ( $shib && $shib_login && $type eq 'opac' ) {
919 my $retuserid;
921 # Do not pass password here, else shib will not be checked in checkpw.
922 ( $return, $cardnumber, $retuserid ) = checkpw( $dbh, $userid, undef, $query );
923 $userid = $retuserid;
924 $shibSuccess = $return;
925 $info{'invalidShibLogin'} = 1 unless ($return);
928 # If shib login and match were successfull, skip further login methods
929 unless ($shibSuccess) {
930 if ( $cas && $query->param('ticket') ) {
931 my $retuserid;
932 ( $return, $cardnumber, $retuserid ) =
933 checkpw( $dbh, $userid, $password, $query );
934 $userid = $retuserid;
935 $info{'invalidCasLogin'} = 1 unless ($return);
938 elsif ($persona) {
939 my $value = $persona;
941 # If we're looking up the email, there's a chance that the person
942 # doesn't have a userid. So if there is none, we pass along the
943 # borrower number, and the bits of code that need to know the user
944 # ID will have to be smart enough to handle that.
945 require C4::Members;
946 my @users_info = C4::Members::GetBorrowersWithEmail($value);
947 if (@users_info) {
949 # First the userid, then the borrowernum
950 $value = $users_info[0][1] || $users_info[0][0];
952 else {
953 undef $value;
955 $return = $value ? 1 : 0;
956 $userid = $value;
959 elsif (
960 ( $pki_field eq 'Common Name' && $ENV{'SSL_CLIENT_S_DN_CN'} )
961 || ( $pki_field eq 'emailAddress'
962 && $ENV{'SSL_CLIENT_S_DN_Email'} )
965 my $value;
966 if ( $pki_field eq 'Common Name' ) {
967 $value = $ENV{'SSL_CLIENT_S_DN_CN'};
969 elsif ( $pki_field eq 'emailAddress' ) {
970 $value = $ENV{'SSL_CLIENT_S_DN_Email'};
972 # If we're looking up the email, there's a chance that the person
973 # doesn't have a userid. So if there is none, we pass along the
974 # borrower number, and the bits of code that need to know the user
975 # ID will have to be smart enough to handle that.
976 require C4::Members;
977 my @users_info = C4::Members::GetBorrowersWithEmail($value);
978 if (@users_info) {
980 # First the userid, then the borrowernum
981 $value = $users_info[0][1] || $users_info[0][0];
982 } else {
983 undef $value;
987 $return = $value ? 1 : 0;
988 $userid = $value;
991 else {
992 my $retuserid;
993 ( $return, $cardnumber, $retuserid ) =
994 checkpw( $dbh, $userid, $password, $query );
995 $userid = $retuserid if ($retuserid);
996 $info{'invalid_username_or_password'} = 1 unless ($return);
1000 # $return: 1 = valid user, 2 = superlibrarian
1001 if ($return) {
1003 #_session_log(sprintf "%20s from %16s logged in at %30s.\n", $userid,$ENV{'REMOTE_ADDR'},(strftime '%c', localtime));
1004 if ( $flags = haspermission( $userid, $flagsrequired ) ) {
1005 $loggedin = 1;
1007 else {
1008 $info{'nopermission'} = 1;
1009 C4::Context->_unset_userenv($sessionID);
1011 my ( $borrowernumber, $firstname, $surname, $userflags,
1012 $branchcode, $branchname, $branchprinter, $emailaddress );
1014 if ( $return == 1 ) {
1015 my $select = "
1016 SELECT borrowernumber, firstname, surname, flags, borrowers.branchcode,
1017 branches.branchname as branchname,
1018 branches.branchprinter as branchprinter,
1019 email
1020 FROM borrowers
1021 LEFT JOIN branches on borrowers.branchcode=branches.branchcode
1023 my $sth = $dbh->prepare("$select where userid=?");
1024 $sth->execute($userid);
1025 unless ( $sth->rows ) {
1026 $debug and print STDERR "AUTH_1: no rows for userid='$userid'\n";
1027 $sth = $dbh->prepare("$select where cardnumber=?");
1028 $sth->execute($cardnumber);
1030 unless ( $sth->rows ) {
1031 $debug and print STDERR "AUTH_2a: no rows for cardnumber='$cardnumber'\n";
1032 $sth->execute($userid);
1033 unless ( $sth->rows ) {
1034 $debug and print STDERR "AUTH_2b: no rows for userid='$userid' AS cardnumber\n";
1038 if ( $sth->rows ) {
1039 ( $borrowernumber, $firstname, $surname, $userflags,
1040 $branchcode, $branchname, $branchprinter, $emailaddress ) = $sth->fetchrow;
1041 $debug and print STDERR "AUTH_3 results: " .
1042 "$cardnumber,$borrowernumber,$userid,$firstname,$surname,$userflags,$branchcode,$emailaddress\n";
1043 } else {
1044 print STDERR "AUTH_3: no results for userid='$userid', cardnumber='$cardnumber'.\n";
1047 # launch a sequence to check if we have a ip for the branch, i
1048 # if we have one we replace the branchcode of the userenv by the branch bound in the ip.
1050 my $ip = $ENV{'REMOTE_ADDR'};
1052 # if they specify at login, use that
1053 if ( $query->param('branch') ) {
1054 $branchcode = $query->param('branch');
1055 $branchname = GetBranchName($branchcode);
1057 my $branches = GetBranches();
1058 if ( C4::Context->boolean_preference('IndependentBranches') && C4::Context->boolean_preference('Autolocation') ) {
1060 # we have to check they are coming from the right ip range
1061 my $domain = $branches->{$branchcode}->{'branchip'};
1062 if ( $ip !~ /^$domain/ ) {
1063 $loggedin = 0;
1064 $info{'wrongip'} = 1;
1068 my @branchesloop;
1069 foreach my $br ( keys %$branches ) {
1071 # now we work with the treatment of ip
1072 my $domain = $branches->{$br}->{'branchip'};
1073 if ( $domain && $ip =~ /^$domain/ ) {
1074 $branchcode = $branches->{$br}->{'branchcode'};
1076 # new op dev : add the branchprinter and branchname in the cookie
1077 $branchprinter = $branches->{$br}->{'branchprinter'};
1078 $branchname = $branches->{$br}->{'branchname'};
1081 $session->param( 'number', $borrowernumber );
1082 $session->param( 'id', $userid );
1083 $session->param( 'cardnumber', $cardnumber );
1084 $session->param( 'firstname', $firstname );
1085 $session->param( 'surname', $surname );
1086 $session->param( 'branch', $branchcode );
1087 $session->param( 'branchname', $branchname );
1088 $session->param( 'flags', $userflags );
1089 $session->param( 'emailaddress', $emailaddress );
1090 $session->param( 'ip', $session->remote_addr() );
1091 $session->param( 'lasttime', time() );
1092 $session->param( 'shibboleth', $shibSuccess );
1093 $debug and printf STDERR "AUTH_4: (%s)\t%s %s - %s\n", map { $session->param($_) } qw(cardnumber firstname surname branch);
1095 elsif ( $return == 2 ) {
1097 #We suppose the user is the superlibrarian
1098 $borrowernumber = 0;
1099 $session->param( 'number', 0 );
1100 $session->param( 'id', C4::Context->config('user') );
1101 $session->param( 'cardnumber', C4::Context->config('user') );
1102 $session->param( 'firstname', C4::Context->config('user') );
1103 $session->param( 'surname', C4::Context->config('user') );
1104 $session->param( 'branch', 'NO_LIBRARY_SET' );
1105 $session->param( 'branchname', 'NO_LIBRARY_SET' );
1106 $session->param( 'flags', 1 );
1107 $session->param( 'emailaddress', C4::Context->preference('KohaAdminEmailAddress') );
1108 $session->param( 'ip', $session->remote_addr() );
1109 $session->param( 'lasttime', time() );
1111 if ($persona) {
1112 $session->param( 'persona', 1 );
1114 C4::Context::set_userenv(
1115 $session->param('number'), $session->param('id'),
1116 $session->param('cardnumber'), $session->param('firstname'),
1117 $session->param('surname'), $session->param('branch'),
1118 $session->param('branchname'), $session->param('flags'),
1119 $session->param('emailaddress'), $session->param('branchprinter'),
1120 $session->param('persona'), $session->param('shibboleth')
1124 # $return: 0 = invalid user
1125 # reset to anonymous session
1126 else {
1127 $debug and warn "Login failed, resetting anonymous session...";
1128 if ($userid) {
1129 $info{'invalid_username_or_password'} = 1;
1130 C4::Context->_unset_userenv($sessionID);
1132 $session->param( 'lasttime', time() );
1133 $session->param( 'ip', $session->remote_addr() );
1134 $session->param( 'sessiontype', 'anon' );
1136 } # END if ( $userid = $query->param('userid') )
1137 elsif ( $type eq "opac" ) {
1139 # if we are here this is an anonymous session; add public lists to it and a few other items...
1140 # anonymous sessions are created only for the OPAC
1141 $debug and warn "Initiating an anonymous session...";
1143 # setting a couple of other session vars...
1144 $session->param( 'ip', $session->remote_addr() );
1145 $session->param( 'lasttime', time() );
1146 $session->param( 'sessiontype', 'anon' );
1148 } # END unless ($userid)
1150 # finished authentification, now respond
1151 if ( $loggedin || $authnotrequired )
1153 # successful login
1154 unless ($cookie) {
1155 $cookie = $query->cookie(
1156 -name => 'CGISESSID',
1157 -value => '',
1158 -HttpOnly => 1
1161 return ( $userid, $cookie, $sessionID, $flags );
1166 # AUTH rejected, show the login/password template, after checking the DB.
1170 # get the inputs from the incoming query
1171 my @inputs = ();
1172 foreach my $name ( param $query) {
1173 (next) if ( $name eq 'userid' || $name eq 'password' || $name eq 'ticket' );
1174 my $value = $query->param($name);
1175 push @inputs, { name => $name, value => $value };
1178 my $LibraryNameTitle = C4::Context->preference("LibraryName");
1179 $LibraryNameTitle =~ s/<(?:\/?)(?:br|p)\s*(?:\/?)>/ /sgi;
1180 $LibraryNameTitle =~ s/<(?:[^<>'"]|'(?:[^']*)'|"(?:[^"]*)")*>//sg;
1182 my $template_name = ( $type eq 'opac' ) ? 'opac-auth.tt' : 'auth.tt';
1183 my $template = C4::Templates::gettemplate( $template_name, $type, $query );
1184 $template->param(
1185 branchloop => GetBranchesLoop(),
1186 opaccolorstylesheet => C4::Context->preference("opaccolorstylesheet"),
1187 opaclayoutstylesheet => C4::Context->preference("opaclayoutstylesheet"),
1188 login => 1,
1189 INPUTS => \@inputs,
1190 casAuthentication => C4::Context->preference("casAuthentication"),
1191 shibbolethAuthentication => $shib,
1192 SessionRestrictionByIP => C4::Context->preference("SessionRestrictionByIP"),
1193 suggestion => C4::Context->preference("suggestion"),
1194 virtualshelves => C4::Context->preference("virtualshelves"),
1195 LibraryName => "" . C4::Context->preference("LibraryName"),
1196 LibraryNameTitle => "" . $LibraryNameTitle,
1197 opacuserlogin => C4::Context->preference("opacuserlogin"),
1198 OpacNav => C4::Context->preference("OpacNav"),
1199 OpacNavRight => C4::Context->preference("OpacNavRight"),
1200 OpacNavBottom => C4::Context->preference("OpacNavBottom"),
1201 opaccredits => C4::Context->preference("opaccredits"),
1202 OpacFavicon => C4::Context->preference("OpacFavicon"),
1203 opacreadinghistory => C4::Context->preference("opacreadinghistory"),
1204 opaclanguagesdisplay => C4::Context->preference("opaclanguagesdisplay"),
1205 opacuserjs => C4::Context->preference("opacuserjs"),
1206 opacbookbag => "" . C4::Context->preference("opacbookbag"),
1207 OpacCloud => C4::Context->preference("OpacCloud"),
1208 OpacTopissue => C4::Context->preference("OpacTopissue"),
1209 OpacAuthorities => C4::Context->preference("OpacAuthorities"),
1210 OpacBrowser => C4::Context->preference("OpacBrowser"),
1211 opacheader => C4::Context->preference("opacheader"),
1212 TagsEnabled => C4::Context->preference("TagsEnabled"),
1213 OPACUserCSS => C4::Context->preference("OPACUserCSS"),
1214 intranetcolorstylesheet => C4::Context->preference("intranetcolorstylesheet"),
1215 intranetstylesheet => C4::Context->preference("intranetstylesheet"),
1216 intranetbookbag => C4::Context->preference("intranetbookbag"),
1217 IntranetNav => C4::Context->preference("IntranetNav"),
1218 IntranetFavicon => C4::Context->preference("IntranetFavicon"),
1219 intranetuserjs => C4::Context->preference("intranetuserjs"),
1220 IndependentBranches => C4::Context->preference("IndependentBranches"),
1221 AutoLocation => C4::Context->preference("AutoLocation"),
1222 wrongip => $info{'wrongip'},
1223 PatronSelfRegistration => C4::Context->preference("PatronSelfRegistration"),
1224 PatronSelfRegistrationDefaultCategory => C4::Context->preference("PatronSelfRegistrationDefaultCategory"),
1225 persona => C4::Context->preference("Persona"),
1226 opac_css_override => $ENV{'OPAC_CSS_OVERRIDE'},
1229 $template->param( OpacPublic => C4::Context->preference("OpacPublic") );
1230 $template->param( loginprompt => 1 ) unless $info{'nopermission'};
1232 if ( $type eq 'opac' ) {
1233 my ( $total, $pubshelves ) = C4::VirtualShelves::GetSomeShelfNames( undef, 'MASTHEAD' );
1234 $template->param(
1235 pubshelves => $total->{pubtotal},
1236 pubshelvesloop => $pubshelves,
1240 if ($cas) {
1242 # Is authentication against multiple CAS servers enabled?
1243 if ( C4::Auth_with_cas::multipleAuth && !$casparam ) {
1244 my $casservers = C4::Auth_with_cas::getMultipleAuth();
1245 my @tmplservers;
1246 foreach my $key ( keys %$casservers ) {
1247 push @tmplservers, { name => $key, value => login_cas_url( $query, $key ) . "?cas=$key" };
1249 $template->param(
1250 casServersLoop => \@tmplservers
1252 } else {
1253 $template->param(
1254 casServerUrl => login_cas_url($query),
1258 $template->param(
1259 invalidCasLogin => $info{'invalidCasLogin'}
1263 if ($shib) {
1264 $template->param(
1265 shibbolethAuthentication => $shib,
1266 shibbolethLoginUrl => login_shib_url($query),
1270 my $self_url = $query->url( -absolute => 1 );
1271 $template->param(
1272 url => $self_url,
1273 LibraryName => C4::Context->preference("LibraryName"),
1275 $template->param(%info);
1277 # $cookie = $query->cookie(CGISESSID => $session->id
1278 # );
1279 print $query->header(
1280 -type => 'text/html',
1281 -charset => 'utf-8',
1282 -cookie => $cookie
1284 $template->output;
1285 safe_exit;
1288 =head2 check_api_auth
1290 ($status, $cookie, $sessionId) = check_api_auth($query, $userflags);
1292 Given a CGI query containing the parameters 'userid' and 'password' and/or a session
1293 cookie, determine if the user has the privileges specified by C<$userflags>.
1295 C<check_api_auth> is is meant for authenticating users of web services, and
1296 consequently will always return and will not attempt to redirect the user
1297 agent.
1299 If a valid session cookie is already present, check_api_auth will return a status
1300 of "ok", the cookie, and the Koha session ID.
1302 If no session cookie is present, check_api_auth will check the 'userid' and 'password
1303 parameters and create a session cookie and Koha session if the supplied credentials
1304 are OK.
1306 Possible return values in C<$status> are:
1308 =over
1310 =item "ok" -- user authenticated; C<$cookie> and C<$sessionid> have valid values.
1312 =item "failed" -- credentials are not correct; C<$cookie> and C<$sessionid> are undef
1314 =item "maintenance" -- DB is in maintenance mode; no login possible at the moment
1316 =item "expired -- session cookie has expired; API user should resubmit userid and password
1318 =back
1320 =cut
1322 sub check_api_auth {
1323 my $query = shift;
1324 my $flagsrequired = shift;
1326 my $dbh = C4::Context->dbh;
1327 my $timeout = _timeout_syspref();
1329 unless ( C4::Context->preference('Version') ) {
1331 # database has not been installed yet
1332 return ( "maintenance", undef, undef );
1334 my $kohaversion = C4::Context::KOHAVERSION;
1335 $kohaversion =~ s/(.*\..*)\.(.*)\.(.*)/$1$2$3/;
1336 if ( C4::Context->preference('Version') < $kohaversion ) {
1338 # database in need of version update; assume that
1339 # no API should be called while databsae is in
1340 # this condition.
1341 return ( "maintenance", undef, undef );
1344 # FIXME -- most of what follows is a copy-and-paste
1345 # of code from checkauth. There is an obvious need
1346 # for refactoring to separate the various parts of
1347 # the authentication code, but as of 2007-11-19 this
1348 # is deferred so as to not introduce bugs into the
1349 # regular authentication code for Koha 3.0.
1351 # see if we have a valid session cookie already
1352 # however, if a userid parameter is present (i.e., from
1353 # a form submission, assume that any current cookie
1354 # is to be ignored
1355 my $sessionID = undef;
1356 unless ( $query->param('userid') ) {
1357 $sessionID = $query->cookie("CGISESSID");
1359 if ( $sessionID && not( $cas && $query->param('PT') ) ) {
1360 my $session = get_session($sessionID);
1361 C4::Context->_new_userenv($sessionID);
1362 if ($session) {
1363 C4::Context::set_userenv(
1364 $session->param('number'), $session->param('id'),
1365 $session->param('cardnumber'), $session->param('firstname'),
1366 $session->param('surname'), $session->param('branch'),
1367 $session->param('branchname'), $session->param('flags'),
1368 $session->param('emailaddress'), $session->param('branchprinter')
1371 my $ip = $session->param('ip');
1372 my $lasttime = $session->param('lasttime');
1373 my $userid = $session->param('id');
1374 if ( $lasttime < time() - $timeout ) {
1376 # time out
1377 $session->delete();
1378 $session->flush;
1379 C4::Context->_unset_userenv($sessionID);
1380 $userid = undef;
1381 $sessionID = undef;
1382 return ( "expired", undef, undef );
1383 } elsif ( C4::Context->preference('SessionRestrictionByIP') && $ip ne $ENV{'REMOTE_ADDR'} ) {
1385 # IP address changed
1386 $session->delete();
1387 $session->flush;
1388 C4::Context->_unset_userenv($sessionID);
1389 $userid = undef;
1390 $sessionID = undef;
1391 return ( "expired", undef, undef );
1392 } else {
1393 my $cookie = $query->cookie(
1394 -name => 'CGISESSID',
1395 -value => $session->id,
1396 -HttpOnly => 1,
1398 $session->param( 'lasttime', time() );
1399 my $flags = haspermission( $userid, $flagsrequired );
1400 if ($flags) {
1401 return ( "ok", $cookie, $sessionID );
1402 } else {
1403 $session->delete();
1404 $session->flush;
1405 C4::Context->_unset_userenv($sessionID);
1406 $userid = undef;
1407 $sessionID = undef;
1408 return ( "failed", undef, undef );
1411 } else {
1412 return ( "expired", undef, undef );
1414 } else {
1416 # new login
1417 my $userid = $query->param('userid');
1418 my $password = $query->param('password');
1419 my ( $return, $cardnumber );
1421 # Proxy CAS auth
1422 if ( $cas && $query->param('PT') ) {
1423 my $retuserid;
1424 $debug and print STDERR "## check_api_auth - checking CAS\n";
1426 # In case of a CAS authentication, we use the ticket instead of the password
1427 my $PT = $query->param('PT');
1428 ( $return, $cardnumber, $userid ) = check_api_auth_cas( $dbh, $PT, $query ); # EXTERNAL AUTH
1429 } else {
1431 # User / password auth
1432 unless ( $userid and $password ) {
1434 # caller did something wrong, fail the authenticateion
1435 return ( "failed", undef, undef );
1437 ( $return, $cardnumber ) = checkpw( $dbh, $userid, $password, $query );
1440 if ( $return and haspermission( $userid, $flagsrequired ) ) {
1441 my $session = get_session("");
1442 return ( "failed", undef, undef ) unless $session;
1444 my $sessionID = $session->id;
1445 C4::Context->_new_userenv($sessionID);
1446 my $cookie = $query->cookie(
1447 -name => 'CGISESSID',
1448 -value => $sessionID,
1449 -HttpOnly => 1,
1451 if ( $return == 1 ) {
1452 my (
1453 $borrowernumber, $firstname, $surname,
1454 $userflags, $branchcode, $branchname,
1455 $branchprinter, $emailaddress
1457 my $sth =
1458 $dbh->prepare(
1459 "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=?"
1461 $sth->execute($userid);
1463 $borrowernumber, $firstname, $surname,
1464 $userflags, $branchcode, $branchname,
1465 $branchprinter, $emailaddress
1466 ) = $sth->fetchrow if ( $sth->rows );
1468 unless ( $sth->rows ) {
1469 my $sth = $dbh->prepare(
1470 "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=?"
1472 $sth->execute($cardnumber);
1474 $borrowernumber, $firstname, $surname,
1475 $userflags, $branchcode, $branchname,
1476 $branchprinter, $emailaddress
1477 ) = $sth->fetchrow if ( $sth->rows );
1479 unless ( $sth->rows ) {
1480 $sth->execute($userid);
1482 $borrowernumber, $firstname, $surname, $userflags,
1483 $branchcode, $branchname, $branchprinter, $emailaddress
1484 ) = $sth->fetchrow if ( $sth->rows );
1488 my $ip = $ENV{'REMOTE_ADDR'};
1490 # if they specify at login, use that
1491 if ( $query->param('branch') ) {
1492 $branchcode = $query->param('branch');
1493 $branchname = GetBranchName($branchcode);
1495 my $branches = GetBranches();
1496 my @branchesloop;
1497 foreach my $br ( keys %$branches ) {
1499 # now we work with the treatment of ip
1500 my $domain = $branches->{$br}->{'branchip'};
1501 if ( $domain && $ip =~ /^$domain/ ) {
1502 $branchcode = $branches->{$br}->{'branchcode'};
1504 # new op dev : add the branchprinter and branchname in the cookie
1505 $branchprinter = $branches->{$br}->{'branchprinter'};
1506 $branchname = $branches->{$br}->{'branchname'};
1509 $session->param( 'number', $borrowernumber );
1510 $session->param( 'id', $userid );
1511 $session->param( 'cardnumber', $cardnumber );
1512 $session->param( 'firstname', $firstname );
1513 $session->param( 'surname', $surname );
1514 $session->param( 'branch', $branchcode );
1515 $session->param( 'branchname', $branchname );
1516 $session->param( 'flags', $userflags );
1517 $session->param( 'emailaddress', $emailaddress );
1518 $session->param( 'ip', $session->remote_addr() );
1519 $session->param( 'lasttime', time() );
1520 } elsif ( $return == 2 ) {
1522 #We suppose the user is the superlibrarian
1523 $session->param( 'number', 0 );
1524 $session->param( 'id', C4::Context->config('user') );
1525 $session->param( 'cardnumber', C4::Context->config('user') );
1526 $session->param( 'firstname', C4::Context->config('user') );
1527 $session->param( 'surname', C4::Context->config('user') );
1528 $session->param( 'branch', 'NO_LIBRARY_SET' );
1529 $session->param( 'branchname', 'NO_LIBRARY_SET' );
1530 $session->param( 'flags', 1 );
1531 $session->param( 'emailaddress', C4::Context->preference('KohaAdminEmailAddress') );
1532 $session->param( 'ip', $session->remote_addr() );
1533 $session->param( 'lasttime', time() );
1535 C4::Context::set_userenv(
1536 $session->param('number'), $session->param('id'),
1537 $session->param('cardnumber'), $session->param('firstname'),
1538 $session->param('surname'), $session->param('branch'),
1539 $session->param('branchname'), $session->param('flags'),
1540 $session->param('emailaddress'), $session->param('branchprinter')
1542 return ( "ok", $cookie, $sessionID );
1543 } else {
1544 return ( "failed", undef, undef );
1549 =head2 check_cookie_auth
1551 ($status, $sessionId) = check_api_auth($cookie, $userflags);
1553 Given a CGISESSID cookie set during a previous login to Koha, determine
1554 if the user has the privileges specified by C<$userflags>.
1556 C<check_cookie_auth> is meant for authenticating special services
1557 such as tools/upload-file.pl that are invoked by other pages that
1558 have been authenticated in the usual way.
1560 Possible return values in C<$status> are:
1562 =over
1564 =item "ok" -- user authenticated; C<$sessionID> have valid values.
1566 =item "failed" -- credentials are not correct; C<$sessionid> are undef
1568 =item "maintenance" -- DB is in maintenance mode; no login possible at the moment
1570 =item "expired -- session cookie has expired; API user should resubmit userid and password
1572 =back
1574 =cut
1576 sub check_cookie_auth {
1577 my $cookie = shift;
1578 my $flagsrequired = shift;
1580 my $dbh = C4::Context->dbh;
1581 my $timeout = _timeout_syspref();
1583 unless ( C4::Context->preference('Version') ) {
1585 # database has not been installed yet
1586 return ( "maintenance", undef );
1588 my $kohaversion = C4::Context::KOHAVERSION;
1589 $kohaversion =~ s/(.*\..*)\.(.*)\.(.*)/$1$2$3/;
1590 if ( C4::Context->preference('Version') < $kohaversion ) {
1592 # database in need of version update; assume that
1593 # no API should be called while databsae is in
1594 # this condition.
1595 return ( "maintenance", undef );
1598 # FIXME -- most of what follows is a copy-and-paste
1599 # of code from checkauth. There is an obvious need
1600 # for refactoring to separate the various parts of
1601 # the authentication code, but as of 2007-11-23 this
1602 # is deferred so as to not introduce bugs into the
1603 # regular authentication code for Koha 3.0.
1605 # see if we have a valid session cookie already
1606 # however, if a userid parameter is present (i.e., from
1607 # a form submission, assume that any current cookie
1608 # is to be ignored
1609 unless ( defined $cookie and $cookie ) {
1610 return ( "failed", undef );
1612 my $sessionID = $cookie;
1613 my $session = get_session($sessionID);
1614 C4::Context->_new_userenv($sessionID);
1615 if ($session) {
1616 C4::Context::set_userenv(
1617 $session->param('number'), $session->param('id'),
1618 $session->param('cardnumber'), $session->param('firstname'),
1619 $session->param('surname'), $session->param('branch'),
1620 $session->param('branchname'), $session->param('flags'),
1621 $session->param('emailaddress'), $session->param('branchprinter')
1624 my $ip = $session->param('ip');
1625 my $lasttime = $session->param('lasttime');
1626 my $userid = $session->param('id');
1627 if ( $lasttime < time() - $timeout ) {
1629 # time out
1630 $session->delete();
1631 $session->flush;
1632 C4::Context->_unset_userenv($sessionID);
1633 $userid = undef;
1634 $sessionID = undef;
1635 return ("expired", undef);
1636 } elsif ( C4::Context->preference('SessionRestrictionByIP') && $ip ne $ENV{'REMOTE_ADDR'} ) {
1638 # IP address changed
1639 $session->delete();
1640 $session->flush;
1641 C4::Context->_unset_userenv($sessionID);
1642 $userid = undef;
1643 $sessionID = undef;
1644 return ( "expired", undef );
1645 } else {
1646 $session->param( 'lasttime', time() );
1647 my $flags = haspermission( $userid, $flagsrequired );
1648 if ($flags) {
1649 return ( "ok", $sessionID );
1650 } else {
1651 $session->delete();
1652 $session->flush;
1653 C4::Context->_unset_userenv($sessionID);
1654 $userid = undef;
1655 $sessionID = undef;
1656 return ( "failed", undef );
1659 } else {
1660 return ( "expired", undef );
1664 =head2 get_session
1666 use CGI::Session;
1667 my $session = get_session($sessionID);
1669 Given a session ID, retrieve the CGI::Session object used to store
1670 the session's state. The session object can be used to store
1671 data that needs to be accessed by different scripts during a
1672 user's session.
1674 If the C<$sessionID> parameter is an empty string, a new session
1675 will be created.
1677 =cut
1679 sub get_session {
1680 my $sessionID = shift;
1681 my $storage_method = C4::Context->preference('SessionStorage');
1682 my $dbh = C4::Context->dbh;
1683 my $session;
1684 if ( $storage_method eq 'mysql' ) {
1685 $session = new CGI::Session( "driver:MySQL;serializer:yaml;id:md5", $sessionID, { Handle => $dbh } );
1687 elsif ( $storage_method eq 'Pg' ) {
1688 $session = new CGI::Session( "driver:PostgreSQL;serializer:yaml;id:md5", $sessionID, { Handle => $dbh } );
1690 elsif ( $storage_method eq 'memcached' && C4::Context->ismemcached ) {
1691 $session = new CGI::Session( "driver:memcached;serializer:yaml;id:md5", $sessionID, { Memcached => C4::Context->memcached } );
1693 else {
1694 # catch all defaults to tmp should work on all systems
1695 $session = new CGI::Session( "driver:File;serializer:yaml;id:md5", $sessionID, { Directory => '/tmp' } );
1697 return $session;
1700 sub checkpw {
1701 my ( $dbh, $userid, $password, $query ) = @_;
1702 if ($ldap) {
1703 $debug and print STDERR "## checkpw - checking LDAP\n";
1704 my ( $retval, $retcard, $retuserid ) = checkpw_ldap(@_); # EXTERNAL AUTH
1705 return 0 if $retval == -1; # Incorrect password for LDAP login attempt
1706 ($retval) and return ( $retval, $retcard, $retuserid );
1709 if ( $cas && $query && $query->param('ticket') ) {
1710 $debug and print STDERR "## checkpw - checking CAS\n";
1712 # In case of a CAS authentication, we use the ticket instead of the password
1713 my $ticket = $query->param('ticket');
1714 $query->delete('ticket'); # remove ticket to come back to original URL
1715 my ( $retval, $retcard, $retuserid ) = checkpw_cas( $dbh, $ticket, $query ); # EXTERNAL AUTH
1716 ($retval) and return ( $retval, $retcard, $retuserid );
1717 return 0;
1720 # If we are in a shibboleth session (shibboleth is enabled, and a shibboleth match attribute is present)
1721 # Check for password to asertain whether we want to be testing against shibboleth or another method this
1722 # time around.
1723 if ( $shib && $shib_login && !$password ) {
1725 $debug and print STDERR "## checkpw - checking Shibboleth\n";
1727 # In case of a Shibboleth authentication, we expect a shibboleth user attribute
1728 # (defined under shibboleth mapping in koha-conf.xml) to contain the login of the
1729 # shibboleth-authenticated user
1731 # Then, we check if it matches a valid koha user
1732 if ($shib_login) {
1733 my ( $retval, $retcard, $retuserid ) = C4::Auth_with_shibboleth::checkpw_shib($shib_login); # EXTERNAL AUTH
1734 ($retval) and return ( $retval, $retcard, $retuserid );
1735 return 0;
1739 # INTERNAL AUTH
1740 return checkpw_internal(@_)
1743 sub checkpw_internal {
1744 my ( $dbh, $userid, $password ) = @_;
1746 if ( $userid && $userid eq C4::Context->config('user') ) {
1747 if ( $password && $password eq C4::Context->config('pass') ) {
1749 # Koha superuser account
1750 # C4::Context->set_userenv(0,0,C4::Context->config('user'),C4::Context->config('user'),C4::Context->config('user'),"",1);
1751 return 2;
1753 else {
1754 return 0;
1758 my $sth =
1759 $dbh->prepare(
1760 "select password,cardnumber,borrowernumber,userid,firstname,surname,branchcode,flags from borrowers where userid=?"
1762 $sth->execute($userid);
1763 if ( $sth->rows ) {
1764 my ( $stored_hash, $cardnumber, $borrowernumber, $userid, $firstname,
1765 $surname, $branchcode, $flags )
1766 = $sth->fetchrow;
1768 if ( checkpw_hash( $password, $stored_hash ) ) {
1770 C4::Context->set_userenv( "$borrowernumber", $userid, $cardnumber,
1771 $firstname, $surname, $branchcode, $flags );
1772 return 1, $cardnumber, $userid;
1775 $sth =
1776 $dbh->prepare(
1777 "select password,cardnumber,borrowernumber,userid, firstname,surname,branchcode,flags from borrowers where cardnumber=?"
1779 $sth->execute($userid);
1780 if ( $sth->rows ) {
1781 my ( $stored_hash, $cardnumber, $borrowernumber, $userid, $firstname,
1782 $surname, $branchcode, $flags )
1783 = $sth->fetchrow;
1785 if ( checkpw_hash( $password, $stored_hash ) ) {
1787 C4::Context->set_userenv( $borrowernumber, $userid, $cardnumber,
1788 $firstname, $surname, $branchcode, $flags );
1789 return 1, $cardnumber, $userid;
1792 if ( $userid && $userid eq 'demo'
1793 && "$password" eq 'demo'
1794 && C4::Context->config('demo') )
1797 # DEMO => the demo user is allowed to do everything (if demo set to 1 in koha.conf
1798 # some features won't be effective : modify systempref, modify MARC structure,
1799 return 2;
1801 return 0;
1804 sub checkpw_hash {
1805 my ( $password, $stored_hash ) = @_;
1807 return if $stored_hash eq '!';
1809 # check what encryption algorithm was implemented: Bcrypt - if the hash starts with '$2' it is Bcrypt else md5
1810 my $hash;
1811 if ( substr( $stored_hash, 0, 2 ) eq '$2' ) {
1812 $hash = hash_password( $password, $stored_hash );
1813 } else {
1814 $hash = md5_base64($password);
1816 return $hash eq $stored_hash;
1819 =head2 getuserflags
1821 my $authflags = getuserflags($flags, $userid, [$dbh]);
1823 Translates integer flags into permissions strings hash.
1825 C<$flags> is the integer userflags value ( borrowers.userflags )
1826 C<$userid> is the members.userid, used for building subpermissions
1827 C<$authflags> is a hashref of permissions
1829 =cut
1831 sub getuserflags {
1832 my $flags = shift;
1833 my $userid = shift;
1834 my $dbh = @_ ? shift : C4::Context->dbh;
1835 my $userflags;
1837 # I don't want to do this, but if someone logs in as the database
1838 # user, it would be preferable not to spam them to death with
1839 # numeric warnings. So, we make $flags numeric.
1840 no warnings 'numeric';
1841 $flags += 0;
1843 my $sth = $dbh->prepare("SELECT bit, flag, defaulton FROM userflags");
1844 $sth->execute;
1846 while ( my ( $bit, $flag, $defaulton ) = $sth->fetchrow ) {
1847 if ( ( $flags & ( 2**$bit ) ) || $defaulton ) {
1848 $userflags->{$flag} = 1;
1850 else {
1851 $userflags->{$flag} = 0;
1855 # get subpermissions and merge with top-level permissions
1856 my $user_subperms = get_user_subpermissions($userid);
1857 foreach my $module ( keys %$user_subperms ) {
1858 next if $userflags->{$module} == 1; # user already has permission for everything in this module
1859 $userflags->{$module} = $user_subperms->{$module};
1862 return $userflags;
1865 =head2 get_user_subpermissions
1867 $user_perm_hashref = get_user_subpermissions($userid);
1869 Given the userid (note, not the borrowernumber) of a staff user,
1870 return a hashref of hashrefs of the specific subpermissions
1871 accorded to the user. An example return is
1874 tools => {
1875 export_catalog => 1,
1876 import_patrons => 1,
1880 The top-level hash-key is a module or function code from
1881 userflags.flag, while the second-level key is a code
1882 from permissions.
1884 The results of this function do not give a complete picture
1885 of the functions that a staff user can access; it is also
1886 necessary to check borrowers.flags.
1888 =cut
1890 sub get_user_subpermissions {
1891 my $userid = shift;
1893 my $dbh = C4::Context->dbh;
1894 my $sth = $dbh->prepare( "SELECT flag, user_permissions.code
1895 FROM user_permissions
1896 JOIN permissions USING (module_bit, code)
1897 JOIN userflags ON (module_bit = bit)
1898 JOIN borrowers USING (borrowernumber)
1899 WHERE userid = ?" );
1900 $sth->execute($userid);
1902 my $user_perms = {};
1903 while ( my $perm = $sth->fetchrow_hashref ) {
1904 $user_perms->{ $perm->{'flag'} }->{ $perm->{'code'} } = 1;
1906 return $user_perms;
1909 =head2 get_all_subpermissions
1911 my $perm_hashref = get_all_subpermissions();
1913 Returns a hashref of hashrefs defining all specific
1914 permissions currently defined. The return value
1915 has the same structure as that of C<get_user_subpermissions>,
1916 except that the innermost hash value is the description
1917 of the subpermission.
1919 =cut
1921 sub get_all_subpermissions {
1922 my $dbh = C4::Context->dbh;
1923 my $sth = $dbh->prepare( "SELECT flag, code, description
1924 FROM permissions
1925 JOIN userflags ON (module_bit = bit)" );
1926 $sth->execute();
1928 my $all_perms = {};
1929 while ( my $perm = $sth->fetchrow_hashref ) {
1930 $all_perms->{ $perm->{'flag'} }->{ $perm->{'code'} } = $perm->{'description'};
1932 return $all_perms;
1935 =head2 haspermission
1937 $flags = ($userid, $flagsrequired);
1939 C<$userid> the userid of the member
1940 C<$flags> is a hashref of required flags like C<$borrower-&lt;{authflags}>
1942 Returns member's flags or 0 if a permission is not met.
1944 =cut
1946 sub haspermission {
1947 my ( $userid, $flagsrequired ) = @_;
1948 my $sth = C4::Context->dbh->prepare("SELECT flags FROM borrowers WHERE userid=?");
1949 $sth->execute($userid);
1950 my $row = $sth->fetchrow();
1951 my $flags = getuserflags( $row, $userid );
1952 if ( $userid eq C4::Context->config('user') ) {
1954 # Super User Account from /etc/koha.conf
1955 $flags->{'superlibrarian'} = 1;
1957 elsif ( $userid eq 'demo' && C4::Context->config('demo') ) {
1959 # Demo user that can do "anything" (demo=1 in /etc/koha.conf)
1960 $flags->{'superlibrarian'} = 1;
1963 return $flags if $flags->{superlibrarian};
1965 foreach my $module ( keys %$flagsrequired ) {
1966 my $subperm = $flagsrequired->{$module};
1967 if ( $subperm eq '*' ) {
1968 return 0 unless ( $flags->{$module} == 1 or ref( $flags->{$module} ) );
1969 } else {
1970 return 0 unless ( $flags->{$module} == 1 or
1971 ( ref( $flags->{$module} ) and
1972 exists $flags->{$module}->{$subperm} and
1973 $flags->{$module}->{$subperm} == 1
1978 return $flags;
1980 #FIXME - This fcn should return the failed permission so a suitable error msg can be delivered.
1983 sub getborrowernumber {
1984 my ($userid) = @_;
1985 my $userenv = C4::Context->userenv;
1986 if ( defined($userenv) && ref($userenv) eq 'HASH' && $userenv->{number} ) {
1987 return $userenv->{number};
1989 my $dbh = C4::Context->dbh;
1990 for my $field ( 'userid', 'cardnumber' ) {
1991 my $sth =
1992 $dbh->prepare("select borrowernumber from borrowers where $field=?");
1993 $sth->execute($userid);
1994 if ( $sth->rows ) {
1995 my ($bnumber) = $sth->fetchrow;
1996 return $bnumber;
1999 return 0;
2002 END { } # module clean-up code here (global destructor)
2004 __END__
2006 =head1 SEE ALSO
2008 CGI(3)
2010 C4::Output(3)
2012 Crypt::Eksblowfish::Bcrypt(3)
2014 Digest::MD5(3)
2016 =cut