Bug 6331: (follow-up) do not populate deleteditems.marc
[koha.git] / C4 / Auth.pm
blob00eeeef84243fa77a317c283278990d89177341b
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 decode_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::Branch; # GetBranches
31 use C4::VirtualShelves;
32 use Koha::AuthUtils qw(hash_password);
33 use POSIX qw/strftime/;
34 use List::MoreUtils qw/ any /;
36 # use utf8;
37 use vars qw($VERSION @ISA @EXPORT @EXPORT_OK %EXPORT_TAGS $debug $ldap $cas $caslogout);
39 BEGIN {
40 sub psgi_env { any { /^psgi\./ } keys %ENV }
41 sub safe_exit {
42 if ( psgi_env ) { die 'psgi:exit' }
43 else { exit }
45 $VERSION = 3.07.00.049; # set version for version checking
47 $debug = $ENV{DEBUG};
48 @ISA = qw(Exporter);
49 @EXPORT = qw(&checkauth &get_template_and_user &haspermission &get_user_subpermissions);
50 @EXPORT_OK = qw(&check_api_auth &get_session &check_cookie_auth &checkpw &checkpw_internal &checkpw_hash
51 &get_all_subpermissions &get_user_subpermissions
52 ParseSearchHistoryCookie
54 %EXPORT_TAGS = ( EditPermissions => [qw(get_all_subpermissions get_user_subpermissions)] );
55 $ldap = C4::Context->config('useldapserver') || 0;
56 $cas = C4::Context->preference('casAuthentication');
57 $caslogout = C4::Context->preference('casLogout');
58 require C4::Auth_with_cas; # no import
59 if ($ldap) {
60 require C4::Auth_with_ldap;
61 import C4::Auth_with_ldap qw(checkpw_ldap);
63 if ($cas) {
64 import C4::Auth_with_cas qw(check_api_auth_cas checkpw_cas login_cas logout_cas login_cas_url);
69 =head1 NAME
71 C4::Auth - Authenticates Koha users
73 =head1 SYNOPSIS
75 use CGI;
76 use C4::Auth;
77 use C4::Output;
79 my $query = new CGI;
81 my ($template, $borrowernumber, $cookie)
82 = get_template_and_user(
84 template_name => "opac-main.tmpl",
85 query => $query,
86 type => "opac",
87 authnotrequired => 1,
88 flagsrequired => {borrow => 1, catalogue => '*', tools => 'import_patrons' },
92 output_html_with_http_headers $query, $cookie, $template->output;
94 =head1 DESCRIPTION
96 The main function of this module is to provide
97 authentification. However the get_template_and_user function has
98 been provided so that a users login information is passed along
99 automatically. This gets loaded into the template.
101 =head1 FUNCTIONS
103 =head2 get_template_and_user
105 my ($template, $borrowernumber, $cookie)
106 = get_template_and_user(
108 template_name => "opac-main.tmpl",
109 query => $query,
110 type => "opac",
111 authnotrequired => 1,
112 flagsrequired => {borrow => 1, catalogue => '*', tools => 'import_patrons' },
116 This call passes the C<query>, C<flagsrequired> and C<authnotrequired>
117 to C<&checkauth> (in this module) to perform authentification.
118 See C<&checkauth> for an explanation of these parameters.
120 The C<template_name> is then used to find the correct template for
121 the page. The authenticated users details are loaded onto the
122 template in the HTML::Template LOOP variable C<USER_INFO>. Also the
123 C<sessionID> is passed to the template. This can be used in templates
124 if cookies are disabled. It needs to be put as and input to every
125 authenticated page.
127 More information on the C<gettemplate> sub can be found in the
128 Output.pm module.
130 =cut
132 my $SEARCH_HISTORY_INSERT_SQL =<<EOQ;
133 INSERT INTO search_history(userid, sessionid, query_desc, query_cgi, total, time )
134 VALUES ( ?, ?, ?, ?, ?, FROM_UNIXTIME(?))
137 sub get_template_and_user {
139 my $in = shift;
140 my ( $user, $cookie, $sessionID, $flags );
142 my $template = C4::Templates::gettemplate(
143 $in->{'template_name'},
144 $in->{'type'},
145 $in->{'query'},
146 $in->{'is_plugin'}
149 if ( $in->{'template_name'} !~m/maintenance/ ) {
150 ( $user, $cookie, $sessionID, $flags ) = checkauth(
151 $in->{'query'},
152 $in->{'authnotrequired'},
153 $in->{'flagsrequired'},
154 $in->{'type'}
158 my $borrowernumber;
159 if ($user) {
160 require C4::Members;
161 # It's possible for $user to be the borrowernumber if they don't have a
162 # userid defined (and are logging in through some other method, such
163 # as SSL certs against an email address)
164 $borrowernumber = getborrowernumber($user) if defined($user);
165 if (!defined($borrowernumber) && defined($user)) {
166 my $borrower = C4::Members::GetMember(borrowernumber => $user);
167 if ($borrower) {
168 $borrowernumber = $user;
169 # A bit of a hack, but I don't know there's a nicer way
170 # to do it.
171 $user = $borrower->{firstname} . ' ' . $borrower->{surname};
175 # user info
176 $template->param( loggedinusername => $user );
177 $template->param( sessionID => $sessionID );
179 my ($total, $pubshelves, $barshelves) = C4::VirtualShelves::GetSomeShelfNames($borrowernumber, 'MASTHEAD');
180 $template->param(
181 pubshelves => $total->{pubtotal},
182 pubshelvesloop => $pubshelves,
183 barshelves => $total->{bartotal},
184 barshelvesloop => $barshelves,
187 my ( $borr ) = C4::Members::GetMemberDetails( $borrowernumber );
188 my @bordat;
189 $bordat[0] = $borr;
190 $template->param( "USER_INFO" => \@bordat );
192 my $all_perms = get_all_subpermissions();
194 my @flagroots = qw(circulate catalogue parameters borrowers permissions reserveforothers borrow
195 editcatalogue updatecharges management tools editauthorities serials reports acquisition);
196 # We are going to use the $flags returned by checkauth
197 # to create the template's parameters that will indicate
198 # which menus the user can access.
199 if ( $flags && $flags->{superlibrarian}==1 ) {
200 $template->param( CAN_user_circulate => 1 );
201 $template->param( CAN_user_catalogue => 1 );
202 $template->param( CAN_user_parameters => 1 );
203 $template->param( CAN_user_borrowers => 1 );
204 $template->param( CAN_user_permissions => 1 );
205 $template->param( CAN_user_reserveforothers => 1 );
206 $template->param( CAN_user_borrow => 1 );
207 $template->param( CAN_user_editcatalogue => 1 );
208 $template->param( CAN_user_updatecharges => 1 );
209 $template->param( CAN_user_acquisition => 1 );
210 $template->param( CAN_user_management => 1 );
211 $template->param( CAN_user_tools => 1 );
212 $template->param( CAN_user_editauthorities => 1 );
213 $template->param( CAN_user_serials => 1 );
214 $template->param( CAN_user_reports => 1 );
215 $template->param( CAN_user_staffaccess => 1 );
216 $template->param( CAN_user_plugins => 1 );
217 $template->param( CAN_user_coursereserves => 1 );
218 foreach my $module (keys %$all_perms) {
219 foreach my $subperm (keys %{ $all_perms->{$module} }) {
220 $template->param( "CAN_user_${module}_${subperm}" => 1 );
225 if ( $flags ) {
226 foreach my $module (keys %$all_perms) {
227 if ( $flags->{$module} == 1) {
228 foreach my $subperm (keys %{ $all_perms->{$module} }) {
229 $template->param( "CAN_user_${module}_${subperm}" => 1 );
231 } elsif ( ref($flags->{$module}) ) {
232 foreach my $subperm (keys %{ $flags->{$module} } ) {
233 $template->param( "CAN_user_${module}_${subperm}" => 1 );
239 if ($flags) {
240 foreach my $module (keys %$flags) {
241 if ( $flags->{$module} == 1 or ref($flags->{$module}) ) {
242 $template->param( "CAN_user_$module" => 1 );
243 if ($module eq "parameters") {
244 $template->param( CAN_user_management => 1 );
249 # Logged-in opac search history
250 # If the requested template is an opac one and opac search history is enabled
251 if ($in->{type} eq 'opac' && C4::Context->preference('EnableOpacSearchHistory')) {
252 my $dbh = C4::Context->dbh;
253 my $query = "SELECT COUNT(*) FROM search_history WHERE userid=?";
254 my $sth = $dbh->prepare($query);
255 $sth->execute($borrowernumber);
257 # If at least one search has already been performed
258 if ($sth->fetchrow_array > 0) {
259 # We show the link in opac
260 $template->param(ShowOpacRecentSearchLink => 1);
263 # And if there's a cookie with searches performed when the user was not logged in,
264 # we add them to the logged-in search history
265 my @recentSearches = ParseSearchHistoryCookie($in->{'query'});
266 if (@recentSearches) {
267 my $sth = $dbh->prepare($SEARCH_HISTORY_INSERT_SQL);
268 $sth->execute( $borrowernumber,
269 $in->{'query'}->cookie("CGISESSID"),
270 $_->{'query_desc'},
271 $_->{'query_cgi'},
272 $_->{'total'},
273 $_->{'time'},
274 ) foreach @recentSearches;
276 # And then, delete the cookie's content
277 my $newsearchcookie = $in->{'query'}->cookie(
278 -name => 'KohaOpacRecentSearches',
279 -value => encode_json([]),
280 -HttpOnly => 1,
281 -expires => ''
283 $cookie = [$cookie, $newsearchcookie];
287 else { # if this is an anonymous session, setup to display public lists...
289 $template->param( sessionID => $sessionID );
291 my ($total, $pubshelves) = C4::VirtualShelves::GetSomeShelfNames(undef, 'MASTHEAD');
292 $template->param(
293 pubshelves => $total->{pubtotal},
294 pubshelvesloop => $pubshelves,
297 # Anonymous opac search history
298 # If opac search history is enabled and at least one search has already been performed
299 if (C4::Context->preference('EnableOpacSearchHistory')) {
300 my @recentSearches = ParseSearchHistoryCookie($in->{'query'});
301 if (@recentSearches) {
302 $template->param(ShowOpacRecentSearchLink => 1);
306 if(C4::Context->preference('dateformat')){
307 $template->param(dateformat => C4::Context->preference('dateformat'))
310 # these template parameters are set the same regardless of $in->{'type'}
311 $template->param(
312 "BiblioDefaultView".C4::Context->preference("BiblioDefaultView") => 1,
313 EnhancedMessagingPreferences => C4::Context->preference('EnhancedMessagingPreferences'),
314 GoogleJackets => C4::Context->preference("GoogleJackets"),
315 OpenLibraryCovers => C4::Context->preference("OpenLibraryCovers"),
316 KohaAdminEmailAddress => "" . C4::Context->preference("KohaAdminEmailAddress"),
317 LoginBranchcode => (C4::Context->userenv?C4::Context->userenv->{"branch"}:undef),
318 LoginFirstname => (C4::Context->userenv?C4::Context->userenv->{"firstname"}:"Bel"),
319 LoginSurname => C4::Context->userenv?C4::Context->userenv->{"surname"}:"Inconnu",
320 emailaddress => C4::Context->userenv?C4::Context->userenv->{"emailaddress"}:undef,
321 loggedinpersona => C4::Context->userenv?C4::Context->userenv->{"persona"}:undef,
322 TagsEnabled => C4::Context->preference("TagsEnabled"),
323 hide_marc => C4::Context->preference("hide_marc"),
324 item_level_itypes => C4::Context->preference('item-level_itypes'),
325 patronimages => C4::Context->preference("patronimages"),
326 singleBranchMode => C4::Context->preference("singleBranchMode"),
327 XSLTDetailsDisplay => C4::Context->preference("XSLTDetailsDisplay"),
328 XSLTResultsDisplay => C4::Context->preference("XSLTResultsDisplay"),
329 using_https => $in->{'query'}->https() ? 1 : 0,
330 noItemTypeImages => C4::Context->preference("noItemTypeImages"),
331 marcflavour => C4::Context->preference("marcflavour"),
332 persona => C4::Context->preference("persona"),
334 if ( $in->{'type'} eq "intranet" ) {
335 $template->param(
336 AmazonCoverImages => C4::Context->preference("AmazonCoverImages"),
337 AutoLocation => C4::Context->preference("AutoLocation"),
338 "BiblioDefaultView".C4::Context->preference("IntranetBiblioDefaultView") => 1,
339 CalendarFirstDayOfWeek => (C4::Context->preference("CalendarFirstDayOfWeek") eq "Sunday")?0:1,
340 CircAutocompl => C4::Context->preference("CircAutocompl"),
341 FRBRizeEditions => C4::Context->preference("FRBRizeEditions"),
342 IndependentBranches => C4::Context->preference("IndependentBranches"),
343 IntranetNav => C4::Context->preference("IntranetNav"),
344 IntranetmainUserblock => C4::Context->preference("IntranetmainUserblock"),
345 LibraryName => C4::Context->preference("LibraryName"),
346 LoginBranchname => (C4::Context->userenv?C4::Context->userenv->{"branchname"}:undef),
347 advancedMARCEditor => C4::Context->preference("advancedMARCEditor"),
348 canreservefromotherbranches => C4::Context->preference('canreservefromotherbranches'),
349 intranetcolorstylesheet => C4::Context->preference("intranetcolorstylesheet"),
350 IntranetFavicon => C4::Context->preference("IntranetFavicon"),
351 intranetreadinghistory => C4::Context->preference("intranetreadinghistory"),
352 intranetstylesheet => C4::Context->preference("intranetstylesheet"),
353 IntranetUserCSS => C4::Context->preference("IntranetUserCSS"),
354 intranetuserjs => C4::Context->preference("intranetuserjs"),
355 intranetbookbag => C4::Context->preference("intranetbookbag"),
356 suggestion => C4::Context->preference("suggestion"),
357 virtualshelves => C4::Context->preference("virtualshelves"),
358 StaffSerialIssueDisplayCount => C4::Context->preference("StaffSerialIssueDisplayCount"),
359 EasyAnalyticalRecords => C4::Context->preference('EasyAnalyticalRecords'),
360 LocalCoverImages => C4::Context->preference('LocalCoverImages'),
361 OPACLocalCoverImages => C4::Context->preference('OPACLocalCoverImages'),
362 AllowMultipleCovers => C4::Context->preference('AllowMultipleCovers'),
363 EnableBorrowerFiles => C4::Context->preference('EnableBorrowerFiles'),
364 UseKohaPlugins => C4::Context->preference('UseKohaPlugins'),
365 UseCourseReserves => C4::Context->preference("UseCourseReserves"),
368 else {
369 warn "template type should be OPAC, here it is=[" . $in->{'type'} . "]" unless ( $in->{'type'} eq 'opac' );
370 #TODO : replace LibraryName syspref with 'system name', and remove this html processing
371 my $LibraryNameTitle = C4::Context->preference("LibraryName");
372 $LibraryNameTitle =~ s/<(?:\/?)(?:br|p)\s*(?:\/?)>/ /sgi;
373 $LibraryNameTitle =~ s/<(?:[^<>'"]|'(?:[^']*)'|"(?:[^"]*)")*>//sg;
374 # clean up the busc param in the session if the page is not opac-detail and not the "add to list" page
375 if ( C4::Context->preference("OpacBrowseResults")
376 && $in->{'template_name'} =~ /opac-(.+)\.(?:tt|tmpl)$/ ) {
377 my $pagename = $1;
378 unless ( $pagename =~ /^(?:MARC|ISBD)?detail$/
379 or $pagename =~ /^addbybiblionumber$/ ) {
380 my $sessionSearch = get_session($sessionID || $in->{'query'}->cookie("CGISESSID"));
381 $sessionSearch->clear(["busc"]) if ($sessionSearch->param("busc"));
384 # variables passed from CGI: opac_css_override and opac_search_limits.
385 my $opac_search_limit = $ENV{'OPAC_SEARCH_LIMIT'};
386 my $opac_limit_override = $ENV{'OPAC_LIMIT_OVERRIDE'};
387 my $opac_name = '';
388 if (($opac_search_limit && $opac_search_limit =~ /branch:(\w+)/ && $opac_limit_override) || ($in->{'query'}->param('limit') && $in->{'query'}->param('limit') =~ /branch:(\w+)/)){
389 $opac_name = $1; # opac_search_limit is a branch, so we use it.
390 } elsif ( $in->{'query'}->param('multibranchlimit') ) {
391 $opac_name = $in->{'query'}->param('multibranchlimit');
392 } elsif (C4::Context->preference("SearchMyLibraryFirst") && C4::Context->userenv && C4::Context->userenv->{'branch'}) {
393 $opac_name = C4::Context->userenv->{'branch'};
395 $template->param(
396 opaccolorstylesheet => C4::Context->preference("opaccolorstylesheet"),
397 AnonSuggestions => "" . C4::Context->preference("AnonSuggestions"),
398 AuthorisedValueImages => C4::Context->preference("AuthorisedValueImages"),
399 BranchesLoop => GetBranchesLoop($opac_name),
400 BranchCategoriesLoop => GetBranchCategories( 'searchdomain', 1, $opac_name ),
401 CalendarFirstDayOfWeek => (C4::Context->preference("CalendarFirstDayOfWeek") eq "Sunday")?0:1,
402 LibraryName => "" . C4::Context->preference("LibraryName"),
403 LibraryNameTitle => "" . $LibraryNameTitle,
404 LoginBranchname => C4::Context->userenv?C4::Context->userenv->{"branchname"}:"",
405 OPACAmazonCoverImages => C4::Context->preference("OPACAmazonCoverImages"),
406 OPACFRBRizeEditions => C4::Context->preference("OPACFRBRizeEditions"),
407 OpacHighlightedWords => C4::Context->preference("OpacHighlightedWords"),
408 OPACItemHolds => C4::Context->preference("OPACItemHolds"),
409 OPACShelfBrowser => "". C4::Context->preference("OPACShelfBrowser"),
410 OPACURLOpenInNewWindow => "" . C4::Context->preference("OPACURLOpenInNewWindow"),
411 OPACUserCSS => "". C4::Context->preference("OPACUserCSS"),
412 OPACMobileUserCSS => "". C4::Context->preference("OPACMobileUserCSS"),
413 OPACViewOthersSuggestions => "" . C4::Context->preference("OPACViewOthersSuggestions"),
414 OpacAuthorities => C4::Context->preference("OpacAuthorities"),
415 OPACBaseURL => ($in->{'query'}->https() ? "https://" : "http://") . $ENV{'SERVER_NAME'} .
416 ($ENV{'SERVER_PORT'} eq ($in->{'query'}->https() ? "443" : "80") ? '' : ":$ENV{'SERVER_PORT'}"),
417 opac_css_override => $ENV{'OPAC_CSS_OVERRIDE'},
418 opac_search_limit => $opac_search_limit,
419 opac_limit_override => $opac_limit_override,
420 OpacBrowser => C4::Context->preference("OpacBrowser"),
421 OpacCloud => C4::Context->preference("OpacCloud"),
422 OpacKohaUrl => C4::Context->preference("OpacKohaUrl"),
423 OpacMainUserBlock => "" . C4::Context->preference("OpacMainUserBlock"),
424 OpacMainUserBlockMobile => "" . C4::Context->preference("OpacMainUserBlockMobile"),
425 OpacShowFiltersPulldownMobile => C4::Context->preference("OpacShowFiltersPulldownMobile"),
426 OpacShowLibrariesPulldownMobile => C4::Context->preference("OpacShowLibrariesPulldownMobile"),
427 OpacNav => "" . C4::Context->preference("OpacNav"),
428 OpacNavRight => "" . C4::Context->preference("OpacNavRight"),
429 OpacNavBottom => "" . C4::Context->preference("OpacNavBottom"),
430 OpacPasswordChange => C4::Context->preference("OpacPasswordChange"),
431 OPACPatronDetails => C4::Context->preference("OPACPatronDetails"),
432 OPACPrivacy => C4::Context->preference("OPACPrivacy"),
433 OPACFinesTab => C4::Context->preference("OPACFinesTab"),
434 OpacTopissue => C4::Context->preference("OpacTopissue"),
435 RequestOnOpac => C4::Context->preference("RequestOnOpac"),
436 'Version' => C4::Context->preference('Version'),
437 hidelostitems => C4::Context->preference("hidelostitems"),
438 mylibraryfirst => (C4::Context->preference("SearchMyLibraryFirst") && C4::Context->userenv) ? C4::Context->userenv->{'branch'} : '',
439 opaclayoutstylesheet => "" . C4::Context->preference("opaclayoutstylesheet"),
440 opacbookbag => "" . C4::Context->preference("opacbookbag"),
441 opaccredits => "" . C4::Context->preference("opaccredits"),
442 OpacFavicon => C4::Context->preference("OpacFavicon"),
443 opacheader => "" . C4::Context->preference("opacheader"),
444 opaclanguagesdisplay => "" . C4::Context->preference("opaclanguagesdisplay"),
445 opacreadinghistory => C4::Context->preference("opacreadinghistory"),
446 opacsmallimage => "" . C4::Context->preference("opacsmallimage"),
447 opacuserjs => C4::Context->preference("opacuserjs"),
448 opacuserlogin => "" . C4::Context->preference("opacuserlogin"),
449 ShowReviewer => C4::Context->preference("ShowReviewer"),
450 ShowReviewerPhoto => C4::Context->preference("ShowReviewerPhoto"),
451 suggestion => "" . C4::Context->preference("suggestion"),
452 virtualshelves => "" . C4::Context->preference("virtualshelves"),
453 OPACSerialIssueDisplayCount => C4::Context->preference("OPACSerialIssueDisplayCount"),
454 OPACXSLTDetailsDisplay => C4::Context->preference("OPACXSLTDetailsDisplay"),
455 OPACXSLTResultsDisplay => C4::Context->preference("OPACXSLTResultsDisplay"),
456 SyndeticsClientCode => C4::Context->preference("SyndeticsClientCode"),
457 SyndeticsEnabled => C4::Context->preference("SyndeticsEnabled"),
458 SyndeticsCoverImages => C4::Context->preference("SyndeticsCoverImages"),
459 SyndeticsTOC => C4::Context->preference("SyndeticsTOC"),
460 SyndeticsSummary => C4::Context->preference("SyndeticsSummary"),
461 SyndeticsEditions => C4::Context->preference("SyndeticsEditions"),
462 SyndeticsExcerpt => C4::Context->preference("SyndeticsExcerpt"),
463 SyndeticsReviews => C4::Context->preference("SyndeticsReviews"),
464 SyndeticsAuthorNotes => C4::Context->preference("SyndeticsAuthorNotes"),
465 SyndeticsAwards => C4::Context->preference("SyndeticsAwards"),
466 SyndeticsSeries => C4::Context->preference("SyndeticsSeries"),
467 SyndeticsCoverImageSize => C4::Context->preference("SyndeticsCoverImageSize"),
468 OPACLocalCoverImages => C4::Context->preference("OPACLocalCoverImages"),
469 PatronSelfRegistration => C4::Context->preference("PatronSelfRegistration"),
470 PatronSelfRegistrationDefaultCategory => C4::Context->preference("PatronSelfRegistrationDefaultCategory"),
473 $template->param(OpacPublic => '1') if ($user || C4::Context->preference("OpacPublic"));
476 # Check if we were asked using parameters to force a specific language
477 if ( defined $in->{'query'}->param('language') ) {
478 # Extract the language, let C4::Templates::getlanguage choose
479 # what to do
480 my $language = C4::Templates::getlanguage($in->{'query'},$in->{'type'});
481 my $languagecookie = C4::Templates::getlanguagecookie($in->{'query'},$language);
482 if ( ref $cookie eq 'ARRAY' ) {
483 push @{ $cookie }, $languagecookie;
484 } else {
485 $cookie = [$cookie, $languagecookie];
489 return ( $template, $borrowernumber, $cookie, $flags);
492 =head2 checkauth
494 ($userid, $cookie, $sessionID) = &checkauth($query, $noauth, $flagsrequired, $type);
496 Verifies that the user is authorized to run this script. If
497 the user is authorized, a (userid, cookie, session-id, flags)
498 quadruple is returned. If the user is not authorized but does
499 not have the required privilege (see $flagsrequired below), it
500 displays an error page and exits. Otherwise, it displays the
501 login page and exits.
503 Note that C<&checkauth> will return if and only if the user
504 is authorized, so it should be called early on, before any
505 unfinished operations (e.g., if you've opened a file, then
506 C<&checkauth> won't close it for you).
508 C<$query> is the CGI object for the script calling C<&checkauth>.
510 The C<$noauth> argument is optional. If it is set, then no
511 authorization is required for the script.
513 C<&checkauth> fetches user and session information from C<$query> and
514 ensures that the user is authorized to run scripts that require
515 authorization.
517 The C<$flagsrequired> argument specifies the required privileges
518 the user must have if the username and password are correct.
519 It should be specified as a reference-to-hash; keys in the hash
520 should be the "flags" for the user, as specified in the Members
521 intranet module. Any key specified must correspond to a "flag"
522 in the userflags table. E.g., { circulate => 1 } would specify
523 that the user must have the "circulate" privilege in order to
524 proceed. To make sure that access control is correct, the
525 C<$flagsrequired> parameter must be specified correctly.
527 Koha also has a concept of sub-permissions, also known as
528 granular permissions. This makes the value of each key
529 in the C<flagsrequired> hash take on an additional
530 meaning, i.e.,
534 The user must have access to all subfunctions of the module
535 specified by the hash key.
539 The user must have access to at least one subfunction of the module
540 specified by the hash key.
542 specific permission, e.g., 'export_catalog'
544 The user must have access to the specific subfunction list, which
545 must correspond to a row in the permissions table.
547 The C<$type> argument specifies whether the template should be
548 retrieved from the opac or intranet directory tree. "opac" is
549 assumed if it is not specified; however, if C<$type> is specified,
550 "intranet" is assumed if it is not "opac".
552 If C<$query> does not have a valid session ID associated with it
553 (i.e., the user has not logged in) or if the session has expired,
554 C<&checkauth> presents the user with a login page (from the point of
555 view of the original script, C<&checkauth> does not return). Once the
556 user has authenticated, C<&checkauth> restarts the original script
557 (this time, C<&checkauth> returns).
559 The login page is provided using a HTML::Template, which is set in the
560 systempreferences table or at the top of this file. The variable C<$type>
561 selects which template to use, either the opac or the intranet
562 authentification template.
564 C<&checkauth> returns a user ID, a cookie, and a session ID. The
565 cookie should be sent back to the browser; it verifies that the user
566 has authenticated.
568 =cut
570 sub _version_check {
571 my $type = shift;
572 my $query = shift;
573 my $version;
574 # If Version syspref is unavailable, it means Koha is beeing installed,
575 # and so we must redirect to OPAC maintenance page or to the WebInstaller
576 # also, if OpacMaintenance is ON, OPAC should redirect to maintenance
577 if (C4::Context->preference('OpacMaintenance') && $type eq 'opac') {
578 warn "OPAC Install required, redirecting to maintenance";
579 print $query->redirect("/cgi-bin/koha/maintenance.pl");
580 safe_exit;
582 unless ( $version = C4::Context->preference('Version') ) { # assignment, not comparison
583 if ( $type ne 'opac' ) {
584 warn "Install required, redirecting to Installer";
585 print $query->redirect("/cgi-bin/koha/installer/install.pl");
586 } else {
587 warn "OPAC Install required, redirecting to maintenance";
588 print $query->redirect("/cgi-bin/koha/maintenance.pl");
590 safe_exit;
593 # check that database and koha version are the same
594 # there is no DB version, it's a fresh install,
595 # go to web installer
596 # there is a DB version, compare it to the code version
597 my $kohaversion=C4::Context::KOHAVERSION;
598 # remove the 3 last . to have a Perl number
599 $kohaversion =~ s/(.*\..*)\.(.*)\.(.*)/$1$2$3/;
600 $debug and print STDERR "kohaversion : $kohaversion\n";
601 if ($version < $kohaversion){
602 my $warning = "Database update needed, redirecting to %s. Database is $version and Koha is $kohaversion";
603 if ($type ne 'opac'){
604 warn sprintf($warning, 'Installer');
605 print $query->redirect("/cgi-bin/koha/installer/install.pl?step=3");
606 } else {
607 warn sprintf("OPAC: " . $warning, 'maintenance');
608 print $query->redirect("/cgi-bin/koha/maintenance.pl");
610 safe_exit;
614 sub _session_log {
615 (@_) or return 0;
616 open my $fh, '>>', "/tmp/sessionlog" or warn "ERROR: Cannot append to /tmp/sessionlog";
617 printf $fh join("\n",@_);
618 close $fh;
621 sub _timeout_syspref {
622 my $timeout = C4::Context->preference('timeout') || 600;
623 # value in days, convert in seconds
624 if ($timeout =~ /(\d+)[dD]/) {
625 $timeout = $1 * 86400;
627 return $timeout;
630 sub checkauth {
631 my $query = shift;
632 $debug and warn "Checking Auth";
633 # $authnotrequired will be set for scripts which will run without authentication
634 my $authnotrequired = shift;
635 my $flagsrequired = shift;
636 my $type = shift;
637 my $persona = shift;
638 $type = 'opac' unless $type;
640 my $dbh = C4::Context->dbh;
641 my $timeout = _timeout_syspref();
643 _version_check($type,$query);
644 # state variables
645 my $loggedin = 0;
646 my %info;
647 my ( $userid, $cookie, $sessionID, $flags, $barshelves, $pubshelves );
648 my $logout = $query->param('logout.x');
650 # This parameter is the name of the CAS server we want to authenticate against,
651 # when using authentication against multiple CAS servers, as configured in Auth_cas_servers.yaml
652 my $casparam = $query->param('cas');
653 my $q_userid = $query->param('userid') // '';
655 if ( $userid = $ENV{'REMOTE_USER'} ) {
656 # Using Basic Authentication, no cookies required
657 $cookie = $query->cookie(
658 -name => 'CGISESSID',
659 -value => '',
660 -expires => '',
661 -HttpOnly => 1,
663 $loggedin = 1;
665 elsif ( $persona ){
666 # we dont want to set a session because we are being called by a persona callback
668 elsif ( $sessionID = $query->cookie("CGISESSID") )
669 { # assignment, not comparison
670 my $session = get_session($sessionID);
671 C4::Context->_new_userenv($sessionID);
672 my ($ip, $lasttime, $sessiontype);
673 my $s_userid = '';
674 if ($session){
675 $s_userid = $session->param('id') // '';
676 C4::Context::set_userenv(
677 $session->param('number'), $s_userid,
678 $session->param('cardnumber'), $session->param('firstname'),
679 $session->param('surname'), $session->param('branch'),
680 $session->param('branchname'), $session->param('flags'),
681 $session->param('emailaddress'), $session->param('branchprinter'),
682 $session->param('persona')
684 C4::Context::set_shelves_userenv('bar',$session->param('barshelves'));
685 C4::Context::set_shelves_userenv('pub',$session->param('pubshelves'));
686 C4::Context::set_shelves_userenv('tot',$session->param('totshelves'));
687 $debug and printf STDERR "AUTH_SESSION: (%s)\t%s %s - %s\n", map {$session->param($_)} qw(cardnumber firstname surname branch) ;
688 $ip = $session->param('ip');
689 $lasttime = $session->param('lasttime');
690 $userid = $s_userid;
691 $sessiontype = $session->param('sessiontype') || '';
693 if ( ( $query->param('koha_login_context') && ($q_userid ne $s_userid) )
694 || ( $cas && $query->param('ticket') ) ) {
695 #if a user enters an id ne to the id in the current session, we need to log them in...
696 #first we need to clear the anonymous session...
697 $debug and warn "query id = $q_userid but session id = $s_userid";
698 $session->flush;
699 $session->delete();
700 C4::Context->_unset_userenv($sessionID);
701 $sessionID = undef;
702 $userid = undef;
704 elsif ($logout) {
705 # voluntary logout the user
706 $session->flush;
707 $session->delete();
708 C4::Context->_unset_userenv($sessionID);
709 #_session_log(sprintf "%20s from %16s logged out at %30s (manually).\n", $userid,$ip,(strftime "%c",localtime));
710 $sessionID = undef;
711 $userid = undef;
713 if ($cas and $caslogout) {
714 logout_cas($query);
717 elsif ( !$lasttime || ($lasttime < time() - $timeout) ) {
718 # timed logout
719 $info{'timed_out'} = 1;
720 $session->delete() if $session;
721 C4::Context->_unset_userenv($sessionID);
722 #_session_log(sprintf "%20s from %16s logged out at %30s (inactivity).\n", $userid,$ip,(strftime "%c",localtime));
723 $userid = undef;
724 $sessionID = undef;
726 elsif ( $ip ne $ENV{'REMOTE_ADDR'} ) {
727 # Different ip than originally logged in from
728 $info{'oldip'} = $ip;
729 $info{'newip'} = $ENV{'REMOTE_ADDR'};
730 $info{'different_ip'} = 1;
731 $session->delete();
732 C4::Context->_unset_userenv($sessionID);
733 #_session_log(sprintf "%20s from %16s logged out at %30s (ip changed to %16s).\n", $userid,$ip,(strftime "%c",localtime), $info{'newip'});
734 $sessionID = undef;
735 $userid = undef;
737 else {
738 $cookie = $query->cookie(
739 -name => 'CGISESSID',
740 -value => $session->id,
741 -HttpOnly => 1
743 $session->param( 'lasttime', time() );
744 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...
745 $flags = haspermission($userid, $flagsrequired);
746 if ($flags) {
747 $loggedin = 1;
748 } else {
749 $info{'nopermission'} = 1;
754 unless ($userid || $sessionID) {
756 #we initiate a session prior to checking for a username to allow for anonymous sessions...
757 my $session = get_session("") or die "Auth ERROR: Cannot get_session()";
758 my $sessionID = $session->id;
759 C4::Context->_new_userenv($sessionID);
760 $cookie = $query->cookie(
761 -name => 'CGISESSID',
762 -value => $session->id,
763 -HttpOnly => 1
765 $userid = $q_userid;
766 my $pki_field = C4::Context->preference('AllowPKIAuth');
767 if (! defined($pki_field) ) {
768 print STDERR "ERROR: Missing system preference AllowPKIAuth.\n";
769 $pki_field = 'None';
771 if ( ( $cas && $query->param('ticket') )
772 || $userid
773 || $pki_field ne 'None'
774 || $persona )
776 my $password = $query->param('password');
778 my ( $return, $cardnumber );
779 if ( $cas && $query->param('ticket') ) {
780 my $retuserid;
781 ( $return, $cardnumber, $retuserid ) =
782 checkpw( $dbh, $userid, $password, $query );
783 $userid = $retuserid;
784 $info{'invalidCasLogin'} = 1 unless ($return);
787 elsif ($persona) {
788 my $value = $persona;
790 # If we're looking up the email, there's a chance that the person
791 # doesn't have a userid. So if there is none, we pass along the
792 # borrower number, and the bits of code that need to know the user
793 # ID will have to be smart enough to handle that.
794 require C4::Members;
795 my @users_info = C4::Members::GetBorrowersWithEmail($value);
796 if (@users_info) {
798 # First the userid, then the borrowernum
799 $value = $users_info[0][1] || $users_info[0][0];
801 else {
802 undef $value;
804 $return = $value ? 1 : 0;
805 $userid = $value;
808 elsif (
809 ( $pki_field eq 'Common Name' && $ENV{'SSL_CLIENT_S_DN_CN'} )
810 || ( $pki_field eq 'emailAddress'
811 && $ENV{'SSL_CLIENT_S_DN_Email'} )
814 my $value;
815 if ( $pki_field eq 'Common Name' ) {
816 $value = $ENV{'SSL_CLIENT_S_DN_CN'};
818 elsif ( $pki_field eq 'emailAddress' ) {
819 $value = $ENV{'SSL_CLIENT_S_DN_Email'};
821 # If we're looking up the email, there's a chance that the person
822 # doesn't have a userid. So if there is none, we pass along the
823 # borrower number, and the bits of code that need to know the user
824 # ID will have to be smart enough to handle that.
825 require C4::Members;
826 my @users_info = C4::Members::GetBorrowersWithEmail($value);
827 if (@users_info) {
829 # First the userid, then the borrowernum
830 $value = $users_info[0][1] || $users_info[0][0];
831 } else {
832 undef $value;
837 $return = $value ? 1 : 0;
838 $userid = $value;
841 else {
842 my $retuserid;
843 ( $return, $cardnumber, $retuserid ) =
844 checkpw( $dbh, $userid, $password, $query );
845 $userid = $retuserid if ( $retuserid );
847 if ($return) {
848 #_session_log(sprintf "%20s from %16s logged in at %30s.\n", $userid,$ENV{'REMOTE_ADDR'},(strftime '%c', localtime));
849 if ( $flags = haspermission( $userid, $flagsrequired ) ) {
850 $loggedin = 1;
852 else {
853 $info{'nopermission'} = 1;
854 C4::Context->_unset_userenv($sessionID);
856 my ($borrowernumber, $firstname, $surname, $userflags,
857 $branchcode, $branchname, $branchprinter, $emailaddress);
859 if ( $return == 1 ) {
860 my $select = "
861 SELECT borrowernumber, firstname, surname, flags, borrowers.branchcode,
862 branches.branchname as branchname,
863 branches.branchprinter as branchprinter,
864 email
865 FROM borrowers
866 LEFT JOIN branches on borrowers.branchcode=branches.branchcode
868 my $sth = $dbh->prepare("$select where userid=?");
869 $sth->execute($userid);
870 unless ($sth->rows) {
871 $debug and print STDERR "AUTH_1: no rows for userid='$userid'\n";
872 $sth = $dbh->prepare("$select where cardnumber=?");
873 $sth->execute($cardnumber);
875 unless ($sth->rows) {
876 $debug and print STDERR "AUTH_2a: no rows for cardnumber='$cardnumber'\n";
877 $sth->execute($userid);
878 unless ($sth->rows) {
879 $debug and print STDERR "AUTH_2b: no rows for userid='$userid' AS cardnumber\n";
883 if ($sth->rows) {
884 ($borrowernumber, $firstname, $surname, $userflags,
885 $branchcode, $branchname, $branchprinter, $emailaddress) = $sth->fetchrow;
886 $debug and print STDERR "AUTH_3 results: " .
887 "$cardnumber,$borrowernumber,$userid,$firstname,$surname,$userflags,$branchcode,$emailaddress\n";
888 } else {
889 print STDERR "AUTH_3: no results for userid='$userid', cardnumber='$cardnumber'.\n";
892 # launch a sequence to check if we have a ip for the branch, i
893 # if we have one we replace the branchcode of the userenv by the branch bound in the ip.
895 my $ip = $ENV{'REMOTE_ADDR'};
896 # if they specify at login, use that
897 if ($query->param('branch')) {
898 $branchcode = $query->param('branch');
899 $branchname = GetBranchName($branchcode);
901 my $branches = GetBranches();
902 if (C4::Context->boolean_preference('IndependentBranches') && C4::Context->boolean_preference('Autolocation')){
903 # we have to check they are coming from the right ip range
904 my $domain = $branches->{$branchcode}->{'branchip'};
905 if ($ip !~ /^$domain/){
906 $loggedin=0;
907 $info{'wrongip'} = 1;
911 my @branchesloop;
912 foreach my $br ( keys %$branches ) {
913 # now we work with the treatment of ip
914 my $domain = $branches->{$br}->{'branchip'};
915 if ( $domain && $ip =~ /^$domain/ ) {
916 $branchcode = $branches->{$br}->{'branchcode'};
918 # new op dev : add the branchprinter and branchname in the cookie
919 $branchprinter = $branches->{$br}->{'branchprinter'};
920 $branchname = $branches->{$br}->{'branchname'};
923 $session->param('number',$borrowernumber);
924 $session->param('id',$userid);
925 $session->param('cardnumber',$cardnumber);
926 $session->param('firstname',$firstname);
927 $session->param('surname',$surname);
928 $session->param('branch',$branchcode);
929 $session->param('branchname',$branchname);
930 $session->param('flags',$userflags);
931 $session->param('emailaddress',$emailaddress);
932 $session->param('ip',$session->remote_addr());
933 $session->param('lasttime',time());
934 $debug and printf STDERR "AUTH_4: (%s)\t%s %s - %s\n", map {$session->param($_)} qw(cardnumber firstname surname branch) ;
936 elsif ( $return == 2 ) {
937 #We suppose the user is the superlibrarian
938 $borrowernumber = 0;
939 $session->param('number',0);
940 $session->param('id',C4::Context->config('user'));
941 $session->param('cardnumber',C4::Context->config('user'));
942 $session->param('firstname',C4::Context->config('user'));
943 $session->param('surname',C4::Context->config('user'));
944 $session->param('branch','NO_LIBRARY_SET');
945 $session->param('branchname','NO_LIBRARY_SET');
946 $session->param('flags',1);
947 $session->param('emailaddress', C4::Context->preference('KohaAdminEmailAddress'));
948 $session->param('ip',$session->remote_addr());
949 $session->param('lasttime',time());
951 if ($persona){
952 $session->param('persona',1);
954 C4::Context::set_userenv(
955 $session->param('number'), $session->param('id'),
956 $session->param('cardnumber'), $session->param('firstname'),
957 $session->param('surname'), $session->param('branch'),
958 $session->param('branchname'), $session->param('flags'),
959 $session->param('emailaddress'), $session->param('branchprinter'),
960 $session->param('persona')
964 else {
965 if ($userid) {
966 $info{'invalid_username_or_password'} = 1;
967 C4::Context->_unset_userenv($sessionID);
970 } # END if ( $userid = $query->param('userid') )
971 elsif ($type eq "opac") {
972 # if we are here this is an anonymous session; add public lists to it and a few other items...
973 # anonymous sessions are created only for the OPAC
974 $debug and warn "Initiating an anonymous session...";
976 # setting a couple of other session vars...
977 $session->param('ip',$session->remote_addr());
978 $session->param('lasttime',time());
979 $session->param('sessiontype','anon');
981 } # END unless ($userid)
983 # finished authentification, now respond
984 if ( $loggedin || $authnotrequired )
986 # successful login
987 unless ($cookie) {
988 $cookie = $query->cookie(
989 -name => 'CGISESSID',
990 -value => '',
991 -HttpOnly => 1
994 return ( $userid, $cookie, $sessionID, $flags );
999 # AUTH rejected, show the login/password template, after checking the DB.
1003 # get the inputs from the incoming query
1004 my @inputs = ();
1005 foreach my $name ( param $query) {
1006 (next) if ( $name eq 'userid' || $name eq 'password' || $name eq 'ticket' );
1007 my $value = $query->param($name);
1008 push @inputs, { name => $name, value => $value };
1011 my $LibraryNameTitle = C4::Context->preference("LibraryName");
1012 $LibraryNameTitle =~ s/<(?:\/?)(?:br|p)\s*(?:\/?)>/ /sgi;
1013 $LibraryNameTitle =~ s/<(?:[^<>'"]|'(?:[^']*)'|"(?:[^"]*)")*>//sg;
1015 my $template_name = ( $type eq 'opac' ) ? 'opac-auth.tmpl' : 'auth.tmpl';
1016 my $template = C4::Templates::gettemplate($template_name, $type, $query );
1017 $template->param(
1018 branchloop => GetBranchesLoop(),
1019 opaccolorstylesheet => C4::Context->preference("opaccolorstylesheet"),
1020 opaclayoutstylesheet => C4::Context->preference("opaclayoutstylesheet"),
1021 login => 1,
1022 INPUTS => \@inputs,
1023 casAuthentication => C4::Context->preference("casAuthentication"),
1024 suggestion => C4::Context->preference("suggestion"),
1025 virtualshelves => C4::Context->preference("virtualshelves"),
1026 LibraryName => "" . C4::Context->preference("LibraryName"),
1027 LibraryNameTitle => "" . $LibraryNameTitle,
1028 opacuserlogin => C4::Context->preference("opacuserlogin"),
1029 OpacNav => C4::Context->preference("OpacNav"),
1030 OpacNavRight => C4::Context->preference("OpacNavRight"),
1031 OpacNavBottom => C4::Context->preference("OpacNavBottom"),
1032 opaccredits => C4::Context->preference("opaccredits"),
1033 OpacFavicon => C4::Context->preference("OpacFavicon"),
1034 opacreadinghistory => C4::Context->preference("opacreadinghistory"),
1035 opacsmallimage => C4::Context->preference("opacsmallimage"),
1036 opaclanguagesdisplay => C4::Context->preference("opaclanguagesdisplay"),
1037 opacuserjs => C4::Context->preference("opacuserjs"),
1038 opacbookbag => "" . C4::Context->preference("opacbookbag"),
1039 OpacCloud => C4::Context->preference("OpacCloud"),
1040 OpacTopissue => C4::Context->preference("OpacTopissue"),
1041 OpacAuthorities => C4::Context->preference("OpacAuthorities"),
1042 OpacBrowser => C4::Context->preference("OpacBrowser"),
1043 opacheader => C4::Context->preference("opacheader"),
1044 TagsEnabled => C4::Context->preference("TagsEnabled"),
1045 OPACUserCSS => C4::Context->preference("OPACUserCSS"),
1046 intranetcolorstylesheet => C4::Context->preference("intranetcolorstylesheet"),
1047 intranetstylesheet => C4::Context->preference("intranetstylesheet"),
1048 intranetbookbag => C4::Context->preference("intranetbookbag"),
1049 IntranetNav => C4::Context->preference("IntranetNav"),
1050 IntranetFavicon => C4::Context->preference("IntranetFavicon"),
1051 intranetuserjs => C4::Context->preference("intranetuserjs"),
1052 IndependentBranches=> C4::Context->preference("IndependentBranches"),
1053 AutoLocation => C4::Context->preference("AutoLocation"),
1054 wrongip => $info{'wrongip'},
1055 PatronSelfRegistration => C4::Context->preference("PatronSelfRegistration"),
1056 PatronSelfRegistrationDefaultCategory => C4::Context->preference("PatronSelfRegistrationDefaultCategory"),
1057 persona => C4::Context->preference("Persona"),
1058 opac_css_override => $ENV{'OPAC_CSS_OVERRIDE'},
1061 $template->param( OpacPublic => C4::Context->preference("OpacPublic"));
1062 $template->param( loginprompt => 1 ) unless $info{'nopermission'};
1064 if($type eq 'opac'){
1065 my ($total, $pubshelves) = C4::VirtualShelves::GetSomeShelfNames(undef, 'MASTHEAD');
1066 $template->param(
1067 pubshelves => $total->{pubtotal},
1068 pubshelvesloop => $pubshelves,
1072 if ($cas) {
1074 # Is authentication against multiple CAS servers enabled?
1075 if (C4::Auth_with_cas::multipleAuth && !$casparam) {
1076 my $casservers = C4::Auth_with_cas::getMultipleAuth();
1077 my @tmplservers;
1078 foreach my $key (keys %$casservers) {
1079 push @tmplservers, {name => $key, value => login_cas_url($query, $key) . "?cas=$key" };
1081 $template->param(
1082 casServersLoop => \@tmplservers
1084 } else {
1085 $template->param(
1086 casServerUrl => login_cas_url($query),
1090 $template->param(
1091 invalidCasLogin => $info{'invalidCasLogin'}
1095 my $self_url = $query->url( -absolute => 1 );
1096 $template->param(
1097 url => $self_url,
1098 LibraryName => C4::Context->preference("LibraryName"),
1100 $template->param( %info );
1101 # $cookie = $query->cookie(CGISESSID => $session->id
1102 # );
1103 print $query->header(
1104 -type => 'text/html',
1105 -charset => 'utf-8',
1106 -cookie => $cookie
1108 $template->output;
1109 safe_exit;
1112 =head2 check_api_auth
1114 ($status, $cookie, $sessionId) = check_api_auth($query, $userflags);
1116 Given a CGI query containing the parameters 'userid' and 'password' and/or a session
1117 cookie, determine if the user has the privileges specified by C<$userflags>.
1119 C<check_api_auth> is is meant for authenticating users of web services, and
1120 consequently will always return and will not attempt to redirect the user
1121 agent.
1123 If a valid session cookie is already present, check_api_auth will return a status
1124 of "ok", the cookie, and the Koha session ID.
1126 If no session cookie is present, check_api_auth will check the 'userid' and 'password
1127 parameters and create a session cookie and Koha session if the supplied credentials
1128 are OK.
1130 Possible return values in C<$status> are:
1132 =over
1134 =item "ok" -- user authenticated; C<$cookie> and C<$sessionid> have valid values.
1136 =item "failed" -- credentials are not correct; C<$cookie> and C<$sessionid> are undef
1138 =item "maintenance" -- DB is in maintenance mode; no login possible at the moment
1140 =item "expired -- session cookie has expired; API user should resubmit userid and password
1142 =back
1144 =cut
1146 sub check_api_auth {
1147 my $query = shift;
1148 my $flagsrequired = shift;
1150 my $dbh = C4::Context->dbh;
1151 my $timeout = _timeout_syspref();
1153 unless (C4::Context->preference('Version')) {
1154 # database has not been installed yet
1155 return ("maintenance", undef, undef);
1157 my $kohaversion=C4::Context::KOHAVERSION;
1158 $kohaversion =~ s/(.*\..*)\.(.*)\.(.*)/$1$2$3/;
1159 if (C4::Context->preference('Version') < $kohaversion) {
1160 # database in need of version update; assume that
1161 # no API should be called while databsae is in
1162 # this condition.
1163 return ("maintenance", undef, undef);
1166 # FIXME -- most of what follows is a copy-and-paste
1167 # of code from checkauth. There is an obvious need
1168 # for refactoring to separate the various parts of
1169 # the authentication code, but as of 2007-11-19 this
1170 # is deferred so as to not introduce bugs into the
1171 # regular authentication code for Koha 3.0.
1173 # see if we have a valid session cookie already
1174 # however, if a userid parameter is present (i.e., from
1175 # a form submission, assume that any current cookie
1176 # is to be ignored
1177 my $sessionID = undef;
1178 unless ($query->param('userid')) {
1179 $sessionID = $query->cookie("CGISESSID");
1181 if ($sessionID && not ($cas && $query->param('PT')) ) {
1182 my $session = get_session($sessionID);
1183 C4::Context->_new_userenv($sessionID);
1184 if ($session) {
1185 C4::Context::set_userenv(
1186 $session->param('number'), $session->param('id'),
1187 $session->param('cardnumber'), $session->param('firstname'),
1188 $session->param('surname'), $session->param('branch'),
1189 $session->param('branchname'), $session->param('flags'),
1190 $session->param('emailaddress'), $session->param('branchprinter')
1193 my $ip = $session->param('ip');
1194 my $lasttime = $session->param('lasttime');
1195 my $userid = $session->param('id');
1196 if ( $lasttime < time() - $timeout ) {
1197 # time out
1198 $session->delete();
1199 C4::Context->_unset_userenv($sessionID);
1200 $userid = undef;
1201 $sessionID = undef;
1202 return ("expired", undef, undef);
1203 } elsif ( $ip ne $ENV{'REMOTE_ADDR'} ) {
1204 # IP address changed
1205 $session->delete();
1206 C4::Context->_unset_userenv($sessionID);
1207 $userid = undef;
1208 $sessionID = undef;
1209 return ("expired", undef, undef);
1210 } else {
1211 my $cookie = $query->cookie(
1212 -name => 'CGISESSID',
1213 -value => $session->id,
1214 -HttpOnly => 1,
1216 $session->param('lasttime',time());
1217 my $flags = haspermission($userid, $flagsrequired);
1218 if ($flags) {
1219 return ("ok", $cookie, $sessionID);
1220 } else {
1221 $session->delete();
1222 C4::Context->_unset_userenv($sessionID);
1223 $userid = undef;
1224 $sessionID = undef;
1225 return ("failed", undef, undef);
1228 } else {
1229 return ("expired", undef, undef);
1231 } else {
1232 # new login
1233 my $userid = $query->param('userid');
1234 my $password = $query->param('password');
1235 my ($return, $cardnumber);
1237 # Proxy CAS auth
1238 if ($cas && $query->param('PT')) {
1239 my $retuserid;
1240 $debug and print STDERR "## check_api_auth - checking CAS\n";
1241 # In case of a CAS authentication, we use the ticket instead of the password
1242 my $PT = $query->param('PT');
1243 ($return,$cardnumber,$userid) = check_api_auth_cas($dbh, $PT, $query); # EXTERNAL AUTH
1244 } else {
1245 # User / password auth
1246 unless ($userid and $password) {
1247 # caller did something wrong, fail the authenticateion
1248 return ("failed", undef, undef);
1250 ( $return, $cardnumber ) = checkpw( $dbh, $userid, $password, $query );
1253 if ($return and haspermission( $userid, $flagsrequired)) {
1254 my $session = get_session("");
1255 return ("failed", undef, undef) unless $session;
1257 my $sessionID = $session->id;
1258 C4::Context->_new_userenv($sessionID);
1259 my $cookie = $query->cookie(
1260 -name => 'CGISESSID',
1261 -value => $sessionID,
1262 -HttpOnly => 1,
1264 if ( $return == 1 ) {
1265 my (
1266 $borrowernumber, $firstname, $surname,
1267 $userflags, $branchcode, $branchname,
1268 $branchprinter, $emailaddress
1270 my $sth =
1271 $dbh->prepare(
1272 "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=?"
1274 $sth->execute($userid);
1276 $borrowernumber, $firstname, $surname,
1277 $userflags, $branchcode, $branchname,
1278 $branchprinter, $emailaddress
1279 ) = $sth->fetchrow if ( $sth->rows );
1281 unless ($sth->rows ) {
1282 my $sth = $dbh->prepare(
1283 "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=?"
1285 $sth->execute($cardnumber);
1287 $borrowernumber, $firstname, $surname,
1288 $userflags, $branchcode, $branchname,
1289 $branchprinter, $emailaddress
1290 ) = $sth->fetchrow if ( $sth->rows );
1292 unless ( $sth->rows ) {
1293 $sth->execute($userid);
1295 $borrowernumber, $firstname, $surname, $userflags,
1296 $branchcode, $branchname, $branchprinter, $emailaddress
1297 ) = $sth->fetchrow if ( $sth->rows );
1301 my $ip = $ENV{'REMOTE_ADDR'};
1302 # if they specify at login, use that
1303 if ($query->param('branch')) {
1304 $branchcode = $query->param('branch');
1305 $branchname = GetBranchName($branchcode);
1307 my $branches = GetBranches();
1308 my @branchesloop;
1309 foreach my $br ( keys %$branches ) {
1310 # now we work with the treatment of ip
1311 my $domain = $branches->{$br}->{'branchip'};
1312 if ( $domain && $ip =~ /^$domain/ ) {
1313 $branchcode = $branches->{$br}->{'branchcode'};
1315 # new op dev : add the branchprinter and branchname in the cookie
1316 $branchprinter = $branches->{$br}->{'branchprinter'};
1317 $branchname = $branches->{$br}->{'branchname'};
1320 $session->param('number',$borrowernumber);
1321 $session->param('id',$userid);
1322 $session->param('cardnumber',$cardnumber);
1323 $session->param('firstname',$firstname);
1324 $session->param('surname',$surname);
1325 $session->param('branch',$branchcode);
1326 $session->param('branchname',$branchname);
1327 $session->param('flags',$userflags);
1328 $session->param('emailaddress',$emailaddress);
1329 $session->param('ip',$session->remote_addr());
1330 $session->param('lasttime',time());
1331 } elsif ( $return == 2 ) {
1332 #We suppose the user is the superlibrarian
1333 $session->param('number',0);
1334 $session->param('id',C4::Context->config('user'));
1335 $session->param('cardnumber',C4::Context->config('user'));
1336 $session->param('firstname',C4::Context->config('user'));
1337 $session->param('surname',C4::Context->config('user'));
1338 $session->param('branch','NO_LIBRARY_SET');
1339 $session->param('branchname','NO_LIBRARY_SET');
1340 $session->param('flags',1);
1341 $session->param('emailaddress', C4::Context->preference('KohaAdminEmailAddress'));
1342 $session->param('ip',$session->remote_addr());
1343 $session->param('lasttime',time());
1345 C4::Context::set_userenv(
1346 $session->param('number'), $session->param('id'),
1347 $session->param('cardnumber'), $session->param('firstname'),
1348 $session->param('surname'), $session->param('branch'),
1349 $session->param('branchname'), $session->param('flags'),
1350 $session->param('emailaddress'), $session->param('branchprinter')
1352 return ("ok", $cookie, $sessionID);
1353 } else {
1354 return ("failed", undef, undef);
1359 =head2 check_cookie_auth
1361 ($status, $sessionId) = check_api_auth($cookie, $userflags);
1363 Given a CGISESSID cookie set during a previous login to Koha, determine
1364 if the user has the privileges specified by C<$userflags>.
1366 C<check_cookie_auth> is meant for authenticating special services
1367 such as tools/upload-file.pl that are invoked by other pages that
1368 have been authenticated in the usual way.
1370 Possible return values in C<$status> are:
1372 =over
1374 =item "ok" -- user authenticated; C<$sessionID> have valid values.
1376 =item "failed" -- credentials are not correct; C<$sessionid> are undef
1378 =item "maintenance" -- DB is in maintenance mode; no login possible at the moment
1380 =item "expired -- session cookie has expired; API user should resubmit userid and password
1382 =back
1384 =cut
1386 sub check_cookie_auth {
1387 my $cookie = shift;
1388 my $flagsrequired = shift;
1390 my $dbh = C4::Context->dbh;
1391 my $timeout = _timeout_syspref();
1393 unless (C4::Context->preference('Version')) {
1394 # database has not been installed yet
1395 return ("maintenance", undef);
1397 my $kohaversion=C4::Context::KOHAVERSION;
1398 $kohaversion =~ s/(.*\..*)\.(.*)\.(.*)/$1$2$3/;
1399 if (C4::Context->preference('Version') < $kohaversion) {
1400 # database in need of version update; assume that
1401 # no API should be called while databsae is in
1402 # this condition.
1403 return ("maintenance", undef);
1406 # FIXME -- most of what follows is a copy-and-paste
1407 # of code from checkauth. There is an obvious need
1408 # for refactoring to separate the various parts of
1409 # the authentication code, but as of 2007-11-23 this
1410 # is deferred so as to not introduce bugs into the
1411 # regular authentication code for Koha 3.0.
1413 # see if we have a valid session cookie already
1414 # however, if a userid parameter is present (i.e., from
1415 # a form submission, assume that any current cookie
1416 # is to be ignored
1417 unless (defined $cookie and $cookie) {
1418 return ("failed", undef);
1420 my $sessionID = $cookie;
1421 my $session = get_session($sessionID);
1422 C4::Context->_new_userenv($sessionID);
1423 if ($session) {
1424 C4::Context::set_userenv(
1425 $session->param('number'), $session->param('id'),
1426 $session->param('cardnumber'), $session->param('firstname'),
1427 $session->param('surname'), $session->param('branch'),
1428 $session->param('branchname'), $session->param('flags'),
1429 $session->param('emailaddress'), $session->param('branchprinter')
1432 my $ip = $session->param('ip');
1433 my $lasttime = $session->param('lasttime');
1434 my $userid = $session->param('id');
1435 if ( $lasttime < time() - $timeout ) {
1436 # time out
1437 $session->delete();
1438 C4::Context->_unset_userenv($sessionID);
1439 $userid = undef;
1440 $sessionID = undef;
1441 return ("expired", undef);
1442 } elsif ( $ip ne $ENV{'REMOTE_ADDR'} ) {
1443 # IP address changed
1444 $session->delete();
1445 C4::Context->_unset_userenv($sessionID);
1446 $userid = undef;
1447 $sessionID = undef;
1448 return ("expired", undef);
1449 } else {
1450 $session->param('lasttime',time());
1451 my $flags = haspermission($userid, $flagsrequired);
1452 if ($flags) {
1453 return ("ok", $sessionID);
1454 } else {
1455 $session->delete();
1456 C4::Context->_unset_userenv($sessionID);
1457 $userid = undef;
1458 $sessionID = undef;
1459 return ("failed", undef);
1462 } else {
1463 return ("expired", undef);
1467 =head2 get_session
1469 use CGI::Session;
1470 my $session = get_session($sessionID);
1472 Given a session ID, retrieve the CGI::Session object used to store
1473 the session's state. The session object can be used to store
1474 data that needs to be accessed by different scripts during a
1475 user's session.
1477 If the C<$sessionID> parameter is an empty string, a new session
1478 will be created.
1480 =cut
1482 sub get_session {
1483 my $sessionID = shift;
1484 my $storage_method = C4::Context->preference('SessionStorage');
1485 my $dbh = C4::Context->dbh;
1486 my $session;
1487 if ($storage_method eq 'mysql'){
1488 $session = new CGI::Session("driver:MySQL;serializer:yaml;id:md5", $sessionID, {Handle=>$dbh});
1490 elsif ($storage_method eq 'Pg') {
1491 $session = new CGI::Session("driver:PostgreSQL;serializer:yaml;id:md5", $sessionID, {Handle=>$dbh});
1493 elsif ($storage_method eq 'memcached' && C4::Context->ismemcached){
1494 $session = new CGI::Session("driver:memcached;serializer:yaml;id:md5", $sessionID, { Memcached => C4::Context->memcached } );
1496 else {
1497 # catch all defaults to tmp should work on all systems
1498 $session = new CGI::Session("driver:File;serializer:yaml;id:md5", $sessionID, {Directory=>'/tmp'});
1500 return $session;
1503 sub checkpw {
1504 my ( $dbh, $userid, $password, $query ) = @_;
1506 if ($ldap) {
1507 $debug and print STDERR "## checkpw - checking LDAP\n";
1508 my ($retval,$retcard,$retuserid) = checkpw_ldap(@_); # EXTERNAL AUTH
1509 ($retval) and return ($retval,$retcard,$retuserid);
1512 if ($cas && $query && $query->param('ticket')) {
1513 $debug and print STDERR "## checkpw - checking CAS\n";
1514 # In case of a CAS authentication, we use the ticket instead of the password
1515 my $ticket = $query->param('ticket');
1516 my ($retval,$retcard,$retuserid) = checkpw_cas($dbh, $ticket, $query); # EXTERNAL AUTH
1517 ($retval) and return ($retval,$retcard,$retuserid);
1518 return 0;
1521 return checkpw_internal(@_)
1524 sub checkpw_internal {
1525 my ( $dbh, $userid, $password ) = @_;
1527 my $sth =
1528 $dbh->prepare(
1529 "select password,cardnumber,borrowernumber,userid,firstname,surname,branchcode,flags from borrowers where userid=?"
1531 $sth->execute($userid);
1532 if ( $sth->rows ) {
1533 my ( $stored_hash, $cardnumber, $borrowernumber, $userid, $firstname,
1534 $surname, $branchcode, $flags )
1535 = $sth->fetchrow;
1537 if ( checkpw_hash($password, $stored_hash) ) {
1539 C4::Context->set_userenv( "$borrowernumber", $userid, $cardnumber,
1540 $firstname, $surname, $branchcode, $flags );
1541 return 1, $cardnumber, $userid;
1544 $sth =
1545 $dbh->prepare(
1546 "select password,cardnumber,borrowernumber,userid, firstname,surname,branchcode,flags from borrowers where cardnumber=?"
1548 $sth->execute($userid);
1549 if ( $sth->rows ) {
1550 my ( $stored_hash, $cardnumber, $borrowernumber, $userid, $firstname,
1551 $surname, $branchcode, $flags )
1552 = $sth->fetchrow;
1554 if ( checkpw_hash($password, $stored_hash) ) {
1556 C4::Context->set_userenv( $borrowernumber, $userid, $cardnumber,
1557 $firstname, $surname, $branchcode, $flags );
1558 return 1, $cardnumber, $userid;
1561 if ( $userid && $userid eq C4::Context->config('user')
1562 && "$password" eq C4::Context->config('pass') )
1565 # Koha superuser account
1566 # C4::Context->set_userenv(0,0,C4::Context->config('user'),C4::Context->config('user'),C4::Context->config('user'),"",1);
1567 return 2;
1569 if ( $userid && $userid eq 'demo'
1570 && "$password" eq 'demo'
1571 && C4::Context->config('demo') )
1574 # DEMO => the demo user is allowed to do everything (if demo set to 1 in koha.conf
1575 # some features won't be effective : modify systempref, modify MARC structure,
1576 return 2;
1578 return 0;
1581 sub checkpw_hash {
1582 my ( $password, $stored_hash ) = @_;
1584 return if $stored_hash eq '!';
1586 # check what encryption algorithm was implemented: Bcrypt - if the hash starts with '$2' it is Bcrypt else md5
1587 my $hash;
1588 if ( substr($stored_hash,0,2) eq '$2') {
1589 $hash = hash_password($password, $stored_hash);
1590 } else {
1591 $hash = md5_base64($password);
1593 return $hash eq $stored_hash;
1596 =head2 getuserflags
1598 my $authflags = getuserflags($flags, $userid, [$dbh]);
1600 Translates integer flags into permissions strings hash.
1602 C<$flags> is the integer userflags value ( borrowers.userflags )
1603 C<$userid> is the members.userid, used for building subpermissions
1604 C<$authflags> is a hashref of permissions
1606 =cut
1608 sub getuserflags {
1609 my $flags = shift;
1610 my $userid = shift;
1611 my $dbh = @_ ? shift : C4::Context->dbh;
1612 my $userflags;
1614 # I don't want to do this, but if someone logs in as the database
1615 # user, it would be preferable not to spam them to death with
1616 # numeric warnings. So, we make $flags numeric.
1617 no warnings 'numeric';
1618 $flags += 0;
1620 my $sth = $dbh->prepare("SELECT bit, flag, defaulton FROM userflags");
1621 $sth->execute;
1623 while ( my ( $bit, $flag, $defaulton ) = $sth->fetchrow ) {
1624 if ( ( $flags & ( 2**$bit ) ) || $defaulton ) {
1625 $userflags->{$flag} = 1;
1627 else {
1628 $userflags->{$flag} = 0;
1631 # get subpermissions and merge with top-level permissions
1632 my $user_subperms = get_user_subpermissions($userid);
1633 foreach my $module (keys %$user_subperms) {
1634 next if $userflags->{$module} == 1; # user already has permission for everything in this module
1635 $userflags->{$module} = $user_subperms->{$module};
1638 return $userflags;
1641 =head2 get_user_subpermissions
1643 $user_perm_hashref = get_user_subpermissions($userid);
1645 Given the userid (note, not the borrowernumber) of a staff user,
1646 return a hashref of hashrefs of the specific subpermissions
1647 accorded to the user. An example return is
1650 tools => {
1651 export_catalog => 1,
1652 import_patrons => 1,
1656 The top-level hash-key is a module or function code from
1657 userflags.flag, while the second-level key is a code
1658 from permissions.
1660 The results of this function do not give a complete picture
1661 of the functions that a staff user can access; it is also
1662 necessary to check borrowers.flags.
1664 =cut
1666 sub get_user_subpermissions {
1667 my $userid = shift;
1669 my $dbh = C4::Context->dbh;
1670 my $sth = $dbh->prepare("SELECT flag, user_permissions.code
1671 FROM user_permissions
1672 JOIN permissions USING (module_bit, code)
1673 JOIN userflags ON (module_bit = bit)
1674 JOIN borrowers USING (borrowernumber)
1675 WHERE userid = ?");
1676 $sth->execute($userid);
1678 my $user_perms = {};
1679 while (my $perm = $sth->fetchrow_hashref) {
1680 $user_perms->{$perm->{'flag'}}->{$perm->{'code'}} = 1;
1682 return $user_perms;
1685 =head2 get_all_subpermissions
1687 my $perm_hashref = get_all_subpermissions();
1689 Returns a hashref of hashrefs defining all specific
1690 permissions currently defined. The return value
1691 has the same structure as that of C<get_user_subpermissions>,
1692 except that the innermost hash value is the description
1693 of the subpermission.
1695 =cut
1697 sub get_all_subpermissions {
1698 my $dbh = C4::Context->dbh;
1699 my $sth = $dbh->prepare("SELECT flag, code, description
1700 FROM permissions
1701 JOIN userflags ON (module_bit = bit)");
1702 $sth->execute();
1704 my $all_perms = {};
1705 while (my $perm = $sth->fetchrow_hashref) {
1706 $all_perms->{$perm->{'flag'}}->{$perm->{'code'}} = $perm->{'description'};
1708 return $all_perms;
1711 =head2 haspermission
1713 $flags = ($userid, $flagsrequired);
1715 C<$userid> the userid of the member
1716 C<$flags> is a hashref of required flags like C<$borrower-&lt;{authflags}>
1718 Returns member's flags or 0 if a permission is not met.
1720 =cut
1722 sub haspermission {
1723 my ($userid, $flagsrequired) = @_;
1724 my $sth = C4::Context->dbh->prepare("SELECT flags FROM borrowers WHERE userid=?");
1725 $sth->execute($userid);
1726 my $row = $sth->fetchrow();
1727 my $flags = getuserflags($row, $userid);
1728 if ( $userid eq C4::Context->config('user') ) {
1729 # Super User Account from /etc/koha.conf
1730 $flags->{'superlibrarian'} = 1;
1732 elsif ( $userid eq 'demo' && C4::Context->config('demo') ) {
1733 # Demo user that can do "anything" (demo=1 in /etc/koha.conf)
1734 $flags->{'superlibrarian'} = 1;
1737 return $flags if $flags->{superlibrarian};
1739 foreach my $module ( keys %$flagsrequired ) {
1740 my $subperm = $flagsrequired->{$module};
1741 if ($subperm eq '*') {
1742 return 0 unless ( $flags->{$module} == 1 or ref($flags->{$module}) );
1743 } else {
1744 return 0 unless ( $flags->{$module} == 1 or
1745 ( ref($flags->{$module}) and
1746 exists $flags->{$module}->{$subperm} and
1747 $flags->{$module}->{$subperm} == 1
1752 return $flags;
1753 #FIXME - This fcn should return the failed permission so a suitable error msg can be delivered.
1757 sub getborrowernumber {
1758 my ($userid) = @_;
1759 my $userenv = C4::Context->userenv;
1760 if ( defined( $userenv ) && ref( $userenv ) eq 'HASH' && $userenv->{number} ) {
1761 return $userenv->{number};
1763 my $dbh = C4::Context->dbh;
1764 for my $field ( 'userid', 'cardnumber' ) {
1765 my $sth =
1766 $dbh->prepare("select borrowernumber from borrowers where $field=?");
1767 $sth->execute($userid);
1768 if ( $sth->rows ) {
1769 my ($bnumber) = $sth->fetchrow;
1770 return $bnumber;
1773 return 0;
1776 sub ParseSearchHistoryCookie {
1777 my $input = shift;
1778 my $search_cookie = $input->cookie('KohaOpacRecentSearches');
1779 return () unless $search_cookie;
1780 my $obj = eval { decode_json(uri_unescape($search_cookie)) };
1781 return () unless defined $obj;
1782 return () unless ref $obj eq 'ARRAY';
1783 return @{ $obj };
1786 END { } # module clean-up code here (global destructor)
1788 __END__
1790 =head1 SEE ALSO
1792 CGI(3)
1794 C4::Output(3)
1796 Crypt::Eksblowfish::Bcrypt(3)
1798 Digest::MD5(3)
1800 =cut