3 # Copyright 2000-2002 Katipo Communications
5 # This file is part of Koha.
7 # Koha is free software; you can redistribute it and/or modify it under the
8 # terms of the GNU General Public License as published by the Free Software
9 # Foundation; either version 2 of the License, or (at your option) any later
12 # Koha is distributed in the hope that it will be useful, but WITHOUT ANY
13 # WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
14 # A PARTICULAR PURPOSE. See the GNU General Public License for more details.
16 # You should have received a copy of the GNU General Public License along
17 # with Koha; if not, write to the Free Software Foundation, Inc.,
18 # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
21 #use warnings; FIXME - Bug 2505
22 use Digest::MD5 qw(md5_base64);
23 use Storable qw(thaw freeze);
29 use C4::Templates; # to get the template
30 use C4::Branch; # GetBranches
31 use C4::VirtualShelves;
32 use POSIX qw/strftime/;
33 use List::MoreUtils qw/ any /;
36 use vars qw($VERSION @ISA @EXPORT @EXPORT_OK %EXPORT_TAGS $debug $ldap $cas $caslogout);
39 sub psgi_env { any { /^psgi\./ } keys %ENV }
41 if ( psgi_env ) { die 'psgi:exit' }
45 $VERSION = 3.02; # set version for version checking
48 @EXPORT = qw(&checkauth &get_template_and_user &haspermission &get_user_subpermissions);
49 @EXPORT_OK = qw(&check_api_auth &get_session &check_cookie_auth &checkpw &get_all_subpermissions &get_user_subpermissions);
50 %EXPORT_TAGS = ( EditPermissions => [qw(get_all_subpermissions get_user_subpermissions)] );
51 $ldap = C4::Context->config('useldapserver') || 0;
52 $cas = C4::Context->preference('casAuthentication');
53 $caslogout = C4::Context->preference('casLogout');
54 require C4::Auth_with_cas; # no import
56 require C4::Auth_with_ldap;
57 import C4::Auth_with_ldap qw(checkpw_ldap);
60 import C4::Auth_with_cas qw(check_api_auth_cas checkpw_cas login_cas logout_cas login_cas_url);
67 C4::Auth - Authenticates Koha users
77 my ($template, $borrowernumber, $cookie)
78 = get_template_and_user(
80 template_name => "opac-main.tmpl",
84 flagsrequired => {borrow => 1, catalogue => '*', tools => 'import_patrons' },
88 output_html_with_http_headers $query, $cookie, $template->output;
92 The main function of this module is to provide
93 authentification. However the get_template_and_user function has
94 been provided so that a users login information is passed along
95 automatically. This gets loaded into the template.
99 =head2 get_template_and_user
101 my ($template, $borrowernumber, $cookie)
102 = get_template_and_user(
104 template_name => "opac-main.tmpl",
107 authnotrequired => 1,
108 flagsrequired => {borrow => 1, catalogue => '*', tools => 'import_patrons' },
112 This call passes the C<query>, C<flagsrequired> and C<authnotrequired>
113 to C<&checkauth> (in this module) to perform authentification.
114 See C<&checkauth> for an explanation of these parameters.
116 The C<template_name> is then used to find the correct template for
117 the page. The authenticated users details are loaded onto the
118 template in the HTML::Template LOOP variable C<USER_INFO>. Also the
119 C<sessionID> is passed to the template. This can be used in templates
120 if cookies are disabled. It needs to be put as and input to every
123 More information on the C<gettemplate> sub can be found in the
128 my $SEARCH_HISTORY_INSERT_SQL =<<EOQ;
129 INSERT INTO search_history(userid, sessionid, query_desc, query_cgi, total, time )
130 VALUES ( ?, ?, ?, ?, ?, FROM_UNIXTIME(?))
132 sub get_template_and_user {
135 C4::Templates::gettemplate( $in->{'template_name'}, $in->{'type'}, $in->{'query'} );
136 my ( $user, $cookie, $sessionID, $flags );
137 if ( $in->{'template_name'} !~m/maintenance/ ) {
138 ( $user, $cookie, $sessionID, $flags ) = checkauth(
140 $in->{'authnotrequired'},
141 $in->{'flagsrequired'},
147 my $insecure = C4::Context->preference('insecure');
148 if ($user or $insecure) {
151 $template->param( loggedinusername => $user );
152 $template->param( sessionID => $sessionID );
154 my ($total, $pubshelves, $barshelves) = C4::Context->get_shelves_userenv();
155 if (defined($pubshelves)) {
156 $template->param( pubshelves => scalar @{$pubshelves},
157 pubshelvesloop => $pubshelves,
159 $template->param( pubtotal => $total->{'pubtotal'}, ) if ($total->{'pubtotal'} > scalar @{$pubshelves});
161 if (defined($barshelves)) {
162 $template->param( barshelves => scalar @{$barshelves},
163 barshelvesloop => $barshelves,
165 $template->param( bartotal => $total->{'bartotal'}, ) if ($total->{'bartotal'} > scalar @{$barshelves});
168 $borrowernumber = getborrowernumber($user) if defined($user);
170 my ( $borr ) = C4::Members::GetMemberDetails( $borrowernumber );
173 $template->param( "USER_INFO" => \@bordat );
175 my $all_perms = get_all_subpermissions();
177 my @flagroots = qw(circulate catalogue parameters borrowers permissions reserveforothers borrow
178 editcatalogue updatecharges management tools editauthorities serials reports acquisition);
179 # We are going to use the $flags returned by checkauth
180 # to create the template's parameters that will indicate
181 # which menus the user can access.
182 if (( $flags && $flags->{superlibrarian}==1) or $insecure==1) {
183 $template->param( CAN_user_circulate => 1 );
184 $template->param( CAN_user_catalogue => 1 );
185 $template->param( CAN_user_parameters => 1 );
186 $template->param( CAN_user_borrowers => 1 );
187 $template->param( CAN_user_permissions => 1 );
188 $template->param( CAN_user_reserveforothers => 1 );
189 $template->param( CAN_user_borrow => 1 );
190 $template->param( CAN_user_editcatalogue => 1 );
191 $template->param( CAN_user_updatecharges => 1 );
192 $template->param( CAN_user_acquisition => 1 );
193 $template->param( CAN_user_management => 1 );
194 $template->param( CAN_user_tools => 1 );
195 $template->param( CAN_user_editauthorities => 1 );
196 $template->param( CAN_user_serials => 1 );
197 $template->param( CAN_user_reports => 1 );
198 $template->param( CAN_user_staffaccess => 1 );
199 foreach my $module (keys %$all_perms) {
200 foreach my $subperm (keys %{ $all_perms->{$module} }) {
201 $template->param( "CAN_user_${module}_${subperm}" => 1 );
207 foreach my $module (keys %$all_perms) {
208 if ( $flags->{$module} == 1) {
209 foreach my $subperm (keys %{ $all_perms->{$module} }) {
210 $template->param( "CAN_user_${module}_${subperm}" => 1 );
212 } elsif ( ref($flags->{$module}) ) {
213 foreach my $subperm (keys %{ $flags->{$module} } ) {
214 $template->param( "CAN_user_${module}_${subperm}" => 1 );
221 foreach my $module (keys %$flags) {
222 if ( $flags->{$module} == 1 or ref($flags->{$module}) ) {
223 $template->param( "CAN_user_$module" => 1 );
224 if ($module eq "parameters") {
225 $template->param( CAN_user_management => 1 );
230 # Logged-in opac search history
231 # If the requested template is an opac one and opac search history is enabled
232 if ($in->{type} eq 'opac' && C4::Context->preference('EnableOpacSearchHistory')) {
233 my $dbh = C4::Context->dbh;
234 my $query = "SELECT COUNT(*) FROM search_history WHERE userid=?";
235 my $sth = $dbh->prepare($query);
236 $sth->execute($borrowernumber);
238 # If at least one search has already been performed
239 if ($sth->fetchrow_array > 0) {
240 # We show the link in opac
241 $template->param(ShowOpacRecentSearchLink => 1);
244 # And if there's a cookie with searches performed when the user was not logged in,
245 # we add them to the logged-in search history
246 my $searchcookie = $in->{'query'}->cookie('KohaOpacRecentSearches');
248 $searchcookie = uri_unescape($searchcookie);
249 my @recentSearches = @{thaw($searchcookie) || []};
250 if (@recentSearches) {
251 my $sth = $dbh->prepare($SEARCH_HISTORY_INSERT_SQL);
252 $sth->execute( $borrowernumber,
253 $in->{'query'}->cookie("CGISESSID"),
258 ) foreach @recentSearches;
260 # And then, delete the cookie's content
261 my $newsearchcookie = $in->{'query'}->cookie(
262 -name => 'KohaOpacRecentSearches',
263 -value => freeze([]),
266 $cookie = [$cookie, $newsearchcookie];
271 else { # if this is an anonymous session, setup to display public lists...
273 $template->param( sessionID => $sessionID );
275 my ($total, $pubshelves) = C4::Context->get_shelves_userenv(); # an anonymous user has no 'barshelves'...
276 if (defined $pubshelves) {
277 $template->param( pubshelves => scalar @{$pubshelves},
278 pubshelvesloop => $pubshelves,
280 $template->param( pubtotal => $total->{'pubtotal'}, ) if ($total->{'pubtotal'} > scalar @{$pubshelves});
284 # Anonymous opac search history
285 # If opac search history is enabled and at least one search has already been performed
286 if (C4::Context->preference('EnableOpacSearchHistory')) {
287 my $searchcookie = $in->{'query'}->cookie('KohaOpacRecentSearches');
289 $searchcookie = uri_unescape($searchcookie);
290 my @recentSearches = @{thaw($searchcookie) || []};
291 # We show the link in opac
292 if (@recentSearches) {
293 $template->param(ShowOpacRecentSearchLink => 1);
298 if(C4::Context->preference('dateformat')){
299 if(C4::Context->preference('dateformat') eq "metric"){
300 $template->param(dateformat_metric => 1);
301 } elsif(C4::Context->preference('dateformat') eq "us"){
302 $template->param(dateformat_us => 1);
304 $template->param(dateformat_iso => 1);
307 $template->param(dateformat_iso => 1);
310 # these template parameters are set the same regardless of $in->{'type'}
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"}:"insecure"),
318 LoginFirstname => (C4::Context->userenv?C4::Context->userenv->{"firstname"}:"Bel"),
319 LoginSurname => C4::Context->userenv?C4::Context->userenv->{"surname"}:"Inconnu",
320 TagsEnabled => C4::Context->preference("TagsEnabled"),
321 hide_marc => C4::Context->preference("hide_marc"),
322 item_level_itypes => C4::Context->preference('item-level_itypes'),
323 patronimages => C4::Context->preference("patronimages"),
324 singleBranchMode => C4::Context->preference("singleBranchMode"),
325 XSLTDetailsDisplay => C4::Context->preference("XSLTDetailsDisplay"),
326 XSLTResultsDisplay => C4::Context->preference("XSLTResultsDisplay"),
327 using_https => $in->{'query'}->https() ? 1 : 0,
328 noItemTypeImages => C4::Context->preference("noItemTypeImages"),
331 if ( $in->{'type'} eq "intranet" ) {
333 AmazonContent => C4::Context->preference("AmazonContent"),
334 AmazonCoverImages => C4::Context->preference("AmazonCoverImages"),
335 AmazonEnabled => C4::Context->preference("AmazonEnabled"),
336 AmazonSimilarItems => C4::Context->preference("AmazonSimilarItems"),
337 AutoLocation => C4::Context->preference("AutoLocation"),
338 "BiblioDefaultView".C4::Context->preference("IntranetBiblioDefaultView") => 1,
339 CircAutocompl => C4::Context->preference("CircAutocompl"),
340 FRBRizeEditions => C4::Context->preference("FRBRizeEditions"),
341 IndependantBranches => C4::Context->preference("IndependantBranches"),
342 IntranetNav => C4::Context->preference("IntranetNav"),
343 IntranetmainUserblock => C4::Context->preference("IntranetmainUserblock"),
344 LibraryName => C4::Context->preference("LibraryName"),
345 LoginBranchname => (C4::Context->userenv?C4::Context->userenv->{"branchname"}:"insecure"),
346 advancedMARCEditor => C4::Context->preference("advancedMARCEditor"),
347 canreservefromotherbranches => C4::Context->preference('canreservefromotherbranches'),
348 intranetcolorstylesheet => C4::Context->preference("intranetcolorstylesheet"),
349 IntranetFavicon => C4::Context->preference("IntranetFavicon"),
350 intranetreadinghistory => C4::Context->preference("intranetreadinghistory"),
351 intranetstylesheet => C4::Context->preference("intranetstylesheet"),
352 IntranetUserCSS => C4::Context->preference("IntranetUserCSS"),
353 intranetuserjs => C4::Context->preference("intranetuserjs"),
354 intranetbookbag => C4::Context->preference("intranetbookbag"),
355 suggestion => C4::Context->preference("suggestion"),
356 virtualshelves => C4::Context->preference("virtualshelves"),
357 StaffSerialIssueDisplayCount => C4::Context->preference("StaffSerialIssueDisplayCount"),
358 NoZebra => C4::Context->preference('NoZebra'),
359 EasyAnalyticalRecords => C4::Context->preference('EasyAnalyticalRecords'),
360 LocalCoverImages => C4::Context->preference('LocalCoverImages'),
361 OPACLocalCoverImages => C4::Context->preference('OPACLocalCoverImages'),
362 AllowMultipleCovers => C4::Context->preference('AllowMultipleCovers'),
366 warn "template type should be OPAC, here it is=[" . $in->{'type'} . "]" unless ( $in->{'type'} eq 'opac' );
367 #TODO : replace LibraryName syspref with 'system name', and remove this html processing
368 my $LibraryNameTitle = C4::Context->preference("LibraryName");
369 $LibraryNameTitle =~ s/<(?:\/?)(?:br|p)\s*(?:\/?)>/ /sgi;
370 $LibraryNameTitle =~ s/<(?:[^<>'"]|'(?:[^']*)'|"(?:[^"]*)")*>//sg;
371 # clean up the busc param in the session if the page is not opac-detail
372 if ($in->{'template_name'} =~ /opac-(.+)\.(?:tt|tmpl)$/ && $1 !~ /^(?:MARC|ISBD)?detail$/) {
373 my $sessionSearch = get_session($sessionID || $in->{'query'}->cookie("CGISESSID"));
374 $sessionSearch->clear(["busc"]) if ($sessionSearch->param("busc"));
376 # variables passed from CGI: opac_css_override and opac_search_limits.
377 my $opac_search_limit = $ENV{'OPAC_SEARCH_LIMIT'};
378 my $opac_limit_override = $ENV{'OPAC_LIMIT_OVERRIDE'};
380 if (($opac_search_limit =~ /branch:(\w+)/ && $opac_limit_override) || $in->{'query'}->param('limit') =~ /branch:(\w+)/){
381 $opac_name = $1; # opac_search_limit is a branch, so we use it.
382 } elsif (C4::Context->preference("SearchMyLibraryFirst") && C4::Context->userenv && C4::Context->userenv->{'branch'}) {
383 $opac_name = C4::Context->userenv->{'branch'};
385 my $checkstyle = C4::Context->preference("opaccolorstylesheet");
386 if ($checkstyle =~ /http/)
388 $template->param( opacexternalsheet => $checkstyle);
391 my $opaccolorstylesheet = C4::Context->preference("opaccolorstylesheet");
392 $template->param( opaccolorstylesheet => $opaccolorstylesheet);
395 AmazonContent => "" . C4::Context->preference("AmazonContent"),
396 AnonSuggestions => "" . C4::Context->preference("AnonSuggestions"),
397 AuthorisedValueImages => C4::Context->preference("AuthorisedValueImages"),
398 BranchesLoop => GetBranchesLoop($opac_name),
399 LibraryName => "" . C4::Context->preference("LibraryName"),
400 LibraryNameTitle => "" . $LibraryNameTitle,
401 LoginBranchname => C4::Context->userenv?C4::Context->userenv->{"branchname"}:"",
402 OPACAmazonEnabled => C4::Context->preference("OPACAmazonEnabled"),
403 OPACAmazonSimilarItems => C4::Context->preference("OPACAmazonSimilarItems"),
404 OPACAmazonCoverImages => C4::Context->preference("OPACAmazonCoverImages"),
405 OPACAmazonReviews => C4::Context->preference("OPACAmazonReviews"),
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 OpacShowRecentComments => C4::Context->preference("OpacShowRecentComments"),
411 OPACURLOpenInNewWindow => "" . C4::Context->preference("OPACURLOpenInNewWindow"),
412 OPACUserCSS => "". C4::Context->preference("OPACUserCSS"),
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 OpacNav => "" . C4::Context->preference("OpacNav"),
425 OpacNavBottom => "" . C4::Context->preference("OpacNavBottom"),
426 OpacPasswordChange => C4::Context->preference("OpacPasswordChange"),
427 OPACPatronDetails => C4::Context->preference("OPACPatronDetails"),
428 OPACPrivacy => C4::Context->preference("OPACPrivacy"),
429 OPACFinesTab => C4::Context->preference("OPACFinesTab"),
430 OpacTopissue => C4::Context->preference("OpacTopissue"),
431 RequestOnOpac => C4::Context->preference("RequestOnOpac"),
432 'Version' => C4::Context->preference('Version'),
433 hidelostitems => C4::Context->preference("hidelostitems"),
434 mylibraryfirst => (C4::Context->preference("SearchMyLibraryFirst") && C4::Context->userenv) ? C4::Context->userenv->{'branch'} : '',
435 opaclayoutstylesheet => "" . C4::Context->preference("opaclayoutstylesheet"),
436 opacstylesheet => "" . C4::Context->preference("opacstylesheet"),
437 opacbookbag => "" . C4::Context->preference("opacbookbag"),
438 opaccredits => "" . C4::Context->preference("opaccredits"),
439 OpacFavicon => C4::Context->preference("OpacFavicon"),
440 opacheader => "" . C4::Context->preference("opacheader"),
441 opaclanguagesdisplay => "" . C4::Context->preference("opaclanguagesdisplay"),
442 opacreadinghistory => C4::Context->preference("opacreadinghistory"),
443 opacsmallimage => "" . C4::Context->preference("opacsmallimage"),
444 opacuserjs => C4::Context->preference("opacuserjs"),
445 opacuserlogin => "" . C4::Context->preference("opacuserlogin"),
446 reviewson => C4::Context->preference("reviewson"),
447 ShowReviewer => C4::Context->preference("ShowReviewer"),
448 ShowReviewerPhoto => C4::Context->preference("ShowReviewerPhoto"),
449 suggestion => "" . C4::Context->preference("suggestion"),
450 virtualshelves => "" . C4::Context->preference("virtualshelves"),
451 OPACSerialIssueDisplayCount => C4::Context->preference("OPACSerialIssueDisplayCount"),
452 OpacAddMastheadLibraryPulldown => C4::Context->preference("OpacAddMastheadLibraryPulldown"),
453 OPACXSLTDetailsDisplay => C4::Context->preference("OPACXSLTDetailsDisplay"),
454 OPACXSLTResultsDisplay => C4::Context->preference("OPACXSLTResultsDisplay"),
455 SyndeticsClientCode => C4::Context->preference("SyndeticsClientCode"),
456 SyndeticsEnabled => C4::Context->preference("SyndeticsEnabled"),
457 SyndeticsCoverImages => C4::Context->preference("SyndeticsCoverImages"),
458 SyndeticsTOC => C4::Context->preference("SyndeticsTOC"),
459 SyndeticsSummary => C4::Context->preference("SyndeticsSummary"),
460 SyndeticsEditions => C4::Context->preference("SyndeticsEditions"),
461 SyndeticsExcerpt => C4::Context->preference("SyndeticsExcerpt"),
462 SyndeticsReviews => C4::Context->preference("SyndeticsReviews"),
463 SyndeticsAuthorNotes => C4::Context->preference("SyndeticsAuthorNotes"),
464 SyndeticsAwards => C4::Context->preference("SyndeticsAwards"),
465 SyndeticsSeries => C4::Context->preference("SyndeticsSeries"),
466 SyndeticsCoverImageSize => C4::Context->preference("SyndeticsCoverImageSize"),
467 OPACLocalCoverImages => C4::Context->preference("OPACLocalCoverImages"),
470 $template->param(OpacPublic => '1') if ($user || C4::Context->preference("OpacPublic"));
472 return ( $template, $borrowernumber, $cookie, $flags);
477 ($userid, $cookie, $sessionID) = &checkauth($query, $noauth, $flagsrequired, $type);
479 Verifies that the user is authorized to run this script. If
480 the user is authorized, a (userid, cookie, session-id, flags)
481 quadruple is returned. If the user is not authorized but does
482 not have the required privilege (see $flagsrequired below), it
483 displays an error page and exits. Otherwise, it displays the
484 login page and exits.
486 Note that C<&checkauth> will return if and only if the user
487 is authorized, so it should be called early on, before any
488 unfinished operations (e.g., if you've opened a file, then
489 C<&checkauth> won't close it for you).
491 C<$query> is the CGI object for the script calling C<&checkauth>.
493 The C<$noauth> argument is optional. If it is set, then no
494 authorization is required for the script.
496 C<&checkauth> fetches user and session information from C<$query> and
497 ensures that the user is authorized to run scripts that require
500 The C<$flagsrequired> argument specifies the required privileges
501 the user must have if the username and password are correct.
502 It should be specified as a reference-to-hash; keys in the hash
503 should be the "flags" for the user, as specified in the Members
504 intranet module. Any key specified must correspond to a "flag"
505 in the userflags table. E.g., { circulate => 1 } would specify
506 that the user must have the "circulate" privilege in order to
507 proceed. To make sure that access control is correct, the
508 C<$flagsrequired> parameter must be specified correctly.
510 Koha also has a concept of sub-permissions, also known as
511 granular permissions. This makes the value of each key
512 in the C<flagsrequired> hash take on an additional
517 The user must have access to all subfunctions of the module
518 specified by the hash key.
522 The user must have access to at least one subfunction of the module
523 specified by the hash key.
525 specific permission, e.g., 'export_catalog'
527 The user must have access to the specific subfunction list, which
528 must correspond to a row in the permissions table.
530 The C<$type> argument specifies whether the template should be
531 retrieved from the opac or intranet directory tree. "opac" is
532 assumed if it is not specified; however, if C<$type> is specified,
533 "intranet" is assumed if it is not "opac".
535 If C<$query> does not have a valid session ID associated with it
536 (i.e., the user has not logged in) or if the session has expired,
537 C<&checkauth> presents the user with a login page (from the point of
538 view of the original script, C<&checkauth> does not return). Once the
539 user has authenticated, C<&checkauth> restarts the original script
540 (this time, C<&checkauth> returns).
542 The login page is provided using a HTML::Template, which is set in the
543 systempreferences table or at the top of this file. The variable C<$type>
544 selects which template to use, either the opac or the intranet
545 authentification template.
547 C<&checkauth> returns a user ID, a cookie, and a session ID. The
548 cookie should be sent back to the browser; it verifies that the user
553 sub _version_check ($$) {
557 # If Version syspref is unavailable, it means Koha is beeing installed,
558 # and so we must redirect to OPAC maintenance page or to the WebInstaller
559 # also, if OpacMaintenance is ON, OPAC should redirect to maintenance
560 if (C4::Context->preference('OpacMaintenance') && $type eq 'opac') {
561 warn "OPAC Install required, redirecting to maintenance";
562 print $query->redirect("/cgi-bin/koha/maintenance.pl");
564 unless ( $version = C4::Context->preference('Version') ) { # assignment, not comparison
565 if ( $type ne 'opac' ) {
566 warn "Install required, redirecting to Installer";
567 print $query->redirect("/cgi-bin/koha/installer/install.pl");
569 warn "OPAC Install required, redirecting to maintenance";
570 print $query->redirect("/cgi-bin/koha/maintenance.pl");
575 # check that database and koha version are the same
576 # there is no DB version, it's a fresh install,
577 # go to web installer
578 # there is a DB version, compare it to the code version
579 my $kohaversion=C4::Context::KOHAVERSION;
580 # remove the 3 last . to have a Perl number
581 $kohaversion =~ s/(.*\..*)\.(.*)\.(.*)/$1$2$3/;
582 $debug and print STDERR "kohaversion : $kohaversion\n";
583 if ($version < $kohaversion){
584 my $warning = "Database update needed, redirecting to %s. Database is $version and Koha is $kohaversion";
585 if ($type ne 'opac'){
586 warn sprintf($warning, 'Installer');
587 print $query->redirect("/cgi-bin/koha/installer/install.pl?step=3");
589 warn sprintf("OPAC: " . $warning, 'maintenance');
590 print $query->redirect("/cgi-bin/koha/maintenance.pl");
598 open L, ">>/tmp/sessionlog" or warn "ERROR: Cannot append to /tmp/sessionlog";
599 printf L join("\n",@_);
605 $debug and warn "Checking Auth";
606 # $authnotrequired will be set for scripts which will run without authentication
607 my $authnotrequired = shift;
608 my $flagsrequired = shift;
610 $type = 'opac' unless $type;
612 my $dbh = C4::Context->dbh;
613 my $timeout = C4::Context->preference('timeout');
615 if ($timeout =~ /(\d+)[dD]/) {
616 $timeout = $1 * 86400;
618 $timeout = 600 unless $timeout;
620 _version_check($type,$query);
624 my ( $userid, $cookie, $sessionID, $flags, $barshelves, $pubshelves );
625 my $logout = $query->param('logout.x');
627 # This parameter is the name of the CAS server we want to authenticate against,
628 # when using authentication against multiple CAS servers, as configured in Auth_cas_servers.yaml
629 my $casparam = $query->param('cas');
631 if ( $userid = $ENV{'REMOTE_USER'} ) {
632 # Using Basic Authentication, no cookies required
633 $cookie = $query->cookie(
634 -name => 'CGISESSID',
640 elsif ( $sessionID = $query->cookie("CGISESSID")) { # assignment, not comparison
641 my $session = get_session($sessionID);
642 C4::Context->_new_userenv($sessionID);
643 my ($ip, $lasttime, $sessiontype);
645 C4::Context::set_userenv(
646 $session->param('number'), $session->param('id'),
647 $session->param('cardnumber'), $session->param('firstname'),
648 $session->param('surname'), $session->param('branch'),
649 $session->param('branchname'), $session->param('flags'),
650 $session->param('emailaddress'), $session->param('branchprinter')
652 C4::Context::set_shelves_userenv('bar',$session->param('barshelves'));
653 C4::Context::set_shelves_userenv('pub',$session->param('pubshelves'));
654 C4::Context::set_shelves_userenv('tot',$session->param('totshelves'));
655 $debug and printf STDERR "AUTH_SESSION: (%s)\t%s %s - %s\n", map {$session->param($_)} qw(cardnumber firstname surname branch) ;
656 $ip = $session->param('ip');
657 $lasttime = $session->param('lasttime');
658 $userid = $session->param('id');
659 $sessiontype = $session->param('sessiontype');
661 if ( ( ($query->param('koha_login_context')) && ($query->param('userid') ne $session->param('id')) )
662 || ( $cas && $query->param('ticket') ) ) {
663 #if a user enters an id ne to the id in the current session, we need to log them in...
664 #first we need to clear the anonymous session...
665 $debug and warn "query id = " . $query->param('userid') . " but session id = " . $session->param('id');
668 C4::Context->_unset_userenv($sessionID);
673 # voluntary logout the user
676 C4::Context->_unset_userenv($sessionID);
677 #_session_log(sprintf "%20s from %16s logged out at %30s (manually).\n", $userid,$ip,(strftime "%c",localtime));
681 if ($cas and $caslogout) {
685 elsif ( $lasttime < time() - $timeout ) {
687 $info{'timed_out'} = 1;
689 C4::Context->_unset_userenv($sessionID);
690 #_session_log(sprintf "%20s from %16s logged out at %30s (inactivity).\n", $userid,$ip,(strftime "%c",localtime));
694 elsif ( $ip ne $ENV{'REMOTE_ADDR'} ) {
695 # Different ip than originally logged in from
696 $info{'oldip'} = $ip;
697 $info{'newip'} = $ENV{'REMOTE_ADDR'};
698 $info{'different_ip'} = 1;
700 C4::Context->_unset_userenv($sessionID);
701 #_session_log(sprintf "%20s from %16s logged out at %30s (ip changed to %16s).\n", $userid,$ip,(strftime "%c",localtime), $info{'newip'});
706 $cookie = $query->cookie( CGISESSID => $session->id );
707 $session->param('lasttime',time());
708 unless ( $sessiontype eq 'anon' ) { #if this is an anonymous session, we want to update the session, but not behave as if they are logged in...
709 $flags = haspermission($userid, $flagsrequired);
713 $info{'nopermission'} = 1;
718 unless ($userid || $sessionID) {
719 #we initiate a session prior to checking for a username to allow for anonymous sessions...
720 my $session = get_session("") or die "Auth ERROR: Cannot get_session()";
721 my $sessionID = $session->id;
722 C4::Context->_new_userenv($sessionID);
723 $cookie = $query->cookie(CGISESSID => $sessionID);
724 $userid = $query->param('userid');
725 if (($cas && $query->param('ticket')) || $userid) {
726 my $password = $query->param('password');
727 my ($return, $cardnumber);
728 if ($cas && $query->param('ticket')) {
730 ( $return, $cardnumber, $retuserid ) = checkpw( $dbh, $userid, $password, $query );
731 $userid = $retuserid;
732 $info{'invalidCasLogin'} = 1 unless ($return);
735 ( $return, $cardnumber, $retuserid ) = checkpw( $dbh, $userid, $password, $query );
736 $userid = $retuserid if ($retuserid ne '');
739 #_session_log(sprintf "%20s from %16s logged in at %30s.\n", $userid,$ENV{'REMOTE_ADDR'},(strftime '%c', localtime));
740 if ( $flags = haspermission( $userid, $flagsrequired ) ) {
744 $info{'nopermission'} = 1;
745 C4::Context->_unset_userenv($sessionID);
748 my ($borrowernumber, $firstname, $surname, $userflags,
749 $branchcode, $branchname, $branchprinter, $emailaddress);
751 if ( $return == 1 ) {
753 SELECT borrowernumber, firstname, surname, flags, borrowers.branchcode,
754 branches.branchname as branchname,
755 branches.branchprinter as branchprinter,
758 LEFT JOIN branches on borrowers.branchcode=branches.branchcode
760 my $sth = $dbh->prepare("$select where userid=?");
761 $sth->execute($userid);
762 unless ($sth->rows) {
763 $debug and print STDERR "AUTH_1: no rows for userid='$userid'\n";
764 $sth = $dbh->prepare("$select where cardnumber=?");
765 $sth->execute($cardnumber);
767 unless ($sth->rows) {
768 $debug and print STDERR "AUTH_2a: no rows for cardnumber='$cardnumber'\n";
769 $sth->execute($userid);
770 unless ($sth->rows) {
771 $debug and print STDERR "AUTH_2b: no rows for userid='$userid' AS cardnumber\n";
776 ($borrowernumber, $firstname, $surname, $userflags,
777 $branchcode, $branchname, $branchprinter, $emailaddress) = $sth->fetchrow;
778 $debug and print STDERR "AUTH_3 results: " .
779 "$cardnumber,$borrowernumber,$userid,$firstname,$surname,$userflags,$branchcode,$emailaddress\n";
781 print STDERR "AUTH_3: no results for userid='$userid', cardnumber='$cardnumber'.\n";
784 # launch a sequence to check if we have a ip for the branch, i
785 # if we have one we replace the branchcode of the userenv by the branch bound in the ip.
787 my $ip = $ENV{'REMOTE_ADDR'};
788 # if they specify at login, use that
789 if ($query->param('branch')) {
790 $branchcode = $query->param('branch');
791 $branchname = GetBranchName($branchcode);
793 my $branches = GetBranches();
794 if (C4::Context->boolean_preference('IndependantBranches') && C4::Context->boolean_preference('Autolocation')){
795 # we have to check they are coming from the right ip range
796 my $domain = $branches->{$branchcode}->{'branchip'};
797 if ($ip !~ /^$domain/){
799 $info{'wrongip'} = 1;
804 foreach my $br ( keys %$branches ) {
805 # now we work with the treatment of ip
806 my $domain = $branches->{$br}->{'branchip'};
807 if ( $domain && $ip =~ /^$domain/ ) {
808 $branchcode = $branches->{$br}->{'branchcode'};
810 # new op dev : add the branchprinter and branchname in the cookie
811 $branchprinter = $branches->{$br}->{'branchprinter'};
812 $branchname = $branches->{$br}->{'branchname'};
815 $session->param('number',$borrowernumber);
816 $session->param('id',$userid);
817 $session->param('cardnumber',$cardnumber);
818 $session->param('firstname',$firstname);
819 $session->param('surname',$surname);
820 $session->param('branch',$branchcode);
821 $session->param('branchname',$branchname);
822 $session->param('flags',$userflags);
823 $session->param('emailaddress',$emailaddress);
824 $session->param('ip',$session->remote_addr());
825 $session->param('lasttime',time());
826 $debug and printf STDERR "AUTH_4: (%s)\t%s %s - %s\n", map {$session->param($_)} qw(cardnumber firstname surname branch) ;
828 elsif ( $return == 2 ) {
829 #We suppose the user is the superlibrarian
831 $session->param('number',0);
832 $session->param('id',C4::Context->config('user'));
833 $session->param('cardnumber',C4::Context->config('user'));
834 $session->param('firstname',C4::Context->config('user'));
835 $session->param('surname',C4::Context->config('user'));
836 $session->param('branch','NO_LIBRARY_SET');
837 $session->param('branchname','NO_LIBRARY_SET');
838 $session->param('flags',1);
839 $session->param('emailaddress', C4::Context->preference('KohaAdminEmailAddress'));
840 $session->param('ip',$session->remote_addr());
841 $session->param('lasttime',time());
843 C4::Context::set_userenv(
844 $session->param('number'), $session->param('id'),
845 $session->param('cardnumber'), $session->param('firstname'),
846 $session->param('surname'), $session->param('branch'),
847 $session->param('branchname'), $session->param('flags'),
848 $session->param('emailaddress'), $session->param('branchprinter')
851 # Grab borrower's shelves and public shelves and add them to the session
852 # $row_count determines how many records are returned from the db query
853 # and the number of lists to be displayed of each type in the 'Lists' button drop down
854 my $row_count = 10; # FIXME:This probably should be a syspref
855 my ($total, $totshelves, $barshelves, $pubshelves);
856 ($barshelves, $totshelves) = C4::VirtualShelves::GetRecentShelves(1, $row_count, $borrowernumber);
857 $total->{'bartotal'} = $totshelves;
858 ($pubshelves, $totshelves) = C4::VirtualShelves::GetRecentShelves(2, $row_count, undef);
859 $total->{'pubtotal'} = $totshelves;
860 $session->param('barshelves', $barshelves);
861 $session->param('pubshelves', $pubshelves);
862 $session->param('totshelves', $total);
864 C4::Context::set_shelves_userenv('bar',$barshelves);
865 C4::Context::set_shelves_userenv('pub',$pubshelves);
866 C4::Context::set_shelves_userenv('tot',$total);
870 $info{'invalid_username_or_password'} = 1;
871 C4::Context->_unset_userenv($sessionID);
874 } # END if ( $userid = $query->param('userid') )
875 elsif ($type eq "opac") {
876 # if we are here this is an anonymous session; add public lists to it and a few other items...
877 # anonymous sessions are created only for the OPAC
878 $debug and warn "Initiating an anonymous session...";
880 # Grab the public shelves and add to the session...
881 my $row_count = 20; # FIXME:This probably should be a syspref
882 my ($total, $totshelves, $pubshelves);
883 ($pubshelves, $totshelves) = C4::VirtualShelves::GetRecentShelves(2, $row_count, undef);
884 $total->{'pubtotal'} = $totshelves;
885 $session->param('pubshelves', $pubshelves);
886 $session->param('totshelves', $total);
887 C4::Context::set_shelves_userenv('pub',$pubshelves);
888 C4::Context::set_shelves_userenv('tot',$total);
890 # setting a couple of other session vars...
891 $session->param('ip',$session->remote_addr());
892 $session->param('lasttime',time());
893 $session->param('sessiontype','anon');
895 } # END unless ($userid)
896 my $insecure = C4::Context->boolean_preference('insecure');
898 # finished authentification, now respond
899 if ( $loggedin || $authnotrequired || ( defined($insecure) && $insecure ) )
903 $cookie = $query->cookie( CGISESSID => '' );
905 return ( $userid, $cookie, $sessionID, $flags );
910 # AUTH rejected, show the login/password template, after checking the DB.
914 # get the inputs from the incoming query
916 foreach my $name ( param $query) {
917 (next) if ( $name eq 'userid' || $name eq 'password' || $name eq 'ticket' );
918 my $value = $query->param($name);
919 push @inputs, { name => $name, value => $value };
921 # get the branchloop, which we need for authentication
922 my $branches = GetBranches();
924 for my $branch_hash (sort keys %$branches) {
925 push @branch_loop, {branchcode => "$branch_hash", branchname => $branches->{$branch_hash}->{'branchname'}, };
928 my $template_name = ( $type eq 'opac' ) ? 'opac-auth.tmpl' : 'auth.tmpl';
929 my $template = C4::Templates::gettemplate( $template_name, $type, $query );
930 $template->param(branchloop => \@branch_loop,);
931 my $checkstyle = C4::Context->preference("opaccolorstylesheet");
932 if ($checkstyle =~ /\//)
934 $template->param( opacexternalsheet => $checkstyle);
937 my $opaccolorstylesheet = C4::Context->preference("opaccolorstylesheet");
938 $template->param( opaccolorstylesheet => $opaccolorstylesheet);
943 casAuthentication => C4::Context->preference("casAuthentication"),
944 suggestion => C4::Context->preference("suggestion"),
945 virtualshelves => C4::Context->preference("virtualshelves"),
946 LibraryName => C4::Context->preference("LibraryName"),
947 opacuserlogin => C4::Context->preference("opacuserlogin"),
948 OpacNav => C4::Context->preference("OpacNav"),
949 OpacNavBottom => C4::Context->preference("OpacNavBottom"),
950 opaccredits => C4::Context->preference("opaccredits"),
951 OpacFavicon => C4::Context->preference("OpacFavicon"),
952 opacreadinghistory => C4::Context->preference("opacreadinghistory"),
953 opacsmallimage => C4::Context->preference("opacsmallimage"),
954 opaclayoutstylesheet => C4::Context->preference("opaclayoutstylesheet"),
955 opaclanguagesdisplay => C4::Context->preference("opaclanguagesdisplay"),
956 opacuserjs => C4::Context->preference("opacuserjs"),
957 opacbookbag => "" . C4::Context->preference("opacbookbag"),
958 OpacCloud => C4::Context->preference("OpacCloud"),
959 OpacTopissue => C4::Context->preference("OpacTopissue"),
960 OpacAuthorities => C4::Context->preference("OpacAuthorities"),
961 OpacBrowser => C4::Context->preference("OpacBrowser"),
962 opacheader => C4::Context->preference("opacheader"),
963 TagsEnabled => C4::Context->preference("TagsEnabled"),
964 OPACUserCSS => C4::Context->preference("OPACUserCSS"),
965 opacstylesheet => C4::Context->preference("opacstylesheet"),
966 intranetcolorstylesheet =>
967 C4::Context->preference("intranetcolorstylesheet"),
968 intranetstylesheet => C4::Context->preference("intranetstylesheet"),
969 intranetbookbag => C4::Context->preference("intranetbookbag"),
970 IntranetNav => C4::Context->preference("IntranetNav"),
971 intranetuserjs => C4::Context->preference("intranetuserjs"),
972 IndependantBranches=> C4::Context->preference("IndependantBranches"),
973 AutoLocation => C4::Context->preference("AutoLocation"),
974 wrongip => $info{'wrongip'},
977 $template->param( OpacPublic => C4::Context->preference("OpacPublic"));
978 $template->param( loginprompt => 1 ) unless $info{'nopermission'};
982 # Is authentication against multiple CAS servers enabled?
983 if (C4::Auth_with_cas::multipleAuth && !$casparam) {
984 my $casservers = C4::Auth_with_cas::getMultipleAuth();
986 foreach my $key (keys %$casservers) {
987 push @tmplservers, {name => $key, value => login_cas_url($query, $key) . "?cas=$key" };
989 #warn Data::Dumper::Dumper(\@tmplservers);
991 casServersLoop => \@tmplservers
995 casServerUrl => login_cas_url($query),
1000 invalidCasLogin => $info{'invalidCasLogin'}
1004 my $self_url = $query->url( -absolute => 1 );
1007 LibraryName => C4::Context->preference("LibraryName"),
1009 $template->param( %info );
1010 # $cookie = $query->cookie(CGISESSID => $session->id
1012 print $query->header(
1013 -type => 'text/html',
1014 -charset => 'utf-8',
1021 =head2 check_api_auth
1023 ($status, $cookie, $sessionId) = check_api_auth($query, $userflags);
1025 Given a CGI query containing the parameters 'userid' and 'password' and/or a session
1026 cookie, determine if the user has the privileges specified by C<$userflags>.
1028 C<check_api_auth> is is meant for authenticating users of web services, and
1029 consequently will always return and will not attempt to redirect the user
1032 If a valid session cookie is already present, check_api_auth will return a status
1033 of "ok", the cookie, and the Koha session ID.
1035 If no session cookie is present, check_api_auth will check the 'userid' and 'password
1036 parameters and create a session cookie and Koha session if the supplied credentials
1039 Possible return values in C<$status> are:
1043 =item "ok" -- user authenticated; C<$cookie> and C<$sessionid> have valid values.
1045 =item "failed" -- credentials are not correct; C<$cookie> and C<$sessionid> are undef
1047 =item "maintenance" -- DB is in maintenance mode; no login possible at the moment
1049 =item "expired -- session cookie has expired; API user should resubmit userid and password
1055 sub check_api_auth {
1057 my $flagsrequired = shift;
1059 my $dbh = C4::Context->dbh;
1060 my $timeout = C4::Context->preference('timeout');
1061 $timeout = 600 unless $timeout;
1063 unless (C4::Context->preference('Version')) {
1064 # database has not been installed yet
1065 return ("maintenance", undef, undef);
1067 my $kohaversion=C4::Context::KOHAVERSION;
1068 $kohaversion =~ s/(.*\..*)\.(.*)\.(.*)/$1$2$3/;
1069 if (C4::Context->preference('Version') < $kohaversion) {
1070 # database in need of version update; assume that
1071 # no API should be called while databsae is in
1073 return ("maintenance", undef, undef);
1076 # FIXME -- most of what follows is a copy-and-paste
1077 # of code from checkauth. There is an obvious need
1078 # for refactoring to separate the various parts of
1079 # the authentication code, but as of 2007-11-19 this
1080 # is deferred so as to not introduce bugs into the
1081 # regular authentication code for Koha 3.0.
1083 # see if we have a valid session cookie already
1084 # however, if a userid parameter is present (i.e., from
1085 # a form submission, assume that any current cookie
1087 my $sessionID = undef;
1088 unless ($query->param('userid')) {
1089 $sessionID = $query->cookie("CGISESSID");
1091 if ($sessionID && not ($cas && $query->param('PT')) ) {
1092 my $session = get_session($sessionID);
1093 C4::Context->_new_userenv($sessionID);
1095 C4::Context::set_userenv(
1096 $session->param('number'), $session->param('id'),
1097 $session->param('cardnumber'), $session->param('firstname'),
1098 $session->param('surname'), $session->param('branch'),
1099 $session->param('branchname'), $session->param('flags'),
1100 $session->param('emailaddress'), $session->param('branchprinter')
1103 my $ip = $session->param('ip');
1104 my $lasttime = $session->param('lasttime');
1105 my $userid = $session->param('id');
1106 if ( $lasttime < time() - $timeout ) {
1109 C4::Context->_unset_userenv($sessionID);
1112 return ("expired", undef, undef);
1113 } elsif ( $ip ne $ENV{'REMOTE_ADDR'} ) {
1114 # IP address changed
1116 C4::Context->_unset_userenv($sessionID);
1119 return ("expired", undef, undef);
1121 my $cookie = $query->cookie( CGISESSID => $session->id );
1122 $session->param('lasttime',time());
1123 my $flags = haspermission($userid, $flagsrequired);
1125 return ("ok", $cookie, $sessionID);
1128 C4::Context->_unset_userenv($sessionID);
1131 return ("failed", undef, undef);
1135 return ("expired", undef, undef);
1139 my $userid = $query->param('userid');
1140 my $password = $query->param('password');
1141 my ($return, $cardnumber);
1144 if ($cas && $query->param('PT')) {
1146 $debug and print STDERR "## check_api_auth - checking CAS\n";
1147 # In case of a CAS authentication, we use the ticket instead of the password
1148 my $PT = $query->param('PT');
1149 ($return,$cardnumber,$userid) = check_api_auth_cas($dbh, $PT, $query); # EXTERNAL AUTH
1151 # User / password auth
1152 unless ($userid and $password) {
1153 # caller did something wrong, fail the authenticateion
1154 return ("failed", undef, undef);
1156 ( $return, $cardnumber ) = checkpw( $dbh, $userid, $password, $query );
1159 if ($return and haspermission( $userid, $flagsrequired)) {
1160 my $session = get_session("");
1161 return ("failed", undef, undef) unless $session;
1163 my $sessionID = $session->id;
1164 C4::Context->_new_userenv($sessionID);
1165 my $cookie = $query->cookie(CGISESSID => $sessionID);
1166 if ( $return == 1 ) {
1168 $borrowernumber, $firstname, $surname,
1169 $userflags, $branchcode, $branchname,
1170 $branchprinter, $emailaddress
1174 "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=?"
1176 $sth->execute($userid);
1178 $borrowernumber, $firstname, $surname,
1179 $userflags, $branchcode, $branchname,
1180 $branchprinter, $emailaddress
1181 ) = $sth->fetchrow if ( $sth->rows );
1183 unless ($sth->rows ) {
1184 my $sth = $dbh->prepare(
1185 "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=?"
1187 $sth->execute($cardnumber);
1189 $borrowernumber, $firstname, $surname,
1190 $userflags, $branchcode, $branchname,
1191 $branchprinter, $emailaddress
1192 ) = $sth->fetchrow if ( $sth->rows );
1194 unless ( $sth->rows ) {
1195 $sth->execute($userid);
1197 $borrowernumber, $firstname, $surname, $userflags,
1198 $branchcode, $branchname, $branchprinter, $emailaddress
1199 ) = $sth->fetchrow if ( $sth->rows );
1203 my $ip = $ENV{'REMOTE_ADDR'};
1204 # if they specify at login, use that
1205 if ($query->param('branch')) {
1206 $branchcode = $query->param('branch');
1207 $branchname = GetBranchName($branchcode);
1209 my $branches = GetBranches();
1211 foreach my $br ( keys %$branches ) {
1212 # now we work with the treatment of ip
1213 my $domain = $branches->{$br}->{'branchip'};
1214 if ( $domain && $ip =~ /^$domain/ ) {
1215 $branchcode = $branches->{$br}->{'branchcode'};
1217 # new op dev : add the branchprinter and branchname in the cookie
1218 $branchprinter = $branches->{$br}->{'branchprinter'};
1219 $branchname = $branches->{$br}->{'branchname'};
1222 $session->param('number',$borrowernumber);
1223 $session->param('id',$userid);
1224 $session->param('cardnumber',$cardnumber);
1225 $session->param('firstname',$firstname);
1226 $session->param('surname',$surname);
1227 $session->param('branch',$branchcode);
1228 $session->param('branchname',$branchname);
1229 $session->param('flags',$userflags);
1230 $session->param('emailaddress',$emailaddress);
1231 $session->param('ip',$session->remote_addr());
1232 $session->param('lasttime',time());
1233 } elsif ( $return == 2 ) {
1234 #We suppose the user is the superlibrarian
1235 $session->param('number',0);
1236 $session->param('id',C4::Context->config('user'));
1237 $session->param('cardnumber',C4::Context->config('user'));
1238 $session->param('firstname',C4::Context->config('user'));
1239 $session->param('surname',C4::Context->config('user'));
1240 $session->param('branch','NO_LIBRARY_SET');
1241 $session->param('branchname','NO_LIBRARY_SET');
1242 $session->param('flags',1);
1243 $session->param('emailaddress', C4::Context->preference('KohaAdminEmailAddress'));
1244 $session->param('ip',$session->remote_addr());
1245 $session->param('lasttime',time());
1247 C4::Context::set_userenv(
1248 $session->param('number'), $session->param('id'),
1249 $session->param('cardnumber'), $session->param('firstname'),
1250 $session->param('surname'), $session->param('branch'),
1251 $session->param('branchname'), $session->param('flags'),
1252 $session->param('emailaddress'), $session->param('branchprinter')
1254 return ("ok", $cookie, $sessionID);
1256 return ("failed", undef, undef);
1261 =head2 check_cookie_auth
1263 ($status, $sessionId) = check_api_auth($cookie, $userflags);
1265 Given a CGISESSID cookie set during a previous login to Koha, determine
1266 if the user has the privileges specified by C<$userflags>.
1268 C<check_cookie_auth> is meant for authenticating special services
1269 such as tools/upload-file.pl that are invoked by other pages that
1270 have been authenticated in the usual way.
1272 Possible return values in C<$status> are:
1276 =item "ok" -- user authenticated; C<$sessionID> have valid values.
1278 =item "failed" -- credentials are not correct; C<$sessionid> are undef
1280 =item "maintenance" -- DB is in maintenance mode; no login possible at the moment
1282 =item "expired -- session cookie has expired; API user should resubmit userid and password
1288 sub check_cookie_auth {
1290 my $flagsrequired = shift;
1292 my $dbh = C4::Context->dbh;
1293 my $timeout = C4::Context->preference('timeout');
1294 $timeout = 600 unless $timeout;
1296 unless (C4::Context->preference('Version')) {
1297 # database has not been installed yet
1298 return ("maintenance", undef);
1300 my $kohaversion=C4::Context::KOHAVERSION;
1301 $kohaversion =~ s/(.*\..*)\.(.*)\.(.*)/$1$2$3/;
1302 if (C4::Context->preference('Version') < $kohaversion) {
1303 # database in need of version update; assume that
1304 # no API should be called while databsae is in
1306 return ("maintenance", undef);
1309 # FIXME -- most of what follows is a copy-and-paste
1310 # of code from checkauth. There is an obvious need
1311 # for refactoring to separate the various parts of
1312 # the authentication code, but as of 2007-11-23 this
1313 # is deferred so as to not introduce bugs into the
1314 # regular authentication code for Koha 3.0.
1316 # see if we have a valid session cookie already
1317 # however, if a userid parameter is present (i.e., from
1318 # a form submission, assume that any current cookie
1320 unless (defined $cookie and $cookie) {
1321 return ("failed", undef);
1323 my $sessionID = $cookie;
1324 my $session = get_session($sessionID);
1325 C4::Context->_new_userenv($sessionID);
1327 C4::Context::set_userenv(
1328 $session->param('number'), $session->param('id'),
1329 $session->param('cardnumber'), $session->param('firstname'),
1330 $session->param('surname'), $session->param('branch'),
1331 $session->param('branchname'), $session->param('flags'),
1332 $session->param('emailaddress'), $session->param('branchprinter')
1335 my $ip = $session->param('ip');
1336 my $lasttime = $session->param('lasttime');
1337 my $userid = $session->param('id');
1338 if ( $lasttime < time() - $timeout ) {
1341 C4::Context->_unset_userenv($sessionID);
1344 return ("expired", undef);
1345 } elsif ( $ip ne $ENV{'REMOTE_ADDR'} ) {
1346 # IP address changed
1348 C4::Context->_unset_userenv($sessionID);
1351 return ("expired", undef);
1353 $session->param('lasttime',time());
1354 my $flags = haspermission($userid, $flagsrequired);
1356 return ("ok", $sessionID);
1359 C4::Context->_unset_userenv($sessionID);
1362 return ("failed", undef);
1366 return ("expired", undef);
1373 my $session = get_session($sessionID);
1375 Given a session ID, retrieve the CGI::Session object used to store
1376 the session's state. The session object can be used to store
1377 data that needs to be accessed by different scripts during a
1380 If the C<$sessionID> parameter is an empty string, a new session
1386 my $sessionID = shift;
1387 my $storage_method = C4::Context->preference('SessionStorage');
1388 my $dbh = C4::Context->dbh;
1390 if ($storage_method eq 'mysql'){
1391 $session = new CGI::Session("driver:MySQL;serializer:yaml;id:md5", $sessionID, {Handle=>$dbh});
1393 elsif ($storage_method eq 'Pg') {
1394 $session = new CGI::Session("driver:PostgreSQL;serializer:yaml;id:md5", $sessionID, {Handle=>$dbh});
1396 elsif ($storage_method eq 'memcached' && C4::Context->ismemcached){
1397 $session = new CGI::Session("driver:memcached;serializer:yaml;id:md5", $sessionID, { Memcached => C4::Context->memcached } );
1400 # catch all defaults to tmp should work on all systems
1401 $session = new CGI::Session("driver:File;serializer:yaml;id:md5", $sessionID, {Directory=>'/tmp'});
1408 my ( $dbh, $userid, $password, $query ) = @_;
1410 $debug and print "## checkpw - checking LDAP\n";
1411 my ($retval,$retcard,$retuserid) = checkpw_ldap(@_); # EXTERNAL AUTH
1412 ($retval) and return ($retval,$retcard,$retuserid);
1415 if ($cas && $query && $query->param('ticket')) {
1416 $debug and print STDERR "## checkpw - checking CAS\n";
1417 # In case of a CAS authentication, we use the ticket instead of the password
1418 my $ticket = $query->param('ticket');
1419 my ($retval,$retcard,$retuserid) = checkpw_cas($dbh, $ticket, $query); # EXTERNAL AUTH
1420 ($retval) and return ($retval,$retcard,$retuserid);
1427 "select password,cardnumber,borrowernumber,userid,firstname,surname,branchcode,flags from borrowers where userid=?"
1429 $sth->execute($userid);
1431 my ( $md5password, $cardnumber, $borrowernumber, $userid, $firstname,
1432 $surname, $branchcode, $flags )
1434 if ( md5_base64($password) eq $md5password and $md5password ne "!") {
1436 C4::Context->set_userenv( "$borrowernumber", $userid, $cardnumber,
1437 $firstname, $surname, $branchcode, $flags );
1438 return 1, $cardnumber, $userid;
1443 "select password,cardnumber,borrowernumber,userid, firstname,surname,branchcode,flags from borrowers where cardnumber=?"
1445 $sth->execute($userid);
1447 my ( $md5password, $cardnumber, $borrowernumber, $userid, $firstname,
1448 $surname, $branchcode, $flags )
1450 if ( md5_base64($password) eq $md5password ) {
1452 C4::Context->set_userenv( $borrowernumber, $userid, $cardnumber,
1453 $firstname, $surname, $branchcode, $flags );
1454 return 1, $cardnumber, $userid;
1457 if ( $userid && $userid eq C4::Context->config('user')
1458 && "$password" eq C4::Context->config('pass') )
1461 # Koha superuser account
1462 # C4::Context->set_userenv(0,0,C4::Context->config('user'),C4::Context->config('user'),C4::Context->config('user'),"",1);
1465 if ( $userid && $userid eq 'demo'
1466 && "$password" eq 'demo'
1467 && C4::Context->config('demo') )
1470 # DEMO => the demo user is allowed to do everything (if demo set to 1 in koha.conf
1471 # some features won't be effective : modify systempref, modify MARC structure,
1479 my $authflags = getuserflags($flags, $userid, [$dbh]);
1481 Translates integer flags into permissions strings hash.
1483 C<$flags> is the integer userflags value ( borrowers.userflags )
1484 C<$userid> is the members.userid, used for building subpermissions
1485 C<$authflags> is a hashref of permissions
1492 my $dbh = @_ ? shift : C4::Context->dbh;
1494 $flags = 0 unless $flags;
1495 my $sth = $dbh->prepare("SELECT bit, flag, defaulton FROM userflags");
1498 while ( my ( $bit, $flag, $defaulton ) = $sth->fetchrow ) {
1499 if ( ( $flags & ( 2**$bit ) ) || $defaulton ) {
1500 $userflags->{$flag} = 1;
1503 $userflags->{$flag} = 0;
1507 # get subpermissions and merge with top-level permissions
1508 my $user_subperms = get_user_subpermissions($userid);
1509 foreach my $module (keys %$user_subperms) {
1510 next if $userflags->{$module} == 1; # user already has permission for everything in this module
1511 $userflags->{$module} = $user_subperms->{$module};
1517 =head2 get_user_subpermissions
1519 $user_perm_hashref = get_user_subpermissions($userid);
1521 Given the userid (note, not the borrowernumber) of a staff user,
1522 return a hashref of hashrefs of the specific subpermissions
1523 accorded to the user. An example return is
1527 export_catalog => 1,
1528 import_patrons => 1,
1532 The top-level hash-key is a module or function code from
1533 userflags.flag, while the second-level key is a code
1536 The results of this function do not give a complete picture
1537 of the functions that a staff user can access; it is also
1538 necessary to check borrowers.flags.
1542 sub get_user_subpermissions {
1545 my $dbh = C4::Context->dbh;
1546 my $sth = $dbh->prepare("SELECT flag, user_permissions.code
1547 FROM user_permissions
1548 JOIN permissions USING (module_bit, code)
1549 JOIN userflags ON (module_bit = bit)
1550 JOIN borrowers USING (borrowernumber)
1552 $sth->execute($userid);
1554 my $user_perms = {};
1555 while (my $perm = $sth->fetchrow_hashref) {
1556 $user_perms->{$perm->{'flag'}}->{$perm->{'code'}} = 1;
1561 =head2 get_all_subpermissions
1563 my $perm_hashref = get_all_subpermissions();
1565 Returns a hashref of hashrefs defining all specific
1566 permissions currently defined. The return value
1567 has the same structure as that of C<get_user_subpermissions>,
1568 except that the innermost hash value is the description
1569 of the subpermission.
1573 sub get_all_subpermissions {
1574 my $dbh = C4::Context->dbh;
1575 my $sth = $dbh->prepare("SELECT flag, code, description
1577 JOIN userflags ON (module_bit = bit)");
1581 while (my $perm = $sth->fetchrow_hashref) {
1582 $all_perms->{$perm->{'flag'}}->{$perm->{'code'}} = $perm->{'description'};
1587 =head2 haspermission
1589 $flags = ($userid, $flagsrequired);
1591 C<$userid> the userid of the member
1592 C<$flags> is a hashref of required flags like C<$borrower-<{authflags}>
1594 Returns member's flags or 0 if a permission is not met.
1599 my ($userid, $flagsrequired) = @_;
1600 my $sth = C4::Context->dbh->prepare("SELECT flags FROM borrowers WHERE userid=?");
1601 $sth->execute($userid);
1602 my $flags = getuserflags($sth->fetchrow(), $userid);
1603 if ( $userid eq C4::Context->config('user') ) {
1604 # Super User Account from /etc/koha.conf
1605 $flags->{'superlibrarian'} = 1;
1607 elsif ( $userid eq 'demo' && C4::Context->config('demo') ) {
1608 # Demo user that can do "anything" (demo=1 in /etc/koha.conf)
1609 $flags->{'superlibrarian'} = 1;
1612 return $flags if $flags->{superlibrarian};
1614 foreach my $module ( keys %$flagsrequired ) {
1615 my $subperm = $flagsrequired->{$module};
1616 if ($subperm eq '*') {
1617 return 0 unless ( $flags->{$module} == 1 or ref($flags->{$module}) );
1619 return 0 unless ( $flags->{$module} == 1 or
1620 ( ref($flags->{$module}) and
1621 exists $flags->{$module}->{$subperm} and
1622 $flags->{$module}->{$subperm} == 1
1628 #FIXME - This fcn should return the failed permission so a suitable error msg can be delivered.
1632 sub getborrowernumber {
1634 my $userenv = C4::Context->userenv;
1635 if ( defined( $userenv ) && ref( $userenv ) eq 'HASH' && $userenv->{number} ) {
1636 return $userenv->{number};
1638 my $dbh = C4::Context->dbh;
1639 for my $field ( 'userid', 'cardnumber' ) {
1641 $dbh->prepare("select borrowernumber from borrowers where $field=?");
1642 $sth->execute($userid);
1644 my ($bnumber) = $sth->fetchrow;
1651 END { } # module clean-up code here (global destructor)