Bug 13200 - Followup of Bug 12246 - noisy C4/Auth.pm
[koha.git] / C4 / Auth.pm
blob16d3ec2a56dd4e11a1c54797cf00c353db470924
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 under the
8 # terms of the GNU General Public License as published by the Free Software
9 # Foundation; either version 2 of the License, or (at your option) any later
10 # version.
12 # Koha is distributed in the hope that it will be useful, but WITHOUT ANY
13 # WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
14 # A PARTICULAR PURPOSE. See the GNU General Public License for more details.
16 # You should have received a copy of the GNU General Public License along
17 # with Koha; if not, write to the Free Software Foundation, Inc.,
18 # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
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 }
43 sub safe_exit {
44 if ( psgi_env ) { die 'psgi:exit' }
45 else { exit }
47 $VERSION = 3.07.00.049; # set version for version checking
49 $debug = $ENV{DEBUG};
50 @ISA = qw(Exporter);
51 @EXPORT = qw(&checkauth &get_template_and_user &haspermission &get_user_subpermissions);
52 @EXPORT_OK = qw(&check_api_auth &get_session &check_cookie_auth &checkpw &checkpw_internal &checkpw_hash
53 &get_all_subpermissions &get_user_subpermissions
55 %EXPORT_TAGS = ( EditPermissions => [qw(get_all_subpermissions get_user_subpermissions)] );
56 $ldap = C4::Context->config('useldapserver') || 0;
57 $cas = C4::Context->preference('casAuthentication');
58 $shib = C4::Context->config('useshibboleth') || 0;
59 $caslogout = C4::Context->preference('casLogout');
60 require C4::Auth_with_cas; # no import
61 if ($ldap) {
62 require C4::Auth_with_ldap;
63 import C4::Auth_with_ldap qw(checkpw_ldap);
65 if ($shib) {
66 require C4::Auth_with_shibboleth;
67 import C4::Auth_with_shibboleth
68 qw(shib_ok checkpw_shib logout_shib login_shib_url get_login_shib);
70 # Check for good config
71 if ( shib_ok() ) {
72 # Get shibboleth login attribute
73 $shib_login = get_login_shib();
75 # Bad config, disable shibboleth
76 else {
77 $shib = 0;
80 if ($cas) {
81 import C4::Auth_with_cas qw(check_api_auth_cas checkpw_cas login_cas logout_cas login_cas_url);
86 =head1 NAME
88 C4::Auth - Authenticates Koha users
90 =head1 SYNOPSIS
92 use CGI;
93 use C4::Auth;
94 use C4::Output;
96 my $query = new CGI;
98 my ($template, $borrowernumber, $cookie)
99 = get_template_and_user(
101 template_name => "opac-main.tt",
102 query => $query,
103 type => "opac",
104 authnotrequired => 0,
105 flagsrequired => {borrow => 1, catalogue => '*', tools => 'import_patrons' },
109 output_html_with_http_headers $query, $cookie, $template->output;
111 =head1 DESCRIPTION
113 The main function of this module is to provide
114 authentification. However the get_template_and_user function has
115 been provided so that a users login information is passed along
116 automatically. This gets loaded into the template.
118 =head1 FUNCTIONS
120 =head2 get_template_and_user
122 my ($template, $borrowernumber, $cookie)
123 = get_template_and_user(
125 template_name => "opac-main.tt",
126 query => $query,
127 type => "opac",
128 authnotrequired => 0,
129 flagsrequired => {borrow => 1, catalogue => '*', tools => 'import_patrons' },
133 This call passes the C<query>, C<flagsrequired> and C<authnotrequired>
134 to C<&checkauth> (in this module) to perform authentification.
135 See C<&checkauth> for an explanation of these parameters.
137 The C<template_name> is then used to find the correct template for
138 the page. The authenticated users details are loaded onto the
139 template in the HTML::Template LOOP variable C<USER_INFO>. Also the
140 C<sessionID> is passed to the template. This can be used in templates
141 if cookies are disabled. It needs to be put as and input to every
142 authenticated page.
144 More information on the C<gettemplate> sub can be found in the
145 Output.pm module.
147 =cut
149 sub get_template_and_user {
151 my $in = shift;
152 my ( $user, $cookie, $sessionID, $flags );
154 C4::Context->interface($in->{type});
156 $in->{'authnotrequired'} ||= 0;
157 my $template = C4::Templates::gettemplate(
158 $in->{'template_name'},
159 $in->{'type'},
160 $in->{'query'},
161 $in->{'is_plugin'}
164 if ( $in->{'template_name'} !~m/maintenance/ ) {
165 ( $user, $cookie, $sessionID, $flags ) = checkauth(
166 $in->{'query'},
167 $in->{'authnotrequired'},
168 $in->{'flagsrequired'},
169 $in->{'type'}
173 my $borrowernumber;
174 if ($user) {
175 require C4::Members;
176 # It's possible for $user to be the borrowernumber if they don't have a
177 # userid defined (and are logging in through some other method, such
178 # as SSL certs against an email address)
179 $borrowernumber = getborrowernumber($user) if defined($user);
180 if (!defined($borrowernumber) && defined($user)) {
181 my $borrower = C4::Members::GetMember(borrowernumber => $user);
182 if ($borrower) {
183 $borrowernumber = $user;
184 # A bit of a hack, but I don't know there's a nicer way
185 # to do it.
186 $user = $borrower->{firstname} . ' ' . $borrower->{surname};
190 # user info
191 $template->param( loggedinusername => $user );
192 $template->param( sessionID => $sessionID );
194 my ($total, $pubshelves, $barshelves) = C4::VirtualShelves::GetSomeShelfNames($borrowernumber, 'MASTHEAD');
195 $template->param(
196 pubshelves => $total->{pubtotal},
197 pubshelvesloop => $pubshelves,
198 barshelves => $total->{bartotal},
199 barshelvesloop => $barshelves,
202 my ( $borr ) = C4::Members::GetMemberDetails( $borrowernumber );
203 my @bordat;
204 $bordat[0] = $borr;
205 $template->param( "USER_INFO" => \@bordat );
207 my $all_perms = get_all_subpermissions();
209 my @flagroots = qw(circulate catalogue parameters borrowers permissions reserveforothers borrow
210 editcatalogue updatecharges management tools editauthorities serials reports acquisition);
211 # We are going to use the $flags returned by checkauth
212 # to create the template's parameters that will indicate
213 # which menus the user can access.
214 if ( $flags && $flags->{superlibrarian}==1 ) {
215 $template->param( CAN_user_circulate => 1 );
216 $template->param( CAN_user_catalogue => 1 );
217 $template->param( CAN_user_parameters => 1 );
218 $template->param( CAN_user_borrowers => 1 );
219 $template->param( CAN_user_permissions => 1 );
220 $template->param( CAN_user_reserveforothers => 1 );
221 $template->param( CAN_user_borrow => 1 );
222 $template->param( CAN_user_editcatalogue => 1 );
223 $template->param( CAN_user_updatecharges => 1 );
224 $template->param( CAN_user_acquisition => 1 );
225 $template->param( CAN_user_management => 1 );
226 $template->param( CAN_user_tools => 1 );
227 $template->param( CAN_user_editauthorities => 1 );
228 $template->param( CAN_user_serials => 1 );
229 $template->param( CAN_user_reports => 1 );
230 $template->param( CAN_user_staffaccess => 1 );
231 $template->param( CAN_user_plugins => 1 );
232 $template->param( CAN_user_coursereserves => 1 );
233 foreach my $module (keys %$all_perms) {
234 foreach my $subperm (keys %{ $all_perms->{$module} }) {
235 $template->param( "CAN_user_${module}_${subperm}" => 1 );
240 if ( $flags ) {
241 foreach my $module (keys %$all_perms) {
242 if ( $flags->{$module} == 1) {
243 foreach my $subperm (keys %{ $all_perms->{$module} }) {
244 $template->param( "CAN_user_${module}_${subperm}" => 1 );
246 } elsif ( ref($flags->{$module}) ) {
247 foreach my $subperm (keys %{ $flags->{$module} } ) {
248 $template->param( "CAN_user_${module}_${subperm}" => 1 );
254 if ($flags) {
255 foreach my $module (keys %$flags) {
256 if ( $flags->{$module} == 1 or ref($flags->{$module}) ) {
257 $template->param( "CAN_user_$module" => 1 );
258 if ($module eq "parameters") {
259 $template->param( CAN_user_management => 1 );
264 # Logged-in opac search history
265 # If the requested template is an opac one and opac search history is enabled
266 if ($in->{type} eq 'opac' && C4::Context->preference('EnableOpacSearchHistory')) {
267 my $dbh = C4::Context->dbh;
268 my $query = "SELECT COUNT(*) FROM search_history WHERE userid=?";
269 my $sth = $dbh->prepare($query);
270 $sth->execute($borrowernumber);
272 # If at least one search has already been performed
273 if ($sth->fetchrow_array > 0) {
274 # We show the link in opac
275 $template->param( EnableOpacSearchHistory => 1 );
278 # And if there are searches performed when the user was not logged in,
279 # we add them to the logged-in search history
280 my @recentSearches = C4::Search::History::get_from_session({ cgi => $in->{'query'} });
281 if (@recentSearches) {
282 my $dbh = C4::Context->dbh;
283 my $query = q{
284 INSERT INTO search_history(userid, sessionid, query_desc, query_cgi, type, total, time )
285 VALUES (?, ?, ?, ?, ?, ?, ?)
288 my $sth = $dbh->prepare($query);
289 $sth->execute( $borrowernumber,
290 $in->{query}->cookie("CGISESSID"),
291 $_->{query_desc},
292 $_->{query_cgi},
293 $_->{type} || 'biblio',
294 $_->{total},
295 $_->{time},
296 ) foreach @recentSearches;
298 # clear out the search history from the session now that
299 # we've saved it to the database
300 C4::Search::History::set_to_session({ cgi => $in->{'query'}, search_history => [] });
302 } elsif ( $in->{type} eq 'intranet' and C4::Context->preference('EnableSearchHistory') ) {
303 $template->param( EnableSearchHistory => 1 );
306 else { # if this is an anonymous session, setup to display public lists...
308 # If shibboleth is enabled, and we're in an anonymous session, we should allow
309 # the user to attemp login via shibboleth.
310 if ( $shib ) {
311 $template->param( shibbolethAuthentication => $shib,
312 shibbolethLoginUrl => login_shib_url($in->{'query'}),
314 # If shibboleth is enabled and we have a shibboleth login attribute,
315 # but we are in an anonymous session, then we clearly have an invalid
316 # shibboleth koha account.
317 if ( $shib_login ) {
318 $template->param( invalidShibLogin => '1');
322 $template->param( sessionID => $sessionID );
324 my ($total, $pubshelves) = C4::VirtualShelves::GetSomeShelfNames(undef, 'MASTHEAD');
325 $template->param(
326 pubshelves => $total->{pubtotal},
327 pubshelvesloop => $pubshelves,
330 # Anonymous opac search history
331 # If opac search history is enabled and at least one search has already been performed
332 if (C4::Context->preference('EnableOpacSearchHistory')) {
333 my @recentSearches = C4::Search::History::get_from_session({ cgi => $in->{'query'} });
334 if (@recentSearches) {
335 $template->param(EnableOpacSearchHistory => 1);
339 if(C4::Context->preference('dateformat')){
340 $template->param(dateformat => C4::Context->preference('dateformat'))
343 # these template parameters are set the same regardless of $in->{'type'}
345 # Set the using_https variable for templates
346 # FIXME Under Plack the CGI->https method always returns 'OFF'
347 my $https = $in->{query}->https();
348 my $using_https = (defined $https and $https ne 'OFF') ? 1 : 0;
350 $template->param(
351 "BiblioDefaultView".C4::Context->preference("BiblioDefaultView") => 1,
352 EnhancedMessagingPreferences => C4::Context->preference('EnhancedMessagingPreferences'),
353 GoogleJackets => C4::Context->preference("GoogleJackets"),
354 OpenLibraryCovers => C4::Context->preference("OpenLibraryCovers"),
355 KohaAdminEmailAddress => "" . C4::Context->preference("KohaAdminEmailAddress"),
356 LoginBranchcode => (C4::Context->userenv?C4::Context->userenv->{"branch"}:undef),
357 LoginFirstname => (C4::Context->userenv?C4::Context->userenv->{"firstname"}:"Bel"),
358 LoginSurname => C4::Context->userenv?C4::Context->userenv->{"surname"}:"Inconnu",
359 emailaddress => C4::Context->userenv?C4::Context->userenv->{"emailaddress"}:undef,
360 loggedinpersona => C4::Context->userenv?C4::Context->userenv->{"persona"}:undef,
361 TagsEnabled => C4::Context->preference("TagsEnabled"),
362 hide_marc => C4::Context->preference("hide_marc"),
363 item_level_itypes => C4::Context->preference('item-level_itypes'),
364 patronimages => C4::Context->preference("patronimages"),
365 singleBranchMode => C4::Context->preference("singleBranchMode"),
366 XSLTDetailsDisplay => C4::Context->preference("XSLTDetailsDisplay"),
367 XSLTResultsDisplay => C4::Context->preference("XSLTResultsDisplay"),
368 using_https => $using_https,
369 noItemTypeImages => C4::Context->preference("noItemTypeImages"),
370 marcflavour => C4::Context->preference("marcflavour"),
371 persona => C4::Context->preference("persona"),
373 if ( $in->{'type'} eq "intranet" ) {
374 $template->param(
375 AmazonCoverImages => C4::Context->preference("AmazonCoverImages"),
376 AutoLocation => C4::Context->preference("AutoLocation"),
377 "BiblioDefaultView".C4::Context->preference("IntranetBiblioDefaultView") => 1,
378 CalendarFirstDayOfWeek => (C4::Context->preference("CalendarFirstDayOfWeek") eq "Sunday")?0:1,
379 CircAutocompl => C4::Context->preference("CircAutocompl"),
380 FRBRizeEditions => C4::Context->preference("FRBRizeEditions"),
381 IndependentBranches => C4::Context->preference("IndependentBranches"),
382 IntranetNav => C4::Context->preference("IntranetNav"),
383 IntranetmainUserblock => C4::Context->preference("IntranetmainUserblock"),
384 LibraryName => C4::Context->preference("LibraryName"),
385 LoginBranchname => (C4::Context->userenv?C4::Context->userenv->{"branchname"}:undef),
386 advancedMARCEditor => C4::Context->preference("advancedMARCEditor"),
387 canreservefromotherbranches => C4::Context->preference('canreservefromotherbranches'),
388 intranetcolorstylesheet => C4::Context->preference("intranetcolorstylesheet"),
389 IntranetFavicon => C4::Context->preference("IntranetFavicon"),
390 intranetreadinghistory => C4::Context->preference("intranetreadinghistory"),
391 intranetstylesheet => C4::Context->preference("intranetstylesheet"),
392 IntranetUserCSS => C4::Context->preference("IntranetUserCSS"),
393 intranetuserjs => C4::Context->preference("intranetuserjs"),
394 intranetbookbag => C4::Context->preference("intranetbookbag"),
395 suggestion => C4::Context->preference("suggestion"),
396 virtualshelves => C4::Context->preference("virtualshelves"),
397 StaffSerialIssueDisplayCount => C4::Context->preference("StaffSerialIssueDisplayCount"),
398 EasyAnalyticalRecords => C4::Context->preference('EasyAnalyticalRecords'),
399 LocalCoverImages => C4::Context->preference('LocalCoverImages'),
400 OPACLocalCoverImages => C4::Context->preference('OPACLocalCoverImages'),
401 AllowMultipleCovers => C4::Context->preference('AllowMultipleCovers'),
402 EnableBorrowerFiles => C4::Context->preference('EnableBorrowerFiles'),
403 UseKohaPlugins => C4::Context->preference('UseKohaPlugins'),
404 UseCourseReserves => C4::Context->preference("UseCourseReserves"),
407 else {
408 warn "template type should be OPAC, here it is=[" . $in->{'type'} . "]" unless ( $in->{'type'} eq 'opac' );
409 #TODO : replace LibraryName syspref with 'system name', and remove this html processing
410 my $LibraryNameTitle = C4::Context->preference("LibraryName");
411 $LibraryNameTitle =~ s/<(?:\/?)(?:br|p)\s*(?:\/?)>/ /sgi;
412 $LibraryNameTitle =~ s/<(?:[^<>'"]|'(?:[^']*)'|"(?:[^"]*)")*>//sg;
413 # clean up the busc param in the session if the page is not opac-detail and not the "add to list" page
414 if ( C4::Context->preference("OpacBrowseResults")
415 && $in->{'template_name'} =~ /opac-(.+)\.(?:tt|tmpl)$/ ) {
416 my $pagename = $1;
417 unless ( $pagename =~ /^(?:MARC|ISBD)?detail$/
418 or $pagename =~ /^addbybiblionumber$/ ) {
419 my $sessionSearch = get_session($sessionID || $in->{'query'}->cookie("CGISESSID"));
420 $sessionSearch->clear(["busc"]) if ($sessionSearch->param("busc"));
423 # variables passed from CGI: opac_css_override and opac_search_limits.
424 my $opac_search_limit = $ENV{'OPAC_SEARCH_LIMIT'};
425 my $opac_limit_override = $ENV{'OPAC_LIMIT_OVERRIDE'};
426 my $opac_name = '';
427 if (
428 ($opac_limit_override && $opac_search_limit && $opac_search_limit =~ /branch:(\w+)/) ||
429 ($in->{'query'}->param('limit') && $in->{'query'}->param('limit') =~ /branch:(\w+)/) ||
430 ($in->{'query'}->param('multibranchlimit') && $in->{'query'}->param('multibranchlimit') =~ /multibranchlimit-(\w+)/)
432 $opac_name = $1; # opac_search_limit is a branch, so we use it.
433 } elsif ( $in->{'query'}->param('multibranchlimit') ) {
434 $opac_name = $in->{'query'}->param('multibranchlimit');
435 } elsif (C4::Context->preference("SearchMyLibraryFirst") && C4::Context->userenv && C4::Context->userenv->{'branch'}) {
436 $opac_name = C4::Context->userenv->{'branch'};
438 # FIXME Under Plack the CGI->https method always returns 'OFF' ($using_https will be set to 0 in this case)
439 my $opac_base_url = C4::Context->preference("OPACBaseURL"); #FIXME uses $using_https below as well
440 if (!$opac_base_url){
441 $opac_base_url = $ENV{'SERVER_NAME'} . ($ENV{'SERVER_PORT'} eq ($using_https ? "443" : "80") ? '' : ":$ENV{'SERVER_PORT'}");
443 $template->param(
444 opaccolorstylesheet => C4::Context->preference("opaccolorstylesheet"),
445 AnonSuggestions => "" . C4::Context->preference("AnonSuggestions"),
446 AuthorisedValueImages => C4::Context->preference("AuthorisedValueImages"),
447 BranchesLoop => GetBranchesLoop($opac_name),
448 BranchCategoriesLoop => GetBranchCategories( 'searchdomain', 1, $opac_name ),
449 CalendarFirstDayOfWeek => (C4::Context->preference("CalendarFirstDayOfWeek") eq "Sunday")?0:1,
450 LibraryName => "" . C4::Context->preference("LibraryName"),
451 LibraryNameTitle => "" . $LibraryNameTitle,
452 LoginBranchname => C4::Context->userenv?C4::Context->userenv->{"branchname"}:"",
453 OPACAmazonCoverImages => C4::Context->preference("OPACAmazonCoverImages"),
454 OPACFRBRizeEditions => C4::Context->preference("OPACFRBRizeEditions"),
455 OpacHighlightedWords => C4::Context->preference("OpacHighlightedWords"),
456 OPACItemHolds => C4::Context->preference("OPACItemHolds"),
457 OPACShelfBrowser => "". C4::Context->preference("OPACShelfBrowser"),
458 OPACURLOpenInNewWindow => "" . C4::Context->preference("OPACURLOpenInNewWindow"),
459 OPACUserCSS => "". C4::Context->preference("OPACUserCSS"),
460 OPACViewOthersSuggestions => "" . C4::Context->preference("OPACViewOthersSuggestions"),
461 OpacAuthorities => C4::Context->preference("OpacAuthorities"),
462 OPACBaseURL => ($using_https ? "https://" : "http://") . $opac_base_url,
463 opac_css_override => $ENV{'OPAC_CSS_OVERRIDE'},
464 opac_search_limit => $opac_search_limit,
465 opac_limit_override => $opac_limit_override,
466 OpacBrowser => C4::Context->preference("OpacBrowser"),
467 OpacCloud => C4::Context->preference("OpacCloud"),
468 OpacKohaUrl => C4::Context->preference("OpacKohaUrl"),
469 OpacMainUserBlock => "" . C4::Context->preference("OpacMainUserBlock"),
470 OpacNav => "" . C4::Context->preference("OpacNav"),
471 OpacNavRight => "" . C4::Context->preference("OpacNavRight"),
472 OpacNavBottom => "" . C4::Context->preference("OpacNavBottom"),
473 OpacPasswordChange => C4::Context->preference("OpacPasswordChange"),
474 OPACPatronDetails => C4::Context->preference("OPACPatronDetails"),
475 OPACPrivacy => C4::Context->preference("OPACPrivacy"),
476 OPACFinesTab => C4::Context->preference("OPACFinesTab"),
477 OpacTopissue => C4::Context->preference("OpacTopissue"),
478 RequestOnOpac => C4::Context->preference("RequestOnOpac"),
479 'Version' => C4::Context->preference('Version'),
480 hidelostitems => C4::Context->preference("hidelostitems"),
481 mylibraryfirst => (C4::Context->preference("SearchMyLibraryFirst") && C4::Context->userenv) ? C4::Context->userenv->{'branch'} : '',
482 opaclayoutstylesheet => "" . C4::Context->preference("opaclayoutstylesheet"),
483 opacbookbag => "" . C4::Context->preference("opacbookbag"),
484 opaccredits => "" . C4::Context->preference("opaccredits"),
485 OpacFavicon => C4::Context->preference("OpacFavicon"),
486 opacheader => "" . C4::Context->preference("opacheader"),
487 opaclanguagesdisplay => "" . C4::Context->preference("opaclanguagesdisplay"),
488 opacreadinghistory => C4::Context->preference("opacreadinghistory"),
489 opacuserjs => C4::Context->preference("opacuserjs"),
490 opacuserlogin => "" . C4::Context->preference("opacuserlogin"),
491 ShowReviewer => C4::Context->preference("ShowReviewer"),
492 ShowReviewerPhoto => C4::Context->preference("ShowReviewerPhoto"),
493 suggestion => "" . C4::Context->preference("suggestion"),
494 virtualshelves => "" . C4::Context->preference("virtualshelves"),
495 OPACSerialIssueDisplayCount => C4::Context->preference("OPACSerialIssueDisplayCount"),
496 OPACXSLTDetailsDisplay => C4::Context->preference("OPACXSLTDetailsDisplay"),
497 OPACXSLTResultsDisplay => C4::Context->preference("OPACXSLTResultsDisplay"),
498 SyndeticsClientCode => C4::Context->preference("SyndeticsClientCode"),
499 SyndeticsEnabled => C4::Context->preference("SyndeticsEnabled"),
500 SyndeticsCoverImages => C4::Context->preference("SyndeticsCoverImages"),
501 SyndeticsTOC => C4::Context->preference("SyndeticsTOC"),
502 SyndeticsSummary => C4::Context->preference("SyndeticsSummary"),
503 SyndeticsEditions => C4::Context->preference("SyndeticsEditions"),
504 SyndeticsExcerpt => C4::Context->preference("SyndeticsExcerpt"),
505 SyndeticsReviews => C4::Context->preference("SyndeticsReviews"),
506 SyndeticsAuthorNotes => C4::Context->preference("SyndeticsAuthorNotes"),
507 SyndeticsAwards => C4::Context->preference("SyndeticsAwards"),
508 SyndeticsSeries => C4::Context->preference("SyndeticsSeries"),
509 SyndeticsCoverImageSize => C4::Context->preference("SyndeticsCoverImageSize"),
510 OPACLocalCoverImages => C4::Context->preference("OPACLocalCoverImages"),
511 PatronSelfRegistration => C4::Context->preference("PatronSelfRegistration"),
512 PatronSelfRegistrationDefaultCategory => C4::Context->preference("PatronSelfRegistrationDefaultCategory"),
515 $template->param(OpacPublic => '1') if ($user || C4::Context->preference("OpacPublic"));
518 # Check if we were asked using parameters to force a specific language
519 if ( defined $in->{'query'}->param('language') ) {
520 # Extract the language, let C4::Languages::getlanguage choose
521 # what to do
522 my $language = C4::Languages::getlanguage($in->{'query'});
523 my $languagecookie = C4::Templates::getlanguagecookie($in->{'query'},$language);
524 if ( ref $cookie eq 'ARRAY' ) {
525 push @{ $cookie }, $languagecookie;
526 } else {
527 $cookie = [$cookie, $languagecookie];
531 return ( $template, $borrowernumber, $cookie, $flags);
534 =head2 checkauth
536 ($userid, $cookie, $sessionID) = &checkauth($query, $noauth, $flagsrequired, $type);
538 Verifies that the user is authorized to run this script. If
539 the user is authorized, a (userid, cookie, session-id, flags)
540 quadruple is returned. If the user is not authorized but does
541 not have the required privilege (see $flagsrequired below), it
542 displays an error page and exits. Otherwise, it displays the
543 login page and exits.
545 Note that C<&checkauth> will return if and only if the user
546 is authorized, so it should be called early on, before any
547 unfinished operations (e.g., if you've opened a file, then
548 C<&checkauth> won't close it for you).
550 C<$query> is the CGI object for the script calling C<&checkauth>.
552 The C<$noauth> argument is optional. If it is set, then no
553 authorization is required for the script.
555 C<&checkauth> fetches user and session information from C<$query> and
556 ensures that the user is authorized to run scripts that require
557 authorization.
559 The C<$flagsrequired> argument specifies the required privileges
560 the user must have if the username and password are correct.
561 It should be specified as a reference-to-hash; keys in the hash
562 should be the "flags" for the user, as specified in the Members
563 intranet module. Any key specified must correspond to a "flag"
564 in the userflags table. E.g., { circulate => 1 } would specify
565 that the user must have the "circulate" privilege in order to
566 proceed. To make sure that access control is correct, the
567 C<$flagsrequired> parameter must be specified correctly.
569 Koha also has a concept of sub-permissions, also known as
570 granular permissions. This makes the value of each key
571 in the C<flagsrequired> hash take on an additional
572 meaning, i.e.,
576 The user must have access to all subfunctions of the module
577 specified by the hash key.
581 The user must have access to at least one subfunction of the module
582 specified by the hash key.
584 specific permission, e.g., 'export_catalog'
586 The user must have access to the specific subfunction list, which
587 must correspond to a row in the permissions table.
589 The C<$type> argument specifies whether the template should be
590 retrieved from the opac or intranet directory tree. "opac" is
591 assumed if it is not specified; however, if C<$type> is specified,
592 "intranet" is assumed if it is not "opac".
594 If C<$query> does not have a valid session ID associated with it
595 (i.e., the user has not logged in) or if the session has expired,
596 C<&checkauth> presents the user with a login page (from the point of
597 view of the original script, C<&checkauth> does not return). Once the
598 user has authenticated, C<&checkauth> restarts the original script
599 (this time, C<&checkauth> returns).
601 The login page is provided using a HTML::Template, which is set in the
602 systempreferences table or at the top of this file. The variable C<$type>
603 selects which template to use, either the opac or the intranet
604 authentification template.
606 C<&checkauth> returns a user ID, a cookie, and a session ID. The
607 cookie should be sent back to the browser; it verifies that the user
608 has authenticated.
610 =cut
612 sub _version_check {
613 my $type = shift;
614 my $query = shift;
615 my $version;
616 # If Version syspref is unavailable, it means Koha is beeing installed,
617 # and so we must redirect to OPAC maintenance page or to the WebInstaller
618 # also, if OpacMaintenance is ON, OPAC should redirect to maintenance
619 if (C4::Context->preference('OpacMaintenance') && $type eq 'opac') {
620 warn "OPAC Install required, redirecting to maintenance";
621 print $query->redirect("/cgi-bin/koha/maintenance.pl");
622 safe_exit;
624 unless ( $version = C4::Context->preference('Version') ) { # assignment, not comparison
625 if ( $type ne 'opac' ) {
626 warn "Install required, redirecting to Installer";
627 print $query->redirect("/cgi-bin/koha/installer/install.pl");
628 } else {
629 warn "OPAC Install required, redirecting to maintenance";
630 print $query->redirect("/cgi-bin/koha/maintenance.pl");
632 safe_exit;
635 # check that database and koha version are the same
636 # there is no DB version, it's a fresh install,
637 # go to web installer
638 # there is a DB version, compare it to the code version
639 my $kohaversion=C4::Context::KOHAVERSION;
640 # remove the 3 last . to have a Perl number
641 $kohaversion =~ s/(.*\..*)\.(.*)\.(.*)/$1$2$3/;
642 $debug and print STDERR "kohaversion : $kohaversion\n";
643 if ($version < $kohaversion){
644 my $warning = "Database update needed, redirecting to %s. Database is $version and Koha is $kohaversion";
645 if ($type ne 'opac'){
646 warn sprintf($warning, 'Installer');
647 print $query->redirect("/cgi-bin/koha/installer/install.pl?step=3");
648 } else {
649 warn sprintf("OPAC: " . $warning, 'maintenance');
650 print $query->redirect("/cgi-bin/koha/maintenance.pl");
652 safe_exit;
656 sub _session_log {
657 (@_) or return 0;
658 open my $fh, '>>', "/tmp/sessionlog" or warn "ERROR: Cannot append to /tmp/sessionlog";
659 printf $fh join("\n",@_);
660 close $fh;
663 sub _timeout_syspref {
664 my $timeout = C4::Context->preference('timeout') || 600;
665 # value in days, convert in seconds
666 if ($timeout =~ /(\d+)[dD]/) {
667 $timeout = $1 * 86400;
669 return $timeout;
672 sub checkauth {
673 my $query = shift;
674 $debug and warn "Checking Auth";
675 # $authnotrequired will be set for scripts which will run without authentication
676 my $authnotrequired = shift;
677 my $flagsrequired = shift;
678 my $type = shift;
679 my $persona = shift;
680 $type = 'opac' unless $type;
682 my $dbh = C4::Context->dbh;
683 my $timeout = _timeout_syspref();
685 _version_check($type,$query);
686 # state variables
687 my $loggedin = 0;
688 my %info;
689 my ( $userid, $cookie, $sessionID, $flags, $barshelves, $pubshelves );
690 my $logout = $query->param('logout.x');
692 my $anon_search_history;
694 # This parameter is the name of the CAS server we want to authenticate against,
695 # when using authentication against multiple CAS servers, as configured in Auth_cas_servers.yaml
696 my $casparam = $query->param('cas');
697 my $q_userid = $query->param('userid') // '';
699 # Basic authentication is incompatible with the use of Shibboleth,
700 # as Shibboleth may return REMOTE_USER as a Shibboleth attribute,
701 # and it may not be the attribute we want to use to match the koha login.
703 # Also, do not consider an empty REMOTE_USER.
705 # Finally, after those tests, we can assume (although if it would be better with
706 # a syspref) that if we get a REMOTE_USER, that's from basic authentication,
707 # and we can affect it to $userid.
708 if ( !$shib and defined($ENV{'REMOTE_USER'}) and $ENV{'REMOTE_USER'} ne '' and $userid = $ENV{'REMOTE_USER'} ) {
710 # Using Basic Authentication, no cookies required
711 $cookie = $query->cookie(
712 -name => 'CGISESSID',
713 -value => '',
714 -expires => '',
715 -HttpOnly => 1,
717 $loggedin = 1;
719 elsif ( $persona ){
720 # we dont want to set a session because we are being called by a persona callback
722 elsif ( $sessionID = $query->cookie("CGISESSID") )
723 { # assignment, not comparison
724 my $session = get_session($sessionID);
725 C4::Context->_new_userenv($sessionID);
726 my ($ip, $lasttime, $sessiontype);
727 my $s_userid = '';
728 if ($session){
729 $s_userid = $session->param('id') // '';
730 C4::Context::set_userenv(
731 $session->param('number'), $s_userid,
732 $session->param('cardnumber'), $session->param('firstname'),
733 $session->param('surname'), $session->param('branch'),
734 $session->param('branchname'), $session->param('flags'),
735 $session->param('emailaddress'), $session->param('branchprinter'),
736 $session->param('persona'), $session->param('shibboleth')
738 C4::Context::set_shelves_userenv('bar',$session->param('barshelves'));
739 C4::Context::set_shelves_userenv('pub',$session->param('pubshelves'));
740 C4::Context::set_shelves_userenv('tot',$session->param('totshelves'));
741 $debug and printf STDERR "AUTH_SESSION: (%s)\t%s %s - %s\n", map {$session->param($_)} qw(cardnumber firstname surname branch) ;
742 $ip = $session->param('ip');
743 $lasttime = $session->param('lasttime');
744 $userid = $s_userid;
745 $sessiontype = $session->param('sessiontype') || '';
747 if ( ( $query->param('koha_login_context') && ($q_userid ne $s_userid) )
748 || ( $cas && $query->param('ticket') ) || ( $shib && $shib_login && !$logout ) ) {
749 #if a user enters an id ne to the id in the current session, we need to log them in...
750 #first we need to clear the anonymous session...
751 $debug and warn "query id = $q_userid but session id = $s_userid";
752 $anon_search_history = $session->param('search_history');
753 $session->delete();
754 $session->flush;
755 C4::Context->_unset_userenv($sessionID);
756 $sessionID = undef;
757 $userid = undef;
759 elsif ($logout) {
760 # voluntary logout the user
761 # check wether the user was using their shibboleth session or a local one
762 my $shibSuccess = C4::Context->userenv->{'shibboleth'};
763 $session->delete();
764 $session->flush;
765 C4::Context->_unset_userenv($sessionID);
766 #_session_log(sprintf "%20s from %16s logged out at %30s (manually).\n", $userid,$ip,(strftime "%c",localtime));
767 $sessionID = undef;
768 $userid = undef;
770 if ($cas and $caslogout) {
771 logout_cas($query);
774 # If we are in a shibboleth session (shibboleth is enabled, a shibboleth match attribute is set and matches koha matchpoint)
775 if ( $shib and $shib_login and $shibSuccess and $type eq 'opac') {
776 # (Note: $type eq 'opac' condition should be removed when shibboleth authentication for intranet will be implemented)
777 logout_shib($query);
780 elsif ( !$lasttime || ($lasttime < time() - $timeout) ) {
781 # timed logout
782 $info{'timed_out'} = 1;
783 if ($session) {
784 $session->delete();
785 $session->flush;
787 C4::Context->_unset_userenv($sessionID);
788 #_session_log(sprintf "%20s from %16s logged out at %30s (inactivity).\n", $userid,$ip,(strftime "%c",localtime));
789 $userid = undef;
790 $sessionID = undef;
792 elsif ( $ip ne $ENV{'REMOTE_ADDR'} ) {
793 # Different ip than originally logged in from
794 $info{'oldip'} = $ip;
795 $info{'newip'} = $ENV{'REMOTE_ADDR'};
796 $info{'different_ip'} = 1;
797 $session->delete();
798 $session->flush;
799 C4::Context->_unset_userenv($sessionID);
800 #_session_log(sprintf "%20s from %16s logged out at %30s (ip changed to %16s).\n", $userid,$ip,(strftime "%c",localtime), $info{'newip'});
801 $sessionID = undef;
802 $userid = undef;
804 else {
805 $cookie = $query->cookie(
806 -name => 'CGISESSID',
807 -value => $session->id,
808 -HttpOnly => 1
810 $session->param( 'lasttime', time() );
811 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...
812 $flags = haspermission($userid, $flagsrequired);
813 if ($flags) {
814 $loggedin = 1;
815 } else {
816 $info{'nopermission'} = 1;
821 unless ($userid || $sessionID) {
823 #we initiate a session prior to checking for a username to allow for anonymous sessions...
824 my $session = get_session("") or die "Auth ERROR: Cannot get_session()";
826 # Save anonymous search history in new session so it can be retrieved
827 # by get_template_and_user to store it in user's search history after
828 # a successful login.
829 if ($anon_search_history) {
830 $session->param('search_history', $anon_search_history);
833 my $sessionID = $session->id;
834 C4::Context->_new_userenv($sessionID);
835 $cookie = $query->cookie(
836 -name => 'CGISESSID',
837 -value => $session->id,
838 -HttpOnly => 1
840 $userid = $q_userid;
841 my $pki_field = C4::Context->preference('AllowPKIAuth');
842 if (! defined($pki_field) ) {
843 print STDERR "ERROR: Missing system preference AllowPKIAuth.\n";
844 $pki_field = 'None';
846 if ( ( $cas && $query->param('ticket') )
847 || $userid
848 || ( $shib && $shib_login )
849 || $pki_field ne 'None'
850 || $persona )
852 my $password = $query->param('password');
853 my $shibSuccess = 0;
855 my ( $return, $cardnumber );
856 # If shib is enabled and we have a shib login, does the login match a valid koha user
857 if ( $shib && $shib_login && $type eq 'opac' ) {
858 my $retuserid;
859 # Do not pass password here, else shib will not be checked in checkpw.
860 ( $return, $cardnumber, $retuserid ) = checkpw( $dbh, $userid, undef, $query );
861 $userid = $retuserid;
862 $shibSuccess = $return;
863 $info{'invalidShibLogin'} = 1 unless ($return);
865 # If shib login and match were successfull, skip further login methods
866 unless ( $shibSuccess ) {
867 if ( $cas && $query->param('ticket') ) {
868 my $retuserid;
869 ( $return, $cardnumber, $retuserid ) =
870 checkpw( $dbh, $userid, $password, $query );
871 $userid = $retuserid;
872 $info{'invalidCasLogin'} = 1 unless ($return);
875 elsif ($persona) {
876 my $value = $persona;
878 # If we're looking up the email, there's a chance that the person
879 # doesn't have a userid. So if there is none, we pass along the
880 # borrower number, and the bits of code that need to know the user
881 # ID will have to be smart enough to handle that.
882 require C4::Members;
883 my @users_info = C4::Members::GetBorrowersWithEmail($value);
884 if (@users_info) {
886 # First the userid, then the borrowernum
887 $value = $users_info[0][1] || $users_info[0][0];
889 else {
890 undef $value;
892 $return = $value ? 1 : 0;
893 $userid = $value;
896 elsif (
897 ( $pki_field eq 'Common Name' && $ENV{'SSL_CLIENT_S_DN_CN'} )
898 || ( $pki_field eq 'emailAddress'
899 && $ENV{'SSL_CLIENT_S_DN_Email'} )
902 my $value;
903 if ( $pki_field eq 'Common Name' ) {
904 $value = $ENV{'SSL_CLIENT_S_DN_CN'};
906 elsif ( $pki_field eq 'emailAddress' ) {
907 $value = $ENV{'SSL_CLIENT_S_DN_Email'};
909 # If we're looking up the email, there's a chance that the person
910 # doesn't have a userid. So if there is none, we pass along the
911 # borrower number, and the bits of code that need to know the user
912 # ID will have to be smart enough to handle that.
913 require C4::Members;
914 my @users_info = C4::Members::GetBorrowersWithEmail($value);
915 if (@users_info) {
917 # First the userid, then the borrowernum
918 $value = $users_info[0][1] || $users_info[0][0];
919 } else {
920 undef $value;
925 $return = $value ? 1 : 0;
926 $userid = $value;
929 else {
930 my $retuserid;
931 ( $return, $cardnumber, $retuserid ) =
932 checkpw( $dbh, $userid, $password, $query );
933 $userid = $retuserid if ( $retuserid );
934 $info{'invalid_username_or_password'} = 1 unless ($return);
936 if ($return) {
937 #_session_log(sprintf "%20s from %16s logged in at %30s.\n", $userid,$ENV{'REMOTE_ADDR'},(strftime '%c', localtime));
938 if ( $flags = haspermission( $userid, $flagsrequired ) ) {
939 $loggedin = 1;
941 else {
942 $info{'nopermission'} = 1;
943 C4::Context->_unset_userenv($sessionID);
945 my ($borrowernumber, $firstname, $surname, $userflags,
946 $branchcode, $branchname, $branchprinter, $emailaddress);
948 if ( $return == 1 ) {
949 my $select = "
950 SELECT borrowernumber, firstname, surname, flags, borrowers.branchcode,
951 branches.branchname as branchname,
952 branches.branchprinter as branchprinter,
953 email
954 FROM borrowers
955 LEFT JOIN branches on borrowers.branchcode=branches.branchcode
957 my $sth = $dbh->prepare("$select where userid=?");
958 $sth->execute($userid);
959 unless ($sth->rows) {
960 $debug and print STDERR "AUTH_1: no rows for userid='$userid'\n";
961 $sth = $dbh->prepare("$select where cardnumber=?");
962 $sth->execute($cardnumber);
964 unless ($sth->rows) {
965 $debug and print STDERR "AUTH_2a: no rows for cardnumber='$cardnumber'\n";
966 $sth->execute($userid);
967 unless ($sth->rows) {
968 $debug and print STDERR "AUTH_2b: no rows for userid='$userid' AS cardnumber\n";
972 if ($sth->rows) {
973 ($borrowernumber, $firstname, $surname, $userflags,
974 $branchcode, $branchname, $branchprinter, $emailaddress) = $sth->fetchrow;
975 $debug and print STDERR "AUTH_3 results: " .
976 "$cardnumber,$borrowernumber,$userid,$firstname,$surname,$userflags,$branchcode,$emailaddress\n";
977 } else {
978 print STDERR "AUTH_3: no results for userid='$userid', cardnumber='$cardnumber'.\n";
981 # launch a sequence to check if we have a ip for the branch, i
982 # if we have one we replace the branchcode of the userenv by the branch bound in the ip.
984 my $ip = $ENV{'REMOTE_ADDR'};
985 # if they specify at login, use that
986 if ($query->param('branch')) {
987 $branchcode = $query->param('branch');
988 $branchname = GetBranchName($branchcode);
990 my $branches = GetBranches();
991 if (C4::Context->boolean_preference('IndependentBranches') && C4::Context->boolean_preference('Autolocation')){
992 # we have to check they are coming from the right ip range
993 my $domain = $branches->{$branchcode}->{'branchip'};
994 if ($ip !~ /^$domain/){
995 $loggedin=0;
996 $info{'wrongip'} = 1;
1000 my @branchesloop;
1001 foreach my $br ( keys %$branches ) {
1002 # now we work with the treatment of ip
1003 my $domain = $branches->{$br}->{'branchip'};
1004 if ( $domain && $ip =~ /^$domain/ ) {
1005 $branchcode = $branches->{$br}->{'branchcode'};
1007 # new op dev : add the branchprinter and branchname in the cookie
1008 $branchprinter = $branches->{$br}->{'branchprinter'};
1009 $branchname = $branches->{$br}->{'branchname'};
1012 $session->param('number',$borrowernumber);
1013 $session->param('id',$userid);
1014 $session->param('cardnumber',$cardnumber);
1015 $session->param('firstname',$firstname);
1016 $session->param('surname',$surname);
1017 $session->param('branch',$branchcode);
1018 $session->param('branchname',$branchname);
1019 $session->param('flags',$userflags);
1020 $session->param('emailaddress',$emailaddress);
1021 $session->param('ip',$session->remote_addr());
1022 $session->param('lasttime',time());
1023 $session->param('shibboleth',$shibSuccess);
1024 $debug and printf STDERR "AUTH_4: (%s)\t%s %s - %s\n", map {$session->param($_)} qw(cardnumber firstname surname branch) ;
1026 elsif ( $return == 2 ) {
1027 #We suppose the user is the superlibrarian
1028 $borrowernumber = 0;
1029 $session->param('number',0);
1030 $session->param('id',C4::Context->config('user'));
1031 $session->param('cardnumber',C4::Context->config('user'));
1032 $session->param('firstname',C4::Context->config('user'));
1033 $session->param('surname',C4::Context->config('user'));
1034 $session->param('branch','NO_LIBRARY_SET');
1035 $session->param('branchname','NO_LIBRARY_SET');
1036 $session->param('flags',1);
1037 $session->param('emailaddress', C4::Context->preference('KohaAdminEmailAddress'));
1038 $session->param('ip',$session->remote_addr());
1039 $session->param('lasttime',time());
1041 if ($persona){
1042 $session->param('persona',1);
1044 C4::Context::set_userenv(
1045 $session->param('number'), $session->param('id'),
1046 $session->param('cardnumber'), $session->param('firstname'),
1047 $session->param('surname'), $session->param('branch'),
1048 $session->param('branchname'), $session->param('flags'),
1049 $session->param('emailaddress'), $session->param('branchprinter'),
1050 $session->param('persona'), $session->param('shibboleth')
1054 else {
1055 if ($userid) {
1056 $info{'invalid_username_or_password'} = 1;
1057 C4::Context->_unset_userenv($sessionID);
1059 $session->param('lasttime',time());
1060 $session->param('ip',$session->remote_addr());
1062 } # END if ( $userid = $query->param('userid') )
1063 elsif ($type eq "opac") {
1064 # if we are here this is an anonymous session; add public lists to it and a few other items...
1065 # anonymous sessions are created only for the OPAC
1066 $debug and warn "Initiating an anonymous session...";
1068 # setting a couple of other session vars...
1069 $session->param('ip',$session->remote_addr());
1070 $session->param('lasttime',time());
1071 $session->param('sessiontype','anon');
1073 } # END unless ($userid)
1075 # finished authentification, now respond
1076 if ( $loggedin || $authnotrequired )
1078 # successful login
1079 unless ($cookie) {
1080 $cookie = $query->cookie(
1081 -name => 'CGISESSID',
1082 -value => '',
1083 -HttpOnly => 1
1086 return ( $userid, $cookie, $sessionID, $flags );
1091 # AUTH rejected, show the login/password template, after checking the DB.
1095 # get the inputs from the incoming query
1096 my @inputs = ();
1097 foreach my $name ( param $query) {
1098 (next) if ( $name eq 'userid' || $name eq 'password' || $name eq 'ticket' );
1099 my $value = $query->param($name);
1100 push @inputs, { name => $name, value => $value };
1103 my $LibraryNameTitle = C4::Context->preference("LibraryName");
1104 $LibraryNameTitle =~ s/<(?:\/?)(?:br|p)\s*(?:\/?)>/ /sgi;
1105 $LibraryNameTitle =~ s/<(?:[^<>'"]|'(?:[^']*)'|"(?:[^"]*)")*>//sg;
1107 my $template_name = ( $type eq 'opac' ) ? 'opac-auth.tt' : 'auth.tt';
1108 my $template = C4::Templates::gettemplate($template_name, $type, $query );
1109 $template->param(
1110 branchloop => GetBranchesLoop(),
1111 opaccolorstylesheet => C4::Context->preference("opaccolorstylesheet"),
1112 opaclayoutstylesheet => C4::Context->preference("opaclayoutstylesheet"),
1113 login => 1,
1114 INPUTS => \@inputs,
1115 casAuthentication => C4::Context->preference("casAuthentication"),
1116 shibbolethAuthentication => $shib,
1117 suggestion => C4::Context->preference("suggestion"),
1118 virtualshelves => C4::Context->preference("virtualshelves"),
1119 LibraryName => "" . C4::Context->preference("LibraryName"),
1120 LibraryNameTitle => "" . $LibraryNameTitle,
1121 opacuserlogin => C4::Context->preference("opacuserlogin"),
1122 OpacNav => C4::Context->preference("OpacNav"),
1123 OpacNavRight => C4::Context->preference("OpacNavRight"),
1124 OpacNavBottom => C4::Context->preference("OpacNavBottom"),
1125 opaccredits => C4::Context->preference("opaccredits"),
1126 OpacFavicon => C4::Context->preference("OpacFavicon"),
1127 opacreadinghistory => C4::Context->preference("opacreadinghistory"),
1128 opaclanguagesdisplay => C4::Context->preference("opaclanguagesdisplay"),
1129 opacuserjs => C4::Context->preference("opacuserjs"),
1130 opacbookbag => "" . C4::Context->preference("opacbookbag"),
1131 OpacCloud => C4::Context->preference("OpacCloud"),
1132 OpacTopissue => C4::Context->preference("OpacTopissue"),
1133 OpacAuthorities => C4::Context->preference("OpacAuthorities"),
1134 OpacBrowser => C4::Context->preference("OpacBrowser"),
1135 opacheader => C4::Context->preference("opacheader"),
1136 TagsEnabled => C4::Context->preference("TagsEnabled"),
1137 OPACUserCSS => C4::Context->preference("OPACUserCSS"),
1138 intranetcolorstylesheet => C4::Context->preference("intranetcolorstylesheet"),
1139 intranetstylesheet => C4::Context->preference("intranetstylesheet"),
1140 intranetbookbag => C4::Context->preference("intranetbookbag"),
1141 IntranetNav => C4::Context->preference("IntranetNav"),
1142 IntranetFavicon => C4::Context->preference("IntranetFavicon"),
1143 intranetuserjs => C4::Context->preference("intranetuserjs"),
1144 IndependentBranches=> C4::Context->preference("IndependentBranches"),
1145 AutoLocation => C4::Context->preference("AutoLocation"),
1146 wrongip => $info{'wrongip'},
1147 PatronSelfRegistration => C4::Context->preference("PatronSelfRegistration"),
1148 PatronSelfRegistrationDefaultCategory => C4::Context->preference("PatronSelfRegistrationDefaultCategory"),
1149 persona => C4::Context->preference("Persona"),
1150 opac_css_override => $ENV{'OPAC_CSS_OVERRIDE'},
1153 $template->param( OpacPublic => C4::Context->preference("OpacPublic"));
1154 $template->param( loginprompt => 1 ) unless $info{'nopermission'};
1156 if($type eq 'opac'){
1157 my ($total, $pubshelves) = C4::VirtualShelves::GetSomeShelfNames(undef, 'MASTHEAD');
1158 $template->param(
1159 pubshelves => $total->{pubtotal},
1160 pubshelvesloop => $pubshelves,
1164 if ($cas) {
1166 # Is authentication against multiple CAS servers enabled?
1167 if (C4::Auth_with_cas::multipleAuth && !$casparam) {
1168 my $casservers = C4::Auth_with_cas::getMultipleAuth();
1169 my @tmplservers;
1170 foreach my $key (keys %$casservers) {
1171 push @tmplservers, {name => $key, value => login_cas_url($query, $key) . "?cas=$key" };
1173 $template->param(
1174 casServersLoop => \@tmplservers
1176 } else {
1177 $template->param(
1178 casServerUrl => login_cas_url($query),
1182 $template->param(
1183 invalidCasLogin => $info{'invalidCasLogin'}
1187 if ($shib) {
1188 $template->param(
1189 shibbolethAuthentication => $shib,
1190 shibbolethLoginUrl => login_shib_url($query),
1194 my $self_url = $query->url( -absolute => 1 );
1195 $template->param(
1196 url => $self_url,
1197 LibraryName => C4::Context->preference("LibraryName"),
1199 $template->param( %info );
1200 # $cookie = $query->cookie(CGISESSID => $session->id
1201 # );
1202 print $query->header(
1203 -type => 'text/html',
1204 -charset => 'utf-8',
1205 -cookie => $cookie
1207 $template->output;
1208 safe_exit;
1211 =head2 check_api_auth
1213 ($status, $cookie, $sessionId) = check_api_auth($query, $userflags);
1215 Given a CGI query containing the parameters 'userid' and 'password' and/or a session
1216 cookie, determine if the user has the privileges specified by C<$userflags>.
1218 C<check_api_auth> is is meant for authenticating users of web services, and
1219 consequently will always return and will not attempt to redirect the user
1220 agent.
1222 If a valid session cookie is already present, check_api_auth will return a status
1223 of "ok", the cookie, and the Koha session ID.
1225 If no session cookie is present, check_api_auth will check the 'userid' and 'password
1226 parameters and create a session cookie and Koha session if the supplied credentials
1227 are OK.
1229 Possible return values in C<$status> are:
1231 =over
1233 =item "ok" -- user authenticated; C<$cookie> and C<$sessionid> have valid values.
1235 =item "failed" -- credentials are not correct; C<$cookie> and C<$sessionid> are undef
1237 =item "maintenance" -- DB is in maintenance mode; no login possible at the moment
1239 =item "expired -- session cookie has expired; API user should resubmit userid and password
1241 =back
1243 =cut
1245 sub check_api_auth {
1246 my $query = shift;
1247 my $flagsrequired = shift;
1249 my $dbh = C4::Context->dbh;
1250 my $timeout = _timeout_syspref();
1252 unless (C4::Context->preference('Version')) {
1253 # database has not been installed yet
1254 return ("maintenance", undef, undef);
1256 my $kohaversion=C4::Context::KOHAVERSION;
1257 $kohaversion =~ s/(.*\..*)\.(.*)\.(.*)/$1$2$3/;
1258 if (C4::Context->preference('Version') < $kohaversion) {
1259 # database in need of version update; assume that
1260 # no API should be called while databsae is in
1261 # this condition.
1262 return ("maintenance", undef, undef);
1265 # FIXME -- most of what follows is a copy-and-paste
1266 # of code from checkauth. There is an obvious need
1267 # for refactoring to separate the various parts of
1268 # the authentication code, but as of 2007-11-19 this
1269 # is deferred so as to not introduce bugs into the
1270 # regular authentication code for Koha 3.0.
1272 # see if we have a valid session cookie already
1273 # however, if a userid parameter is present (i.e., from
1274 # a form submission, assume that any current cookie
1275 # is to be ignored
1276 my $sessionID = undef;
1277 unless ($query->param('userid')) {
1278 $sessionID = $query->cookie("CGISESSID");
1280 if ($sessionID && not ($cas && $query->param('PT')) ) {
1281 my $session = get_session($sessionID);
1282 C4::Context->_new_userenv($sessionID);
1283 if ($session) {
1284 C4::Context::set_userenv(
1285 $session->param('number'), $session->param('id'),
1286 $session->param('cardnumber'), $session->param('firstname'),
1287 $session->param('surname'), $session->param('branch'),
1288 $session->param('branchname'), $session->param('flags'),
1289 $session->param('emailaddress'), $session->param('branchprinter')
1292 my $ip = $session->param('ip');
1293 my $lasttime = $session->param('lasttime');
1294 my $userid = $session->param('id');
1295 if ( $lasttime < time() - $timeout ) {
1296 # time out
1297 $session->delete();
1298 $session->flush;
1299 C4::Context->_unset_userenv($sessionID);
1300 $userid = undef;
1301 $sessionID = undef;
1302 return ("expired", undef, undef);
1303 } elsif ( $ip ne $ENV{'REMOTE_ADDR'} ) {
1304 # IP address changed
1305 $session->delete();
1306 $session->flush;
1307 C4::Context->_unset_userenv($sessionID);
1308 $userid = undef;
1309 $sessionID = undef;
1310 return ("expired", undef, undef);
1311 } else {
1312 my $cookie = $query->cookie(
1313 -name => 'CGISESSID',
1314 -value => $session->id,
1315 -HttpOnly => 1,
1317 $session->param('lasttime',time());
1318 my $flags = haspermission($userid, $flagsrequired);
1319 if ($flags) {
1320 return ("ok", $cookie, $sessionID);
1321 } else {
1322 $session->delete();
1323 $session->flush;
1324 C4::Context->_unset_userenv($sessionID);
1325 $userid = undef;
1326 $sessionID = undef;
1327 return ("failed", undef, undef);
1330 } else {
1331 return ("expired", undef, undef);
1333 } else {
1334 # new login
1335 my $userid = $query->param('userid');
1336 my $password = $query->param('password');
1337 my ($return, $cardnumber);
1339 # Proxy CAS auth
1340 if ($cas && $query->param('PT')) {
1341 my $retuserid;
1342 $debug and print STDERR "## check_api_auth - checking CAS\n";
1343 # In case of a CAS authentication, we use the ticket instead of the password
1344 my $PT = $query->param('PT');
1345 ($return,$cardnumber,$userid) = check_api_auth_cas($dbh, $PT, $query); # EXTERNAL AUTH
1346 } else {
1347 # User / password auth
1348 unless ($userid and $password) {
1349 # caller did something wrong, fail the authenticateion
1350 return ("failed", undef, undef);
1352 ( $return, $cardnumber ) = checkpw( $dbh, $userid, $password, $query );
1355 if ($return and haspermission( $userid, $flagsrequired)) {
1356 my $session = get_session("");
1357 return ("failed", undef, undef) unless $session;
1359 my $sessionID = $session->id;
1360 C4::Context->_new_userenv($sessionID);
1361 my $cookie = $query->cookie(
1362 -name => 'CGISESSID',
1363 -value => $sessionID,
1364 -HttpOnly => 1,
1366 if ( $return == 1 ) {
1367 my (
1368 $borrowernumber, $firstname, $surname,
1369 $userflags, $branchcode, $branchname,
1370 $branchprinter, $emailaddress
1372 my $sth =
1373 $dbh->prepare(
1374 "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=?"
1376 $sth->execute($userid);
1378 $borrowernumber, $firstname, $surname,
1379 $userflags, $branchcode, $branchname,
1380 $branchprinter, $emailaddress
1381 ) = $sth->fetchrow if ( $sth->rows );
1383 unless ($sth->rows ) {
1384 my $sth = $dbh->prepare(
1385 "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=?"
1387 $sth->execute($cardnumber);
1389 $borrowernumber, $firstname, $surname,
1390 $userflags, $branchcode, $branchname,
1391 $branchprinter, $emailaddress
1392 ) = $sth->fetchrow if ( $sth->rows );
1394 unless ( $sth->rows ) {
1395 $sth->execute($userid);
1397 $borrowernumber, $firstname, $surname, $userflags,
1398 $branchcode, $branchname, $branchprinter, $emailaddress
1399 ) = $sth->fetchrow if ( $sth->rows );
1403 my $ip = $ENV{'REMOTE_ADDR'};
1404 # if they specify at login, use that
1405 if ($query->param('branch')) {
1406 $branchcode = $query->param('branch');
1407 $branchname = GetBranchName($branchcode);
1409 my $branches = GetBranches();
1410 my @branchesloop;
1411 foreach my $br ( keys %$branches ) {
1412 # now we work with the treatment of ip
1413 my $domain = $branches->{$br}->{'branchip'};
1414 if ( $domain && $ip =~ /^$domain/ ) {
1415 $branchcode = $branches->{$br}->{'branchcode'};
1417 # new op dev : add the branchprinter and branchname in the cookie
1418 $branchprinter = $branches->{$br}->{'branchprinter'};
1419 $branchname = $branches->{$br}->{'branchname'};
1422 $session->param('number',$borrowernumber);
1423 $session->param('id',$userid);
1424 $session->param('cardnumber',$cardnumber);
1425 $session->param('firstname',$firstname);
1426 $session->param('surname',$surname);
1427 $session->param('branch',$branchcode);
1428 $session->param('branchname',$branchname);
1429 $session->param('flags',$userflags);
1430 $session->param('emailaddress',$emailaddress);
1431 $session->param('ip',$session->remote_addr());
1432 $session->param('lasttime',time());
1433 } elsif ( $return == 2 ) {
1434 #We suppose the user is the superlibrarian
1435 $session->param('number',0);
1436 $session->param('id',C4::Context->config('user'));
1437 $session->param('cardnumber',C4::Context->config('user'));
1438 $session->param('firstname',C4::Context->config('user'));
1439 $session->param('surname',C4::Context->config('user'));
1440 $session->param('branch','NO_LIBRARY_SET');
1441 $session->param('branchname','NO_LIBRARY_SET');
1442 $session->param('flags',1);
1443 $session->param('emailaddress', C4::Context->preference('KohaAdminEmailAddress'));
1444 $session->param('ip',$session->remote_addr());
1445 $session->param('lasttime',time());
1447 C4::Context::set_userenv(
1448 $session->param('number'), $session->param('id'),
1449 $session->param('cardnumber'), $session->param('firstname'),
1450 $session->param('surname'), $session->param('branch'),
1451 $session->param('branchname'), $session->param('flags'),
1452 $session->param('emailaddress'), $session->param('branchprinter')
1454 return ("ok", $cookie, $sessionID);
1455 } else {
1456 return ("failed", undef, undef);
1461 =head2 check_cookie_auth
1463 ($status, $sessionId) = check_api_auth($cookie, $userflags);
1465 Given a CGISESSID cookie set during a previous login to Koha, determine
1466 if the user has the privileges specified by C<$userflags>.
1468 C<check_cookie_auth> is meant for authenticating special services
1469 such as tools/upload-file.pl that are invoked by other pages that
1470 have been authenticated in the usual way.
1472 Possible return values in C<$status> are:
1474 =over
1476 =item "ok" -- user authenticated; C<$sessionID> have valid values.
1478 =item "failed" -- credentials are not correct; C<$sessionid> are undef
1480 =item "maintenance" -- DB is in maintenance mode; no login possible at the moment
1482 =item "expired -- session cookie has expired; API user should resubmit userid and password
1484 =back
1486 =cut
1488 sub check_cookie_auth {
1489 my $cookie = shift;
1490 my $flagsrequired = shift;
1492 my $dbh = C4::Context->dbh;
1493 my $timeout = _timeout_syspref();
1495 unless (C4::Context->preference('Version')) {
1496 # database has not been installed yet
1497 return ("maintenance", undef);
1499 my $kohaversion=C4::Context::KOHAVERSION;
1500 $kohaversion =~ s/(.*\..*)\.(.*)\.(.*)/$1$2$3/;
1501 if (C4::Context->preference('Version') < $kohaversion) {
1502 # database in need of version update; assume that
1503 # no API should be called while databsae is in
1504 # this condition.
1505 return ("maintenance", undef);
1508 # FIXME -- most of what follows is a copy-and-paste
1509 # of code from checkauth. There is an obvious need
1510 # for refactoring to separate the various parts of
1511 # the authentication code, but as of 2007-11-23 this
1512 # is deferred so as to not introduce bugs into the
1513 # regular authentication code for Koha 3.0.
1515 # see if we have a valid session cookie already
1516 # however, if a userid parameter is present (i.e., from
1517 # a form submission, assume that any current cookie
1518 # is to be ignored
1519 unless (defined $cookie and $cookie) {
1520 return ("failed", undef);
1522 my $sessionID = $cookie;
1523 my $session = get_session($sessionID);
1524 C4::Context->_new_userenv($sessionID);
1525 if ($session) {
1526 C4::Context::set_userenv(
1527 $session->param('number'), $session->param('id'),
1528 $session->param('cardnumber'), $session->param('firstname'),
1529 $session->param('surname'), $session->param('branch'),
1530 $session->param('branchname'), $session->param('flags'),
1531 $session->param('emailaddress'), $session->param('branchprinter')
1534 my $ip = $session->param('ip');
1535 my $lasttime = $session->param('lasttime');
1536 my $userid = $session->param('id');
1537 if ( $lasttime < time() - $timeout ) {
1538 # time out
1539 $session->delete();
1540 $session->flush;
1541 C4::Context->_unset_userenv($sessionID);
1542 $userid = undef;
1543 $sessionID = undef;
1544 return ("expired", undef);
1545 } elsif ( $ip ne $ENV{'REMOTE_ADDR'} ) {
1546 # IP address changed
1547 $session->delete();
1548 $session->flush;
1549 C4::Context->_unset_userenv($sessionID);
1550 $userid = undef;
1551 $sessionID = undef;
1552 return ("expired", undef);
1553 } else {
1554 $session->param('lasttime',time());
1555 my $flags = haspermission($userid, $flagsrequired);
1556 if ($flags) {
1557 return ("ok", $sessionID);
1558 } else {
1559 $session->delete();
1560 $session->flush;
1561 C4::Context->_unset_userenv($sessionID);
1562 $userid = undef;
1563 $sessionID = undef;
1564 return ("failed", undef);
1567 } else {
1568 return ("expired", undef);
1572 =head2 get_session
1574 use CGI::Session;
1575 my $session = get_session($sessionID);
1577 Given a session ID, retrieve the CGI::Session object used to store
1578 the session's state. The session object can be used to store
1579 data that needs to be accessed by different scripts during a
1580 user's session.
1582 If the C<$sessionID> parameter is an empty string, a new session
1583 will be created.
1585 =cut
1587 sub get_session {
1588 my $sessionID = shift;
1589 my $storage_method = C4::Context->preference('SessionStorage');
1590 my $dbh = C4::Context->dbh;
1591 my $session;
1592 if ($storage_method eq 'mysql'){
1593 $session = new CGI::Session("driver:MySQL;serializer:yaml;id:md5", $sessionID, {Handle=>$dbh});
1595 elsif ($storage_method eq 'Pg') {
1596 $session = new CGI::Session("driver:PostgreSQL;serializer:yaml;id:md5", $sessionID, {Handle=>$dbh});
1598 elsif ($storage_method eq 'memcached' && C4::Context->ismemcached){
1599 $session = new CGI::Session("driver:memcached;serializer:yaml;id:md5", $sessionID, { Memcached => C4::Context->memcached } );
1601 else {
1602 # catch all defaults to tmp should work on all systems
1603 $session = new CGI::Session("driver:File;serializer:yaml;id:md5", $sessionID, {Directory=>'/tmp'});
1605 return $session;
1608 sub checkpw {
1609 my ( $dbh, $userid, $password, $query ) = @_;
1610 if ($ldap) {
1611 $debug and print STDERR "## checkpw - checking LDAP\n";
1612 my ($retval,$retcard,$retuserid) = checkpw_ldap(@_); # EXTERNAL AUTH
1613 return 0 if $retval == -1; # Incorrect password for LDAP login attempt
1614 ($retval) and return ($retval,$retcard,$retuserid);
1617 if ($cas && $query && $query->param('ticket')) {
1618 $debug and print STDERR "## checkpw - checking CAS\n";
1619 # In case of a CAS authentication, we use the ticket instead of the password
1620 my $ticket = $query->param('ticket');
1621 $query->delete('ticket'); # remove ticket to come back to original URL
1622 my ($retval,$retcard,$retuserid) = checkpw_cas($dbh, $ticket, $query); # EXTERNAL AUTH
1623 ($retval) and return ($retval,$retcard,$retuserid);
1624 return 0;
1627 # If we are in a shibboleth session (shibboleth is enabled, and a shibboleth match attribute is present)
1628 # Check for password to asertain whether we want to be testing against shibboleth or another method this
1629 # time around.
1630 if ($shib && $shib_login && !$password) {
1632 $debug and print STDERR "## checkpw - checking Shibboleth\n";
1633 # In case of a Shibboleth authentication, we expect a shibboleth user attribute
1634 # (defined under shibboleth mapping in koha-conf.xml) to contain the login of the
1635 # shibboleth-authenticated user
1637 # Then, we check if it matches a valid koha user
1638 if ($shib_login) {
1639 my ( $retval, $retcard, $retuserid ) = C4::Auth_with_shibboleth::checkpw_shib( $shib_login ); # EXTERNAL AUTH
1640 ($retval) and return ( $retval, $retcard, $retuserid );
1641 return 0;
1645 # INTERNAL AUTH
1646 return checkpw_internal(@_)
1649 sub checkpw_internal {
1650 my ( $dbh, $userid, $password ) = @_;
1652 if ( $userid && $userid eq C4::Context->config('user') ) {
1653 if ( $password && $password eq C4::Context->config('pass') ) {
1654 # Koha superuser account
1655 # C4::Context->set_userenv(0,0,C4::Context->config('user'),C4::Context->config('user'),C4::Context->config('user'),"",1);
1656 return 2;
1658 else {
1659 return 0;
1663 my $sth =
1664 $dbh->prepare(
1665 "select password,cardnumber,borrowernumber,userid,firstname,surname,branchcode,flags from borrowers where userid=?"
1667 $sth->execute($userid);
1668 if ( $sth->rows ) {
1669 my ( $stored_hash, $cardnumber, $borrowernumber, $userid, $firstname,
1670 $surname, $branchcode, $flags )
1671 = $sth->fetchrow;
1673 if ( checkpw_hash($password, $stored_hash) ) {
1675 C4::Context->set_userenv( "$borrowernumber", $userid, $cardnumber,
1676 $firstname, $surname, $branchcode, $flags );
1677 return 1, $cardnumber, $userid;
1680 $sth =
1681 $dbh->prepare(
1682 "select password,cardnumber,borrowernumber,userid, firstname,surname,branchcode,flags from borrowers where cardnumber=?"
1684 $sth->execute($userid);
1685 if ( $sth->rows ) {
1686 my ( $stored_hash, $cardnumber, $borrowernumber, $userid, $firstname,
1687 $surname, $branchcode, $flags )
1688 = $sth->fetchrow;
1690 if ( checkpw_hash($password, $stored_hash) ) {
1692 C4::Context->set_userenv( $borrowernumber, $userid, $cardnumber,
1693 $firstname, $surname, $branchcode, $flags );
1694 return 1, $cardnumber, $userid;
1697 if ( $userid && $userid eq 'demo'
1698 && "$password" eq 'demo'
1699 && C4::Context->config('demo') )
1702 # DEMO => the demo user is allowed to do everything (if demo set to 1 in koha.conf
1703 # some features won't be effective : modify systempref, modify MARC structure,
1704 return 2;
1706 return 0;
1709 sub checkpw_hash {
1710 my ( $password, $stored_hash ) = @_;
1712 return if $stored_hash eq '!';
1714 # check what encryption algorithm was implemented: Bcrypt - if the hash starts with '$2' it is Bcrypt else md5
1715 my $hash;
1716 if ( substr($stored_hash,0,2) eq '$2') {
1717 $hash = hash_password($password, $stored_hash);
1718 } else {
1719 $hash = md5_base64($password);
1721 return $hash eq $stored_hash;
1724 =head2 getuserflags
1726 my $authflags = getuserflags($flags, $userid, [$dbh]);
1728 Translates integer flags into permissions strings hash.
1730 C<$flags> is the integer userflags value ( borrowers.userflags )
1731 C<$userid> is the members.userid, used for building subpermissions
1732 C<$authflags> is a hashref of permissions
1734 =cut
1736 sub getuserflags {
1737 my $flags = shift;
1738 my $userid = shift;
1739 my $dbh = @_ ? shift : C4::Context->dbh;
1740 my $userflags;
1742 # I don't want to do this, but if someone logs in as the database
1743 # user, it would be preferable not to spam them to death with
1744 # numeric warnings. So, we make $flags numeric.
1745 no warnings 'numeric';
1746 $flags += 0;
1748 my $sth = $dbh->prepare("SELECT bit, flag, defaulton FROM userflags");
1749 $sth->execute;
1751 while ( my ( $bit, $flag, $defaulton ) = $sth->fetchrow ) {
1752 if ( ( $flags & ( 2**$bit ) ) || $defaulton ) {
1753 $userflags->{$flag} = 1;
1755 else {
1756 $userflags->{$flag} = 0;
1759 # get subpermissions and merge with top-level permissions
1760 my $user_subperms = get_user_subpermissions($userid);
1761 foreach my $module (keys %$user_subperms) {
1762 next if $userflags->{$module} == 1; # user already has permission for everything in this module
1763 $userflags->{$module} = $user_subperms->{$module};
1766 return $userflags;
1769 =head2 get_user_subpermissions
1771 $user_perm_hashref = get_user_subpermissions($userid);
1773 Given the userid (note, not the borrowernumber) of a staff user,
1774 return a hashref of hashrefs of the specific subpermissions
1775 accorded to the user. An example return is
1778 tools => {
1779 export_catalog => 1,
1780 import_patrons => 1,
1784 The top-level hash-key is a module or function code from
1785 userflags.flag, while the second-level key is a code
1786 from permissions.
1788 The results of this function do not give a complete picture
1789 of the functions that a staff user can access; it is also
1790 necessary to check borrowers.flags.
1792 =cut
1794 sub get_user_subpermissions {
1795 my $userid = shift;
1797 my $dbh = C4::Context->dbh;
1798 my $sth = $dbh->prepare("SELECT flag, user_permissions.code
1799 FROM user_permissions
1800 JOIN permissions USING (module_bit, code)
1801 JOIN userflags ON (module_bit = bit)
1802 JOIN borrowers USING (borrowernumber)
1803 WHERE userid = ?");
1804 $sth->execute($userid);
1806 my $user_perms = {};
1807 while (my $perm = $sth->fetchrow_hashref) {
1808 $user_perms->{$perm->{'flag'}}->{$perm->{'code'}} = 1;
1810 return $user_perms;
1813 =head2 get_all_subpermissions
1815 my $perm_hashref = get_all_subpermissions();
1817 Returns a hashref of hashrefs defining all specific
1818 permissions currently defined. The return value
1819 has the same structure as that of C<get_user_subpermissions>,
1820 except that the innermost hash value is the description
1821 of the subpermission.
1823 =cut
1825 sub get_all_subpermissions {
1826 my $dbh = C4::Context->dbh;
1827 my $sth = $dbh->prepare("SELECT flag, code, description
1828 FROM permissions
1829 JOIN userflags ON (module_bit = bit)");
1830 $sth->execute();
1832 my $all_perms = {};
1833 while (my $perm = $sth->fetchrow_hashref) {
1834 $all_perms->{$perm->{'flag'}}->{$perm->{'code'}} = $perm->{'description'};
1836 return $all_perms;
1839 =head2 haspermission
1841 $flags = ($userid, $flagsrequired);
1843 C<$userid> the userid of the member
1844 C<$flags> is a hashref of required flags like C<$borrower-&lt;{authflags}>
1846 Returns member's flags or 0 if a permission is not met.
1848 =cut
1850 sub haspermission {
1851 my ($userid, $flagsrequired) = @_;
1852 my $sth = C4::Context->dbh->prepare("SELECT flags FROM borrowers WHERE userid=?");
1853 $sth->execute($userid);
1854 my $row = $sth->fetchrow();
1855 my $flags = getuserflags($row, $userid);
1856 if ( $userid eq C4::Context->config('user') ) {
1857 # Super User Account from /etc/koha.conf
1858 $flags->{'superlibrarian'} = 1;
1860 elsif ( $userid eq 'demo' && C4::Context->config('demo') ) {
1861 # Demo user that can do "anything" (demo=1 in /etc/koha.conf)
1862 $flags->{'superlibrarian'} = 1;
1865 return $flags if $flags->{superlibrarian};
1867 foreach my $module ( keys %$flagsrequired ) {
1868 my $subperm = $flagsrequired->{$module};
1869 if ($subperm eq '*') {
1870 return 0 unless ( $flags->{$module} == 1 or ref($flags->{$module}) );
1871 } else {
1872 return 0 unless ( $flags->{$module} == 1 or
1873 ( ref($flags->{$module}) and
1874 exists $flags->{$module}->{$subperm} and
1875 $flags->{$module}->{$subperm} == 1
1880 return $flags;
1881 #FIXME - This fcn should return the failed permission so a suitable error msg can be delivered.
1885 sub getborrowernumber {
1886 my ($userid) = @_;
1887 my $userenv = C4::Context->userenv;
1888 if ( defined( $userenv ) && ref( $userenv ) eq 'HASH' && $userenv->{number} ) {
1889 return $userenv->{number};
1891 my $dbh = C4::Context->dbh;
1892 for my $field ( 'userid', 'cardnumber' ) {
1893 my $sth =
1894 $dbh->prepare("select borrowernumber from borrowers where $field=?");
1895 $sth->execute($userid);
1896 if ( $sth->rows ) {
1897 my ($bnumber) = $sth->fetchrow;
1898 return $bnumber;
1901 return 0;
1904 END { } # module clean-up code here (global destructor)
1906 __END__
1908 =head1 SEE ALSO
1910 CGI(3)
1912 C4::Output(3)
1914 Crypt::Eksblowfish::Bcrypt(3)
1916 Digest::MD5(3)
1918 =cut