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::Output; # to get the template
32 use C4::Branch; # GetBranches
33 use C4::VirtualShelves;
34 use POSIX qw/strftime/;
37 use vars qw($VERSION @ISA @EXPORT @EXPORT_OK %EXPORT_TAGS $debug $ldap $cas $caslogout $servers $memcached);
40 $VERSION = 3.02; # set version for version checking
43 @EXPORT = qw(&checkauth &get_template_and_user &haspermission &get_user_subpermissions);
44 @EXPORT_OK = qw(&check_api_auth &get_session &check_cookie_auth &checkpw &get_all_subpermissions &get_user_subpermissions);
45 %EXPORT_TAGS = (EditPermissions => [qw(get_all_subpermissions get_user_subpermissions)]);
46 $ldap = C4::Context->config('useldapserver') || 0;
47 $cas = C4::Context->preference('casAuthentication');
48 $caslogout = C4::Context->preference('casLogout');
50 require C4::Auth_with_ldap; # no import
51 import C4::Auth_with_ldap qw(checkpw_ldap);
54 require C4::Auth_with_cas; # no import
55 import C4::Auth_with_cas qw(checkpw_cas login_cas logout_cas login_cas_url);
57 $servers = C4::Context->config('memcached_servers');
59 require Cache::Memcached;
60 $memcached = Cache::Memcached->new({
61 servers => [ $servers ],
63 compress_threshold => 10_000,
64 namespace => C4::Context->config('memcached_namespace') || 'koha',
71 C4::Auth - Authenticates Koha users
81 my ($template, $borrowernumber, $cookie)
82 = get_template_and_user(
84 template_name => "opac-main.tmpl",
88 flagsrequired => {borrow => 1, catalogue => '*', tools => 'import_patrons' },
92 output_html_with_http_headers $query, $cookie, $template->output;
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.
103 =head2 get_template_and_user
105 my ($template, $borrowernumber, $cookie)
106 = get_template_and_user(
108 template_name => "opac-main.tmpl",
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
127 More information on the C<gettemplate> sub can be found in the
132 my $SEARCH_HISTORY_INSERT_SQL =<<EOQ;
133 INSERT INTO search_history(userid, sessionid, query_desc, query_cgi, total, time )
134 VALUES ( ?, ?, ?, ?, ?, FROM_UNIXTIME(?))
136 sub get_template_and_user {
139 gettemplate( $in->{'template_name'}, $in->{'type'}, $in->{'query'} );
140 my ( $user, $cookie, $sessionID, $flags ) = checkauth(
142 $in->{'authnotrequired'},
143 $in->{'flagsrequired'},
145 ) unless ($in->{'template_name'}=~/maintenance/);
148 my $insecure = C4::Context->preference('insecure');
149 if ($user or $insecure) {
151 # load the template variables for stylesheets and JavaScript
152 $template->param( css_libs => $in->{'css_libs'} );
153 $template->param( css_module => $in->{'css_module'} );
154 $template->param( css_page => $in->{'css_page'} );
155 $template->param( css_widgets => $in->{'css_widgets'} );
157 $template->param( js_libs => $in->{'js_libs'} );
158 $template->param( js_module => $in->{'js_module'} );
159 $template->param( js_page => $in->{'js_page'} );
160 $template->param( js_widgets => $in->{'js_widgets'} );
163 $template->param( loggedinusername => $user );
164 $template->param( sessionID => $sessionID );
166 my ($total, $pubshelves, $barshelves) = C4::Context->get_shelves_userenv();
167 if (defined($pubshelves)) {
168 $template->param( pubshelves => scalar @{$pubshelves},
169 pubshelvesloop => $pubshelves,
171 $template->param( pubtotal => $total->{'pubtotal'}, ) if ($total->{'pubtotal'} > scalar @{$pubshelves});
173 if (defined($barshelves)) {
174 $template->param( barshelves => scalar @{$barshelves},
175 barshelvesloop => $barshelves,
177 $template->param( bartotal => $total->{'bartotal'}, ) if ($total->{'bartotal'} > scalar @{$barshelves});
180 $borrowernumber = getborrowernumber($user) if defined($user);
182 my ( $borr ) = GetMemberDetails( $borrowernumber );
185 $template->param( "USER_INFO" => \@bordat );
187 my $all_perms = get_all_subpermissions();
189 my @flagroots = qw(circulate catalogue parameters borrowers permissions reserveforothers borrow
190 editcatalogue updatecharges management tools editauthorities serials reports acquisition);
191 # We are going to use the $flags returned by checkauth
192 # to create the template's parameters that will indicate
193 # which menus the user can access.
194 if (( $flags && $flags->{superlibrarian}==1) or $insecure==1) {
195 $template->param( CAN_user_circulate => 1 );
196 $template->param( CAN_user_catalogue => 1 );
197 $template->param( CAN_user_parameters => 1 );
198 $template->param( CAN_user_borrowers => 1 );
199 $template->param( CAN_user_permissions => 1 );
200 $template->param( CAN_user_reserveforothers => 1 );
201 $template->param( CAN_user_borrow => 1 );
202 $template->param( CAN_user_editcatalogue => 1 );
203 $template->param( CAN_user_updatecharges => 1 );
204 $template->param( CAN_user_acquisition => 1 );
205 $template->param( CAN_user_management => 1 );
206 $template->param( CAN_user_tools => 1 );
207 $template->param( CAN_user_editauthorities => 1 );
208 $template->param( CAN_user_serials => 1 );
209 $template->param( CAN_user_reports => 1 );
210 $template->param( CAN_user_staffaccess => 1 );
211 foreach my $module (keys %$all_perms) {
212 foreach my $subperm (keys %{ $all_perms->{$module} }) {
213 $template->param( "CAN_user_${module}_${subperm}" => 1 );
219 foreach my $module (keys %$all_perms) {
220 if ( $flags->{$module} == 1) {
221 foreach my $subperm (keys %{ $all_perms->{$module} }) {
222 $template->param( "CAN_user_${module}_${subperm}" => 1 );
224 } elsif ( ref($flags->{$module}) ) {
225 foreach my $subperm (keys %{ $flags->{$module} } ) {
226 $template->param( "CAN_user_${module}_${subperm}" => 1 );
233 foreach my $module (keys %$flags) {
234 if ( $flags->{$module} == 1 or ref($flags->{$module}) ) {
235 $template->param( "CAN_user_$module" => 1 );
236 if ($module eq "parameters") {
237 $template->param( CAN_user_management => 1 );
242 # Logged-in opac search history
243 # If the requested template is an opac one and opac search history is enabled
244 if ($in->{type} eq 'opac' && C4::Context->preference('EnableOpacSearchHistory')) {
245 my $dbh = C4::Context->dbh;
246 my $query = "SELECT COUNT(*) FROM search_history WHERE userid=?";
247 my $sth = $dbh->prepare($query);
248 $sth->execute($borrowernumber);
250 # If at least one search has already been performed
251 if ($sth->fetchrow_array > 0) {
252 # We show the link in opac
253 $template->param(ShowOpacRecentSearchLink => 1);
256 # And if there's a cookie with searches performed when the user was not logged in,
257 # we add them to the logged-in search history
258 my $searchcookie = $in->{'query'}->cookie('KohaOpacRecentSearches');
260 $searchcookie = uri_unescape($searchcookie);
261 my @recentSearches = @{thaw($searchcookie) || []};
262 if (@recentSearches) {
263 my $sth = $dbh->prepare($SEARCH_HISTORY_INSERT_SQL);
264 $sth->execute( $borrowernumber,
265 $in->{'query'}->cookie("CGISESSID"),
270 ) foreach @recentSearches;
272 # And then, delete the cookie's content
273 my $newsearchcookie = $in->{'query'}->cookie(
274 -name => 'KohaOpacRecentSearches',
275 -value => freeze([]),
278 $cookie = [$cookie, $newsearchcookie];
283 else { # if this is an anonymous session, setup to display public lists...
285 # load the template variables for stylesheets and JavaScript
286 $template->param( css_libs => $in->{'css_libs'} );
287 $template->param( css_module => $in->{'css_module'} );
288 $template->param( css_page => $in->{'css_page'} );
289 $template->param( css_widgets => $in->{'css_widgets'} );
291 $template->param( js_libs => $in->{'js_libs'} );
292 $template->param( js_module => $in->{'js_module'} );
293 $template->param( js_page => $in->{'js_page'} );
294 $template->param( js_widgets => $in->{'js_widgets'} );
296 $template->param( sessionID => $sessionID );
298 my ($total, $pubshelves) = C4::Context->get_shelves_userenv(); # an anonymous user has no 'barshelves'...
299 if (defined $pubshelves) {
300 $template->param( pubshelves => scalar @{$pubshelves},
301 pubshelvesloop => $pubshelves,
303 $template->param( pubtotal => $total->{'pubtotal'}, ) if ($total->{'pubtotal'} > scalar @{$pubshelves});
307 # Anonymous opac search history
308 # If opac search history is enabled and at least one search has already been performed
309 if (C4::Context->preference('EnableOpacSearchHistory')) {
310 my $searchcookie = $in->{'query'}->cookie('KohaOpacRecentSearches');
312 $searchcookie = uri_unescape($searchcookie);
313 my @recentSearches = @{thaw($searchcookie) || []};
314 # We show the link in opac
315 if (@recentSearches) {
316 $template->param(ShowOpacRecentSearchLink => 1);
321 if(C4::Context->preference('dateformat')){
322 if(C4::Context->preference('dateformat') eq "metric"){
323 $template->param(dateformat_metric => 1);
324 } elsif(C4::Context->preference('dateformat') eq "us"){
325 $template->param(dateformat_us => 1);
327 $template->param(dateformat_iso => 1);
330 $template->param(dateformat_iso => 1);
333 # these template parameters are set the same regardless of $in->{'type'}
335 "BiblioDefaultView".C4::Context->preference("BiblioDefaultView") => 1,
336 EnhancedMessagingPreferences => C4::Context->preference('EnhancedMessagingPreferences'),
337 GoogleJackets => C4::Context->preference("GoogleJackets"),
338 KohaAdminEmailAddress => "" . C4::Context->preference("KohaAdminEmailAddress"),
339 LoginBranchcode => (C4::Context->userenv?C4::Context->userenv->{"branch"}:"insecure"),
340 LoginFirstname => (C4::Context->userenv?C4::Context->userenv->{"firstname"}:"Bel"),
341 LoginSurname => C4::Context->userenv?C4::Context->userenv->{"surname"}:"Inconnu",
342 TagsEnabled => C4::Context->preference("TagsEnabled"),
343 hide_marc => C4::Context->preference("hide_marc"),
344 item_level_itypes => C4::Context->preference('item-level_itypes'),
345 patronimages => C4::Context->preference("patronimages"),
346 singleBranchMode => C4::Context->preference("singleBranchMode"),
347 XSLTDetailsDisplay => C4::Context->preference("XSLTDetailsDisplay"),
348 XSLTResultsDisplay => C4::Context->preference("XSLTResultsDisplay"),
349 using_https => $in->{'query'}->https() ? 1 : 0,
350 noItemTypeImages => C4::Context->preference("noItemTypeImages"),
353 if ( $in->{'type'} eq "intranet" ) {
355 AmazonContent => C4::Context->preference("AmazonContent"),
356 AmazonCoverImages => C4::Context->preference("AmazonCoverImages"),
357 AmazonEnabled => C4::Context->preference("AmazonEnabled"),
358 AmazonSimilarItems => C4::Context->preference("AmazonSimilarItems"),
359 AutoLocation => C4::Context->preference("AutoLocation"),
360 "BiblioDefaultView".C4::Context->preference("IntranetBiblioDefaultView") => 1,
361 CircAutocompl => C4::Context->preference("CircAutocompl"),
362 FRBRizeEditions => C4::Context->preference("FRBRizeEditions"),
363 IndependantBranches => C4::Context->preference("IndependantBranches"),
364 IntranetNav => C4::Context->preference("IntranetNav"),
365 IntranetmainUserblock => C4::Context->preference("IntranetmainUserblock"),
366 LibraryName => C4::Context->preference("LibraryName"),
367 LoginBranchname => (C4::Context->userenv?C4::Context->userenv->{"branchname"}:"insecure"),
368 advancedMARCEditor => C4::Context->preference("advancedMARCEditor"),
369 canreservefromotherbranches => C4::Context->preference('canreservefromotherbranches'),
370 intranetcolorstylesheet => C4::Context->preference("intranetcolorstylesheet"),
371 IntranetFavicon => C4::Context->preference("IntranetFavicon"),
372 intranetreadinghistory => C4::Context->preference("intranetreadinghistory"),
373 intranetstylesheet => C4::Context->preference("intranetstylesheet"),
374 IntranetUserCSS => C4::Context->preference("IntranetUserCSS"),
375 intranetuserjs => C4::Context->preference("intranetuserjs"),
376 intranetbookbag => C4::Context->preference("intranetbookbag"),
377 suggestion => C4::Context->preference("suggestion"),
378 virtualshelves => C4::Context->preference("virtualshelves"),
379 StaffSerialIssueDisplayCount => C4::Context->preference("StaffSerialIssueDisplayCount"),
380 NoZebra => C4::Context->preference('NoZebra'),
384 warn "template type should be OPAC, here it is=[" . $in->{'type'} . "]" unless ( $in->{'type'} eq 'opac' );
385 #TODO : replace LibraryName syspref with 'system name', and remove this html processing
386 my $LibraryNameTitle = C4::Context->preference("LibraryName");
387 $LibraryNameTitle =~ s/<(?:\/?)(?:br|p)\s*(?:\/?)>/ /sgi;
388 $LibraryNameTitle =~ s/<(?:[^<>'"]|'(?:[^']*)'|"(?:[^"]*)")*>//sg;
389 # variables passed from CGI: opac_css_override and opac_search_limits.
390 my $opac_search_limit = $ENV{'OPAC_SEARCH_LIMIT'};
391 my $opac_limit_override = $ENV{'OPAC_LIMIT_OVERRIDE'};
393 if (($opac_search_limit =~ /branch:(\w+)/ && $opac_limit_override) || $in->{'query'}->param('limit') =~ /branch:(\w+)/){
394 $opac_name = $1; # opac_search_limit is a branch, so we use it.
395 } elsif (C4::Context->preference("SearchMyLibraryFirst") && C4::Context->userenv && C4::Context->userenv->{'branch'}) {
396 $opac_name = C4::Context->userenv->{'branch'};
398 my $checkstyle = C4::Context->preference("opaccolorstylesheet");
399 if ($checkstyle =~ /http/)
401 $template->param( opacexternalsheet => $checkstyle);
404 my $opaccolorstylesheet = C4::Context->preference("opaccolorstylesheet");
405 $template->param( opaccolorstylesheet => $opaccolorstylesheet);
408 AmazonContent => "" . C4::Context->preference("AmazonContent"),
409 AnonSuggestions => "" . C4::Context->preference("AnonSuggestions"),
410 AuthorisedValueImages => C4::Context->preference("AuthorisedValueImages"),
411 BranchesLoop => GetBranchesLoop($opac_name),
412 LibraryName => "" . C4::Context->preference("LibraryName"),
413 LibraryNameTitle => "" . $LibraryNameTitle,
414 LoginBranchname => C4::Context->userenv?C4::Context->userenv->{"branchname"}:"",
415 OPACAmazonEnabled => C4::Context->preference("OPACAmazonEnabled"),
416 OPACAmazonSimilarItems => C4::Context->preference("OPACAmazonSimilarItems"),
417 OPACAmazonCoverImages => C4::Context->preference("OPACAmazonCoverImages"),
418 OPACAmazonReviews => C4::Context->preference("OPACAmazonReviews"),
419 OPACFRBRizeEditions => C4::Context->preference("OPACFRBRizeEditions"),
420 OpacHighlightedWords => C4::Context->preference("OpacHighlightedWords"),
421 OPACItemHolds => C4::Context->preference("OPACItemHolds"),
422 OPACShelfBrowser => "". C4::Context->preference("OPACShelfBrowser"),
423 OPACURLOpenInNewWindow => "" . C4::Context->preference("OPACURLOpenInNewWindow"),
424 OPACUserCSS => "". C4::Context->preference("OPACUserCSS"),
425 OPACViewOthersSuggestions => "" . C4::Context->preference("OPACViewOthersSuggestions"),
426 OpacAuthorities => C4::Context->preference("OpacAuthorities"),
427 OPACBaseURL => ($in->{'query'}->https() ? "https://" : "http://") . $ENV{'SERVER_NAME'} .
428 ($ENV{'SERVER_PORT'} eq ($in->{'query'}->https() ? "443" : "80") ? '' : ":$ENV{'SERVER_PORT'}"),
429 opac_css_override => $ENV{'OPAC_CSS_OVERRIDE'},
430 opac_search_limit => $opac_search_limit,
431 opac_limit_override => $opac_limit_override,
432 OpacBrowser => C4::Context->preference("OpacBrowser"),
433 OpacCloud => C4::Context->preference("OpacCloud"),
434 OpacMainUserBlock => "" . C4::Context->preference("OpacMainUserBlock"),
435 OpacNav => "" . C4::Context->preference("OpacNav"),
436 OpacPasswordChange => C4::Context->preference("OpacPasswordChange"),
437 OPACPatronDetails => C4::Context->preference("OPACPatronDetails"),
438 OPACPrivacy => C4::Context->preference("OPACPrivacy"),
439 OPACFinesTab => C4::Context->preference("OPACFinesTab"),
440 OpacTopissue => C4::Context->preference("OpacTopissue"),
441 RequestOnOpac => C4::Context->preference("RequestOnOpac"),
442 'Version' => C4::Context->preference('Version'),
443 hidelostitems => C4::Context->preference("hidelostitems"),
444 mylibraryfirst => (C4::Context->preference("SearchMyLibraryFirst") && C4::Context->userenv) ? C4::Context->userenv->{'branch'} : '',
445 opaclayoutstylesheet => "" . C4::Context->preference("opaclayoutstylesheet"),
446 opacstylesheet => "" . C4::Context->preference("opacstylesheet"),
447 opacbookbag => "" . C4::Context->preference("opacbookbag"),
448 opaccredits => "" . C4::Context->preference("opaccredits"),
449 OpacFavicon => C4::Context->preference("OpacFavicon"),
450 opacheader => "" . C4::Context->preference("opacheader"),
451 opaclanguagesdisplay => "" . C4::Context->preference("opaclanguagesdisplay"),
452 opacreadinghistory => C4::Context->preference("opacreadinghistory"),
453 opacsmallimage => "" . C4::Context->preference("opacsmallimage"),
454 opacuserjs => C4::Context->preference("opacuserjs"),
455 opacuserlogin => "" . C4::Context->preference("opacuserlogin"),
456 reviewson => C4::Context->preference("reviewson"),
457 ShowReviewer => C4::Context->preference("ShowReviewer"),
458 suggestion => "" . C4::Context->preference("suggestion"),
459 virtualshelves => "" . C4::Context->preference("virtualshelves"),
460 OPACSerialIssueDisplayCount => C4::Context->preference("OPACSerialIssueDisplayCount"),
461 OpacAddMastheadLibraryPulldown => C4::Context->preference("OpacAddMastheadLibraryPulldown"),
462 OPACXSLTDetailsDisplay => C4::Context->preference("OPACXSLTDetailsDisplay"),
463 OPACXSLTResultsDisplay => C4::Context->preference("OPACXSLTResultsDisplay"),
464 SyndeticsClientCode => C4::Context->preference("SyndeticsClientCode"),
465 SyndeticsEnabled => C4::Context->preference("SyndeticsEnabled"),
466 SyndeticsCoverImages => C4::Context->preference("SyndeticsCoverImages"),
467 SyndeticsTOC => C4::Context->preference("SyndeticsTOC"),
468 SyndeticsSummary => C4::Context->preference("SyndeticsSummary"),
469 SyndeticsEditions => C4::Context->preference("SyndeticsEditions"),
470 SyndeticsExcerpt => C4::Context->preference("SyndeticsExcerpt"),
471 SyndeticsReviews => C4::Context->preference("SyndeticsReviews"),
472 SyndeticsAuthorNotes => C4::Context->preference("SyndeticsAuthorNotes"),
473 SyndeticsAwards => C4::Context->preference("SyndeticsAwards"),
474 SyndeticsSeries => C4::Context->preference("SyndeticsSeries"),
475 SyndeticsCoverImageSize => C4::Context->preference("SyndeticsCoverImageSize"),
478 $template->param(OpacPublic => '1') if ($user || C4::Context->preference("OpacPublic"));
480 $template->param(listloop=>[{shelfname=>"Freelist", shelfnumber=>110}]);
481 return ( $template, $borrowernumber, $cookie, $flags);
486 ($userid, $cookie, $sessionID) = &checkauth($query, $noauth, $flagsrequired, $type);
488 Verifies that the user is authorized to run this script. If
489 the user is authorized, a (userid, cookie, session-id, flags)
490 quadruple is returned. If the user is not authorized but does
491 not have the required privilege (see $flagsrequired below), it
492 displays an error page and exits. Otherwise, it displays the
493 login page and exits.
495 Note that C<&checkauth> will return if and only if the user
496 is authorized, so it should be called early on, before any
497 unfinished operations (e.g., if you've opened a file, then
498 C<&checkauth> won't close it for you).
500 C<$query> is the CGI object for the script calling C<&checkauth>.
502 The C<$noauth> argument is optional. If it is set, then no
503 authorization is required for the script.
505 C<&checkauth> fetches user and session information from C<$query> and
506 ensures that the user is authorized to run scripts that require
509 The C<$flagsrequired> argument specifies the required privileges
510 the user must have if the username and password are correct.
511 It should be specified as a reference-to-hash; keys in the hash
512 should be the "flags" for the user, as specified in the Members
513 intranet module. Any key specified must correspond to a "flag"
514 in the userflags table. E.g., { circulate => 1 } would specify
515 that the user must have the "circulate" privilege in order to
516 proceed. To make sure that access control is correct, the
517 C<$flagsrequired> parameter must be specified correctly.
519 Koha also has a concept of sub-permissions, also known as
520 granular permissions. This makes the value of each key
521 in the C<flagsrequired> hash take on an additional
526 The user must have access to all subfunctions of the module
527 specified by the hash key.
531 The user must have access to at least one subfunction of the module
532 specified by the hash key.
534 specific permission, e.g., 'export_catalog'
536 The user must have access to the specific subfunction list, which
537 must correspond to a row in the permissions table.
539 The C<$type> argument specifies whether the template should be
540 retrieved from the opac or intranet directory tree. "opac" is
541 assumed if it is not specified; however, if C<$type> is specified,
542 "intranet" is assumed if it is not "opac".
544 If C<$query> does not have a valid session ID associated with it
545 (i.e., the user has not logged in) or if the session has expired,
546 C<&checkauth> presents the user with a login page (from the point of
547 view of the original script, C<&checkauth> does not return). Once the
548 user has authenticated, C<&checkauth> restarts the original script
549 (this time, C<&checkauth> returns).
551 The login page is provided using a HTML::Template, which is set in the
552 systempreferences table or at the top of this file. The variable C<$type>
553 selects which template to use, either the opac or the intranet
554 authentification template.
556 C<&checkauth> returns a user ID, a cookie, and a session ID. The
557 cookie should be sent back to the browser; it verifies that the user
562 sub _version_check ($$) {
566 # If Version syspref is unavailable, it means Koha is beeing installed,
567 # and so we must redirect to OPAC maintenance page or to the WebInstaller
568 # also, if OpacMaintenance is ON, OPAC should redirect to maintenance
569 if (C4::Context->preference('OpacMaintenance') && $type eq 'opac') {
570 warn "OPAC Install required, redirecting to maintenance";
571 print $query->redirect("/cgi-bin/koha/maintenance.pl");
573 unless ($version = C4::Context->preference('Version')) { # assignment, not comparison
574 if ($type ne 'opac') {
575 warn "Install required, redirecting to Installer";
576 print $query->redirect("/cgi-bin/koha/installer/install.pl");
579 warn "OPAC Install required, redirecting to maintenance";
580 print $query->redirect("/cgi-bin/koha/maintenance.pl");
585 # check that database and koha version are the same
586 # there is no DB version, it's a fresh install,
587 # go to web installer
588 # there is a DB version, compare it to the code version
589 my $kohaversion=C4::Context::KOHAVERSION;
590 # remove the 3 last . to have a Perl number
591 $kohaversion =~ s/(.*\..*)\.(.*)\.(.*)/$1$2$3/;
592 $debug and print STDERR "kohaversion : $kohaversion\n";
593 if ($version < $kohaversion){
594 my $warning = "Database update needed, redirecting to %s. Database is $version and Koha is $kohaversion";
595 if ($type ne 'opac'){
596 warn sprintf($warning, 'Installer');
597 print $query->redirect("/cgi-bin/koha/installer/install.pl?step=3");
599 warn sprintf("OPAC: " . $warning, 'maintenance');
600 print $query->redirect("/cgi-bin/koha/maintenance.pl");
608 open L, ">>/tmp/sessionlog" or warn "ERROR: Cannot append to /tmp/sessionlog";
609 printf L join("\n",@_);
615 $debug and warn "Checking Auth";
616 # $authnotrequired will be set for scripts which will run without authentication
617 my $authnotrequired = shift;
618 my $flagsrequired = shift;
620 $type = 'opac' unless $type;
622 my $dbh = C4::Context->dbh;
623 my $timeout = C4::Context->preference('timeout');
625 if ($timeout =~ /(\d+)[dD]/) {
626 $timeout = $1 * 86400;
628 $timeout = 600 unless $timeout;
630 _version_check($type,$query);
634 my ( $userid, $cookie, $sessionID, $flags, $barshelves, $pubshelves );
635 my $logout = $query->param('logout.x');
637 if ( $userid = $ENV{'REMOTE_USER'} ) {
638 # Using Basic Authentication, no cookies required
639 $cookie = $query->cookie(
640 -name => 'CGISESSID',
646 elsif ( $sessionID = $query->cookie("CGISESSID")) { # assignment, not comparison
647 my $session = get_session($sessionID);
648 C4::Context->_new_userenv($sessionID);
649 my ($ip, $lasttime, $sessiontype);
651 C4::Context::set_userenv(
652 $session->param('number'), $session->param('id'),
653 $session->param('cardnumber'), $session->param('firstname'),
654 $session->param('surname'), $session->param('branch'),
655 $session->param('branchname'), $session->param('flags'),
656 $session->param('emailaddress'), $session->param('branchprinter')
658 C4::Context::set_shelves_userenv('bar',$session->param('barshelves'));
659 C4::Context::set_shelves_userenv('pub',$session->param('pubshelves'));
660 C4::Context::set_shelves_userenv('tot',$session->param('totshelves'));
661 $debug and printf STDERR "AUTH_SESSION: (%s)\t%s %s - %s\n", map {$session->param($_)} qw(cardnumber firstname surname branch) ;
662 $ip = $session->param('ip');
663 $lasttime = $session->param('lasttime');
664 $userid = $session->param('id');
665 $sessiontype = $session->param('sessiontype');
667 if ( ($query->param('koha_login_context')) && ($query->param('userid') ne $session->param('id')) ) {
668 #if a user enters an id ne to the id in the current session, we need to log them in...
669 #first we need to clear the anonymous session...
670 $debug and warn "query id = " . $query->param('userid') . " but session id = " . $session->param('id');
673 C4::Context->_unset_userenv($sessionID);
678 # voluntary logout the user
681 C4::Context->_unset_userenv($sessionID);
682 _session_log(sprintf "%20s from %16s logged out at %30s (manually).\n", $userid,$ip,(strftime "%c",localtime));
686 if ($cas and $caslogout) {
690 elsif ( $lasttime < time() - $timeout ) {
692 $info{'timed_out'} = 1;
694 C4::Context->_unset_userenv($sessionID);
695 _session_log(sprintf "%20s from %16s logged out at %30s (inactivity).\n", $userid,$ip,(strftime "%c",localtime));
699 elsif ( $ip ne $ENV{'REMOTE_ADDR'} ) {
700 # Different ip than originally logged in from
701 $info{'oldip'} = $ip;
702 $info{'newip'} = $ENV{'REMOTE_ADDR'};
703 $info{'different_ip'} = 1;
705 C4::Context->_unset_userenv($sessionID);
706 _session_log(sprintf "%20s from %16s logged out at %30s (ip changed to %16s).\n", $userid,$ip,(strftime "%c",localtime), $info{'newip'});
711 $cookie = $query->cookie( CGISESSID => $session->id );
712 $session->param('lasttime',time());
713 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...
714 $flags = haspermission($userid, $flagsrequired);
718 $info{'nopermission'} = 1;
723 unless ($userid || $sessionID) {
724 #we initiate a session prior to checking for a username to allow for anonymous sessions...
725 my $session = get_session("") or die "Auth ERROR: Cannot get_session()";
726 my $sessionID = $session->id;
727 C4::Context->_new_userenv($sessionID);
728 $cookie = $query->cookie(CGISESSID => $sessionID);
729 $userid = $query->param('userid');
730 if ($cas || $userid) {
731 my $password = $query->param('password');
732 my ($return, $cardnumber);
733 if ($cas && $query->param('ticket')) {
735 ( $return, $cardnumber, $retuserid ) = checkpw( $dbh, $userid, $password, $query );
736 $userid = $retuserid;
737 $info{'invalidCasLogin'} = 1 unless ($return);
740 ( $return, $retuserid ) = checkpw( $dbh, $userid, $password, $query );
741 $userid = $retuserid if ($retuserid ne '');
744 _session_log(sprintf "%20s from %16s logged in at %30s.\n", $userid,$ENV{'REMOTE_ADDR'},(strftime '%c', localtime));
745 if ( $flags = haspermission( $userid, $flagsrequired ) ) {
749 $info{'nopermission'} = 1;
750 C4::Context->_unset_userenv($sessionID);
753 my ($borrowernumber, $firstname, $surname, $userflags,
754 $branchcode, $branchname, $branchprinter, $emailaddress);
756 if ( $return == 1 ) {
758 SELECT borrowernumber, firstname, surname, flags, borrowers.branchcode,
759 branches.branchname as branchname,
760 branches.branchprinter as branchprinter,
763 LEFT JOIN branches on borrowers.branchcode=branches.branchcode
765 my $sth = $dbh->prepare("$select where userid=?");
766 $sth->execute($userid);
767 unless ($sth->rows) {
768 $debug and print STDERR "AUTH_1: no rows for userid='$userid'\n";
769 $sth = $dbh->prepare("$select where cardnumber=?");
770 $sth->execute($cardnumber);
772 unless ($sth->rows) {
773 $debug and print STDERR "AUTH_2a: no rows for cardnumber='$cardnumber'\n";
774 $sth->execute($userid);
775 unless ($sth->rows) {
776 $debug and print STDERR "AUTH_2b: no rows for userid='$userid' AS cardnumber\n";
781 ($borrowernumber, $firstname, $surname, $userflags,
782 $branchcode, $branchname, $branchprinter, $emailaddress) = $sth->fetchrow;
783 $debug and print STDERR "AUTH_3 results: " .
784 "$cardnumber,$borrowernumber,$userid,$firstname,$surname,$userflags,$branchcode,$emailaddress\n";
786 print STDERR "AUTH_3: no results for userid='$userid', cardnumber='$cardnumber'.\n";
789 # launch a sequence to check if we have a ip for the branch, i
790 # if we have one we replace the branchcode of the userenv by the branch bound in the ip.
792 my $ip = $ENV{'REMOTE_ADDR'};
793 # if they specify at login, use that
794 if ($query->param('branch')) {
795 $branchcode = $query->param('branch');
796 $branchname = GetBranchName($branchcode);
798 my $branches = GetBranches();
799 if (C4::Context->boolean_preference('IndependantBranches') && C4::Context->boolean_preference('Autolocation')){
800 # we have to check they are coming from the right ip range
801 my $domain = $branches->{$branchcode}->{'branchip'};
802 if ($ip !~ /^$domain/){
804 $info{'wrongip'} = 1;
809 foreach my $br ( keys %$branches ) {
810 # now we work with the treatment of ip
811 my $domain = $branches->{$br}->{'branchip'};
812 if ( $domain && $ip =~ /^$domain/ ) {
813 $branchcode = $branches->{$br}->{'branchcode'};
815 # new op dev : add the branchprinter and branchname in the cookie
816 $branchprinter = $branches->{$br}->{'branchprinter'};
817 $branchname = $branches->{$br}->{'branchname'};
820 $session->param('number',$borrowernumber);
821 $session->param('id',$userid);
822 $session->param('cardnumber',$cardnumber);
823 $session->param('firstname',$firstname);
824 $session->param('surname',$surname);
825 $session->param('branch',$branchcode);
826 $session->param('branchname',$branchname);
827 $session->param('flags',$userflags);
828 $session->param('emailaddress',$emailaddress);
829 $session->param('ip',$session->remote_addr());
830 $session->param('lasttime',time());
831 $debug and printf STDERR "AUTH_4: (%s)\t%s %s - %s\n", map {$session->param($_)} qw(cardnumber firstname surname branch) ;
833 elsif ( $return == 2 ) {
834 #We suppose the user is the superlibrarian
836 $session->param('number',0);
837 $session->param('id',C4::Context->config('user'));
838 $session->param('cardnumber',C4::Context->config('user'));
839 $session->param('firstname',C4::Context->config('user'));
840 $session->param('surname',C4::Context->config('user'));
841 $session->param('branch','NO_LIBRARY_SET');
842 $session->param('branchname','NO_LIBRARY_SET');
843 $session->param('flags',1);
844 $session->param('emailaddress', C4::Context->preference('KohaAdminEmailAddress'));
845 $session->param('ip',$session->remote_addr());
846 $session->param('lasttime',time());
848 C4::Context::set_userenv(
849 $session->param('number'), $session->param('id'),
850 $session->param('cardnumber'), $session->param('firstname'),
851 $session->param('surname'), $session->param('branch'),
852 $session->param('branchname'), $session->param('flags'),
853 $session->param('emailaddress'), $session->param('branchprinter')
856 # Grab borrower's shelves and public shelves and add them to the session
857 # $row_count determines how many records are returned from the db query
858 # and the number of lists to be displayed of each type in the 'Lists' button drop down
859 my $row_count = 10; # FIXME:This probably should be a syspref
860 my ($total, $totshelves, $barshelves, $pubshelves);
861 ($barshelves, $totshelves) = C4::VirtualShelves::GetRecentShelves(1, $row_count, $borrowernumber);
862 $total->{'bartotal'} = $totshelves;
863 ($pubshelves, $totshelves) = C4::VirtualShelves::GetRecentShelves(2, $row_count, undef);
864 $total->{'pubtotal'} = $totshelves;
865 $session->param('barshelves', $barshelves);
866 $session->param('pubshelves', $pubshelves);
867 $session->param('totshelves', $total);
869 C4::Context::set_shelves_userenv('bar',$barshelves);
870 C4::Context::set_shelves_userenv('pub',$pubshelves);
871 C4::Context::set_shelves_userenv('tot',$total);
875 $info{'invalid_username_or_password'} = 1;
876 C4::Context->_unset_userenv($sessionID);
879 } # END if ( $userid = $query->param('userid') )
880 elsif ($type eq "opac") {
881 # if we are here this is an anonymous session; add public lists to it and a few other items...
882 # anonymous sessions are created only for the OPAC
883 $debug and warn "Initiating an anonymous session...";
885 # Grab the public shelves and add to the session...
886 my $row_count = 20; # FIXME:This probably should be a syspref
887 my ($total, $totshelves, $pubshelves);
888 ($pubshelves, $totshelves) = C4::VirtualShelves::GetRecentShelves(2, $row_count, undef);
889 $total->{'pubtotal'} = $totshelves;
890 $session->param('pubshelves', $pubshelves);
891 $session->param('totshelves', $total);
892 C4::Context::set_shelves_userenv('pub',$pubshelves);
893 C4::Context::set_shelves_userenv('tot',$total);
895 # setting a couple of other session vars...
896 $session->param('ip',$session->remote_addr());
897 $session->param('lasttime',time());
898 $session->param('sessiontype','anon');
900 } # END unless ($userid)
901 my $insecure = C4::Context->boolean_preference('insecure');
903 # finished authentification, now respond
904 if ( $loggedin || $authnotrequired || ( defined($insecure) && $insecure ) )
908 $cookie = $query->cookie( CGISESSID => '' );
910 return ( $userid, $cookie, $sessionID, $flags );
915 # AUTH rejected, show the login/password template, after checking the DB.
919 # get the inputs from the incoming query
921 foreach my $name ( param $query) {
922 (next) if ( $name eq 'userid' || $name eq 'password' || $name eq 'ticket' );
923 my $value = $query->param($name);
924 push @inputs, { name => $name, value => $value };
926 # get the branchloop, which we need for authentication
927 my $branches = GetBranches();
929 for my $branch_hash (sort keys %$branches) {
930 push @branch_loop, {branchcode => "$branch_hash", branchname => $branches->{$branch_hash}->{'branchname'}, };
933 my $template_name = ( $type eq 'opac' ) ? 'opac-auth.tmpl' : 'auth.tmpl';
934 my $template = gettemplate( $template_name, $type, $query );
935 $template->param(branchloop => \@branch_loop,);
936 my $checkstyle = C4::Context->preference("opaccolorstylesheet");
937 if ($checkstyle =~ /\//)
939 $template->param( opacexternalsheet => $checkstyle);
942 my $opaccolorstylesheet = C4::Context->preference("opaccolorstylesheet");
943 $template->param( opaccolorstylesheet => $opaccolorstylesheet);
948 casAuthentication => C4::Context->preference("casAuthentication"),
949 suggestion => C4::Context->preference("suggestion"),
950 virtualshelves => C4::Context->preference("virtualshelves"),
951 LibraryName => C4::Context->preference("LibraryName"),
952 opacuserlogin => C4::Context->preference("opacuserlogin"),
953 OpacNav => C4::Context->preference("OpacNav"),
954 opaccredits => C4::Context->preference("opaccredits"),
955 OpacFavicon => C4::Context->preference("OpacFavicon"),
956 opacreadinghistory => C4::Context->preference("opacreadinghistory"),
957 opacsmallimage => C4::Context->preference("opacsmallimage"),
958 opaclayoutstylesheet => C4::Context->preference("opaclayoutstylesheet"),
959 opaclanguagesdisplay => C4::Context->preference("opaclanguagesdisplay"),
960 opacuserjs => C4::Context->preference("opacuserjs"),
961 opacbookbag => "" . C4::Context->preference("opacbookbag"),
962 OpacCloud => C4::Context->preference("OpacCloud"),
963 OpacTopissue => C4::Context->preference("OpacTopissue"),
964 OpacAuthorities => C4::Context->preference("OpacAuthorities"),
965 OpacBrowser => C4::Context->preference("OpacBrowser"),
966 opacheader => C4::Context->preference("opacheader"),
967 TagsEnabled => C4::Context->preference("TagsEnabled"),
968 OPACUserCSS => C4::Context->preference("OPACUserCSS"),
969 opacstylesheet => C4::Context->preference("opacstylesheet"),
970 intranetcolorstylesheet =>
971 C4::Context->preference("intranetcolorstylesheet"),
972 intranetstylesheet => C4::Context->preference("intranetstylesheet"),
973 intranetbookbag => C4::Context->preference("intranetbookbag"),
974 IntranetNav => C4::Context->preference("IntranetNav"),
975 intranetuserjs => C4::Context->preference("intranetuserjs"),
976 IndependantBranches=> C4::Context->preference("IndependantBranches"),
977 AutoLocation => C4::Context->preference("AutoLocation"),
978 wrongip => $info{'wrongip'},
981 $template->param( OpacPublic => C4::Context->preference("OpacPublic"));
982 $template->param( loginprompt => 1 ) unless $info{'nopermission'};
986 casServerUrl => login_cas_url(),
987 invalidCasLogin => $info{'invalidCasLogin'}
991 my $self_url = $query->url( -absolute => 1 );
994 LibraryName => C4::Context->preference("LibraryName"),
996 $template->param( %info );
997 # $cookie = $query->cookie(CGISESSID => $session->id
999 print $query->header(
1000 -type => 'text/html',
1001 -charset => 'utf-8',
1008 =head2 check_api_auth
1010 ($status, $cookie, $sessionId) = check_api_auth($query, $userflags);
1012 Given a CGI query containing the parameters 'userid' and 'password' and/or a session
1013 cookie, determine if the user has the privileges specified by C<$userflags>.
1015 C<check_api_auth> is is meant for authenticating users of web services, and
1016 consequently will always return and will not attempt to redirect the user
1019 If a valid session cookie is already present, check_api_auth will return a status
1020 of "ok", the cookie, and the Koha session ID.
1022 If no session cookie is present, check_api_auth will check the 'userid' and 'password
1023 parameters and create a session cookie and Koha session if the supplied credentials
1026 Possible return values in C<$status> are:
1030 =item "ok" -- user authenticated; C<$cookie> and C<$sessionid> have valid values.
1032 =item "failed" -- credentials are not correct; C<$cookie> and C<$sessionid> are undef
1034 =item "maintenance" -- DB is in maintenance mode; no login possible at the moment
1036 =item "expired -- session cookie has expired; API user should resubmit userid and password
1042 sub check_api_auth {
1044 my $flagsrequired = shift;
1046 my $dbh = C4::Context->dbh;
1047 my $timeout = C4::Context->preference('timeout');
1048 $timeout = 600 unless $timeout;
1050 unless (C4::Context->preference('Version')) {
1051 # database has not been installed yet
1052 return ("maintenance", undef, undef);
1054 my $kohaversion=C4::Context::KOHAVERSION;
1055 $kohaversion =~ s/(.*\..*)\.(.*)\.(.*)/$1$2$3/;
1056 if (C4::Context->preference('Version') < $kohaversion) {
1057 # database in need of version update; assume that
1058 # no API should be called while databsae is in
1060 return ("maintenance", undef, undef);
1063 # FIXME -- most of what follows is a copy-and-paste
1064 # of code from checkauth. There is an obvious need
1065 # for refactoring to separate the various parts of
1066 # the authentication code, but as of 2007-11-19 this
1067 # is deferred so as to not introduce bugs into the
1068 # regular authentication code for Koha 3.0.
1070 # see if we have a valid session cookie already
1071 # however, if a userid parameter is present (i.e., from
1072 # a form submission, assume that any current cookie
1074 my $sessionID = undef;
1075 unless ($query->param('userid')) {
1076 $sessionID = $query->cookie("CGISESSID");
1079 my $session = get_session($sessionID);
1080 C4::Context->_new_userenv($sessionID);
1082 C4::Context::set_userenv(
1083 $session->param('number'), $session->param('id'),
1084 $session->param('cardnumber'), $session->param('firstname'),
1085 $session->param('surname'), $session->param('branch'),
1086 $session->param('branchname'), $session->param('flags'),
1087 $session->param('emailaddress'), $session->param('branchprinter')
1090 my $ip = $session->param('ip');
1091 my $lasttime = $session->param('lasttime');
1092 my $userid = $session->param('id');
1093 if ( $lasttime < time() - $timeout ) {
1096 C4::Context->_unset_userenv($sessionID);
1099 return ("expired", undef, undef);
1100 } elsif ( $ip ne $ENV{'REMOTE_ADDR'} ) {
1101 # IP address changed
1103 C4::Context->_unset_userenv($sessionID);
1106 return ("expired", undef, undef);
1108 my $cookie = $query->cookie( CGISESSID => $session->id );
1109 $session->param('lasttime',time());
1110 my $flags = haspermission($userid, $flagsrequired);
1112 return ("ok", $cookie, $sessionID);
1115 C4::Context->_unset_userenv($sessionID);
1118 return ("failed", undef, undef);
1122 return ("expired", undef, undef);
1126 my $userid = $query->param('userid');
1127 my $password = $query->param('password');
1128 unless ($userid and $password) {
1129 # caller did something wrong, fail the authenticateion
1130 return ("failed", undef, undef);
1132 my ($return, $cardnumber);
1133 if ($cas && $query->param('ticket')) {
1135 ( $return, $cardnumber, $retuserid ) = checkpw( $dbh, $userid, $password, $query );
1136 $userid = $retuserid;
1138 ( $return, $cardnumber ) = checkpw( $dbh, $userid, $password, $query );
1140 if ($return and haspermission( $userid, $flagsrequired)) {
1141 my $session = get_session("");
1142 return ("failed", undef, undef) unless $session;
1144 my $sessionID = $session->id;
1145 C4::Context->_new_userenv($sessionID);
1146 my $cookie = $query->cookie(CGISESSID => $sessionID);
1147 if ( $return == 1 ) {
1149 $borrowernumber, $firstname, $surname,
1150 $userflags, $branchcode, $branchname,
1151 $branchprinter, $emailaddress
1155 "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=?"
1157 $sth->execute($userid);
1159 $borrowernumber, $firstname, $surname,
1160 $userflags, $branchcode, $branchname,
1161 $branchprinter, $emailaddress
1162 ) = $sth->fetchrow if ( $sth->rows );
1164 unless ($sth->rows ) {
1165 my $sth = $dbh->prepare(
1166 "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=?"
1168 $sth->execute($cardnumber);
1170 $borrowernumber, $firstname, $surname,
1171 $userflags, $branchcode, $branchname,
1172 $branchprinter, $emailaddress
1173 ) = $sth->fetchrow if ( $sth->rows );
1175 unless ( $sth->rows ) {
1176 $sth->execute($userid);
1178 $borrowernumber, $firstname, $surname, $userflags,
1179 $branchcode, $branchname, $branchprinter, $emailaddress
1180 ) = $sth->fetchrow if ( $sth->rows );
1184 my $ip = $ENV{'REMOTE_ADDR'};
1185 # if they specify at login, use that
1186 if ($query->param('branch')) {
1187 $branchcode = $query->param('branch');
1188 $branchname = GetBranchName($branchcode);
1190 my $branches = GetBranches();
1192 foreach my $br ( keys %$branches ) {
1193 # now we work with the treatment of ip
1194 my $domain = $branches->{$br}->{'branchip'};
1195 if ( $domain && $ip =~ /^$domain/ ) {
1196 $branchcode = $branches->{$br}->{'branchcode'};
1198 # new op dev : add the branchprinter and branchname in the cookie
1199 $branchprinter = $branches->{$br}->{'branchprinter'};
1200 $branchname = $branches->{$br}->{'branchname'};
1203 $session->param('number',$borrowernumber);
1204 $session->param('id',$userid);
1205 $session->param('cardnumber',$cardnumber);
1206 $session->param('firstname',$firstname);
1207 $session->param('surname',$surname);
1208 $session->param('branch',$branchcode);
1209 $session->param('branchname',$branchname);
1210 $session->param('flags',$userflags);
1211 $session->param('emailaddress',$emailaddress);
1212 $session->param('ip',$session->remote_addr());
1213 $session->param('lasttime',time());
1214 } elsif ( $return == 2 ) {
1215 #We suppose the user is the superlibrarian
1216 $session->param('number',0);
1217 $session->param('id',C4::Context->config('user'));
1218 $session->param('cardnumber',C4::Context->config('user'));
1219 $session->param('firstname',C4::Context->config('user'));
1220 $session->param('surname',C4::Context->config('user'));
1221 $session->param('branch','NO_LIBRARY_SET');
1222 $session->param('branchname','NO_LIBRARY_SET');
1223 $session->param('flags',1);
1224 $session->param('emailaddress', C4::Context->preference('KohaAdminEmailAddress'));
1225 $session->param('ip',$session->remote_addr());
1226 $session->param('lasttime',time());
1228 C4::Context::set_userenv(
1229 $session->param('number'), $session->param('id'),
1230 $session->param('cardnumber'), $session->param('firstname'),
1231 $session->param('surname'), $session->param('branch'),
1232 $session->param('branchname'), $session->param('flags'),
1233 $session->param('emailaddress'), $session->param('branchprinter')
1235 return ("ok", $cookie, $sessionID);
1237 return ("failed", undef, undef);
1242 =head2 check_cookie_auth
1244 ($status, $sessionId) = check_api_auth($cookie, $userflags);
1246 Given a CGISESSID cookie set during a previous login to Koha, determine
1247 if the user has the privileges specified by C<$userflags>.
1249 C<check_cookie_auth> is meant for authenticating special services
1250 such as tools/upload-file.pl that are invoked by other pages that
1251 have been authenticated in the usual way.
1253 Possible return values in C<$status> are:
1257 =item "ok" -- user authenticated; C<$sessionID> have valid values.
1259 =item "failed" -- credentials are not correct; C<$sessionid> are undef
1261 =item "maintenance" -- DB is in maintenance mode; no login possible at the moment
1263 =item "expired -- session cookie has expired; API user should resubmit userid and password
1269 sub check_cookie_auth {
1271 my $flagsrequired = shift;
1273 my $dbh = C4::Context->dbh;
1274 my $timeout = C4::Context->preference('timeout');
1275 $timeout = 600 unless $timeout;
1277 unless (C4::Context->preference('Version')) {
1278 # database has not been installed yet
1279 return ("maintenance", undef);
1281 my $kohaversion=C4::Context::KOHAVERSION;
1282 $kohaversion =~ s/(.*\..*)\.(.*)\.(.*)/$1$2$3/;
1283 if (C4::Context->preference('Version') < $kohaversion) {
1284 # database in need of version update; assume that
1285 # no API should be called while databsae is in
1287 return ("maintenance", undef);
1290 # FIXME -- most of what follows is a copy-and-paste
1291 # of code from checkauth. There is an obvious need
1292 # for refactoring to separate the various parts of
1293 # the authentication code, but as of 2007-11-23 this
1294 # is deferred so as to not introduce bugs into the
1295 # regular authentication code for Koha 3.0.
1297 # see if we have a valid session cookie already
1298 # however, if a userid parameter is present (i.e., from
1299 # a form submission, assume that any current cookie
1301 unless (defined $cookie and $cookie) {
1302 return ("failed", undef);
1304 my $sessionID = $cookie;
1305 my $session = get_session($sessionID);
1306 C4::Context->_new_userenv($sessionID);
1308 C4::Context::set_userenv(
1309 $session->param('number'), $session->param('id'),
1310 $session->param('cardnumber'), $session->param('firstname'),
1311 $session->param('surname'), $session->param('branch'),
1312 $session->param('branchname'), $session->param('flags'),
1313 $session->param('emailaddress'), $session->param('branchprinter')
1316 my $ip = $session->param('ip');
1317 my $lasttime = $session->param('lasttime');
1318 my $userid = $session->param('id');
1319 if ( $lasttime < time() - $timeout ) {
1322 C4::Context->_unset_userenv($sessionID);
1325 return ("expired", undef);
1326 } elsif ( $ip ne $ENV{'REMOTE_ADDR'} ) {
1327 # IP address changed
1329 C4::Context->_unset_userenv($sessionID);
1332 return ("expired", undef);
1334 $session->param('lasttime',time());
1335 my $flags = haspermission($userid, $flagsrequired);
1337 return ("ok", $sessionID);
1340 C4::Context->_unset_userenv($sessionID);
1343 return ("failed", undef);
1347 return ("expired", undef);
1354 my $session = get_session($sessionID);
1356 Given a session ID, retrieve the CGI::Session object used to store
1357 the session's state. The session object can be used to store
1358 data that needs to be accessed by different scripts during a
1361 If the C<$sessionID> parameter is an empty string, a new session
1367 my $sessionID = shift;
1368 my $storage_method = C4::Context->preference('SessionStorage');
1369 my $dbh = C4::Context->dbh;
1371 if ($storage_method eq 'mysql'){
1372 $session = new CGI::Session("driver:MySQL;serializer:yaml;id:md5", $sessionID, {Handle=>$dbh});
1374 elsif ($storage_method eq 'Pg') {
1375 $session = new CGI::Session("driver:PostgreSQL;serializer:yaml;id:md5", $sessionID, {Handle=>$dbh});
1377 elsif ($storage_method eq 'memcached' && $servers){
1378 $session = new CGI::Session("driver:memcached;serializer:yaml;id:md5", $sessionID, { Memcached => $memcached } );
1381 # catch all defaults to tmp should work on all systems
1382 $session = new CGI::Session("driver:File;serializer:yaml;id:md5", $sessionID, {Directory=>'/tmp'});
1389 my ( $dbh, $userid, $password, $query ) = @_;
1391 $debug and print "## checkpw - checking LDAP\n";
1392 my ($retval,$retcard) = checkpw_ldap(@_); # EXTERNAL AUTH
1393 ($retval) and return ($retval,$retcard);
1396 if ($cas && $query->param('ticket')) {
1397 $debug and print STDERR "## checkpw - checking CAS\n";
1398 # In case of a CAS authentication, we use the ticket instead of the password
1399 my $ticket = $query->param('ticket');
1400 my ($retval,$retcard,$retuserid) = checkpw_cas($dbh, $ticket, $query); # EXTERNAL AUTH
1401 ($retval) and return ($retval,$retcard,$retuserid);
1408 "select password,cardnumber,borrowernumber,userid,firstname,surname,branchcode,flags from borrowers where userid=?"
1410 $sth->execute($userid);
1412 my ( $md5password, $cardnumber, $borrowernumber, $userid, $firstname,
1413 $surname, $branchcode, $flags )
1415 if ( md5_base64($password) eq $md5password and $md5password ne "!") {
1417 C4::Context->set_userenv( "$borrowernumber", $userid, $cardnumber,
1418 $firstname, $surname, $branchcode, $flags );
1424 "select password,cardnumber,borrowernumber,userid, firstname,surname,branchcode,flags from borrowers where cardnumber=?"
1426 $sth->execute($userid);
1428 my ( $md5password, $cardnumber, $borrowernumber, $userid, $firstname,
1429 $surname, $branchcode, $flags )
1431 if ( md5_base64($password) eq $md5password ) {
1433 C4::Context->set_userenv( $borrowernumber, $userid, $cardnumber,
1434 $firstname, $surname, $branchcode, $flags );
1438 if ( $userid && $userid eq C4::Context->config('user')
1439 && "$password" eq C4::Context->config('pass') )
1442 # Koha superuser account
1443 # C4::Context->set_userenv(0,0,C4::Context->config('user'),C4::Context->config('user'),C4::Context->config('user'),"",1);
1446 if ( $userid && $userid eq 'demo'
1447 && "$password" eq 'demo'
1448 && C4::Context->config('demo') )
1451 # DEMO => the demo user is allowed to do everything (if demo set to 1 in koha.conf
1452 # some features won't be effective : modify systempref, modify MARC structure,
1460 my $authflags = getuserflags($flags, $userid, [$dbh]);
1462 Translates integer flags into permissions strings hash.
1464 C<$flags> is the integer userflags value ( borrowers.userflags )
1465 C<$userid> is the members.userid, used for building subpermissions
1466 C<$authflags> is a hashref of permissions
1473 my $dbh = @_ ? shift : C4::Context->dbh;
1475 $flags = 0 unless $flags;
1476 my $sth = $dbh->prepare("SELECT bit, flag, defaulton FROM userflags");
1479 while ( my ( $bit, $flag, $defaulton ) = $sth->fetchrow ) {
1480 if ( ( $flags & ( 2**$bit ) ) || $defaulton ) {
1481 $userflags->{$flag} = 1;
1484 $userflags->{$flag} = 0;
1488 # get subpermissions and merge with top-level permissions
1489 my $user_subperms = get_user_subpermissions($userid);
1490 foreach my $module (keys %$user_subperms) {
1491 next if $userflags->{$module} == 1; # user already has permission for everything in this module
1492 $userflags->{$module} = $user_subperms->{$module};
1498 =head2 get_user_subpermissions
1500 $user_perm_hashref = get_user_subpermissions($userid);
1502 Given the userid (note, not the borrowernumber) of a staff user,
1503 return a hashref of hashrefs of the specific subpermissions
1504 accorded to the user. An example return is
1508 export_catalog => 1,
1509 import_patrons => 1,
1513 The top-level hash-key is a module or function code from
1514 userflags.flag, while the second-level key is a code
1517 The results of this function do not give a complete picture
1518 of the functions that a staff user can access; it is also
1519 necessary to check borrowers.flags.
1523 sub get_user_subpermissions {
1526 my $dbh = C4::Context->dbh;
1527 my $sth = $dbh->prepare("SELECT flag, user_permissions.code
1528 FROM user_permissions
1529 JOIN permissions USING (module_bit, code)
1530 JOIN userflags ON (module_bit = bit)
1531 JOIN borrowers USING (borrowernumber)
1533 $sth->execute($userid);
1535 my $user_perms = {};
1536 while (my $perm = $sth->fetchrow_hashref) {
1537 $user_perms->{$perm->{'flag'}}->{$perm->{'code'}} = 1;
1542 =head2 get_all_subpermissions
1544 my $perm_hashref = get_all_subpermissions();
1546 Returns a hashref of hashrefs defining all specific
1547 permissions currently defined. The return value
1548 has the same structure as that of C<get_user_subpermissions>,
1549 except that the innermost hash value is the description
1550 of the subpermission.
1554 sub get_all_subpermissions {
1555 my $dbh = C4::Context->dbh;
1556 my $sth = $dbh->prepare("SELECT flag, code, description
1558 JOIN userflags ON (module_bit = bit)");
1562 while (my $perm = $sth->fetchrow_hashref) {
1563 $all_perms->{$perm->{'flag'}}->{$perm->{'code'}} = $perm->{'description'};
1568 =head2 haspermission
1570 $flags = ($userid, $flagsrequired);
1572 C<$userid> the userid of the member
1573 C<$flags> is a hashref of required flags like C<$borrower-<{authflags}>
1575 Returns member's flags or 0 if a permission is not met.
1580 my ($userid, $flagsrequired) = @_;
1581 my $sth = C4::Context->dbh->prepare("SELECT flags FROM borrowers WHERE userid=?");
1582 $sth->execute($userid);
1583 my $flags = getuserflags($sth->fetchrow(), $userid);
1584 if ( $userid eq C4::Context->config('user') ) {
1585 # Super User Account from /etc/koha.conf
1586 $flags->{'superlibrarian'} = 1;
1588 elsif ( $userid eq 'demo' && C4::Context->config('demo') ) {
1589 # Demo user that can do "anything" (demo=1 in /etc/koha.conf)
1590 $flags->{'superlibrarian'} = 1;
1593 return $flags if $flags->{superlibrarian};
1595 foreach my $module ( keys %$flagsrequired ) {
1596 my $subperm = $flagsrequired->{$module};
1597 if ($subperm eq '*') {
1598 return 0 unless ( $flags->{$module} == 1 or ref($flags->{$module}) );
1600 return 0 unless ( $flags->{$module} == 1 or
1601 ( ref($flags->{$module}) and
1602 exists $flags->{$module}->{$subperm} and
1603 $flags->{$module}->{$subperm} == 1
1609 #FIXME - This fcn should return the failed permission so a suitable error msg can be delivered.
1613 sub getborrowernumber {
1615 my $userenv = C4::Context->userenv;
1616 if ( defined( $userenv ) && ref( $userenv ) eq 'HASH' && $userenv->{number} ) {
1617 return $userenv->{number};
1619 my $dbh = C4::Context->dbh;
1620 for my $field ( 'userid', 'cardnumber' ) {
1622 $dbh->prepare("select borrowernumber from borrowers where $field=?");
1623 $sth->execute($userid);
1625 my ($bnumber) = $sth->fetchrow;
1632 END { } # module clean-up code here (global destructor)