Bug 6825 OpacNav menu hide user menu on opac-user.pl page
[koha.git] / C4 / Auth.pm
blob54fb74c5a4766dfd300fcba85d2c4817c24bd254
1 package C4::Auth;
3 # Copyright 2000-2002 Katipo Communications
5 # This file is part of Koha.
7 # Koha is free software; you can redistribute it and/or modify it under the
8 # terms of the GNU General Public License as published by the Free Software
9 # Foundation; either version 2 of the License, or (at your option) any later
10 # version.
12 # Koha is distributed in the hope that it will be useful, but WITHOUT ANY
13 # WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
14 # A PARTICULAR PURPOSE. See the GNU General Public License for more details.
16 # You should have received a copy of the GNU General Public License along
17 # with Koha; if not, write to the Free Software Foundation, Inc.,
18 # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
20 use strict;
21 #use warnings; FIXME - Bug 2505
22 use Digest::MD5 qw(md5_base64);
23 use Storable qw(thaw freeze);
24 use URI::Escape;
25 use CGI::Session;
27 require Exporter;
28 use C4::Context;
29 use C4::Templates; # to get the template
30 use C4::Members;
31 use C4::Koha;
32 use C4::Branch; # GetBranches
33 use C4::VirtualShelves;
34 use POSIX qw/strftime/;
35 use List::MoreUtils qw/ any /;
37 # use utf8;
38 use vars qw($VERSION @ISA @EXPORT @EXPORT_OK %EXPORT_TAGS $debug $ldap $cas $caslogout $servers $memcached);
40 BEGIN {
41 sub psgi_env { any { /^psgi\./ } keys %ENV }
42 sub safe_exit {
43 if ( psgi_env ) { die 'psgi:exit' }
44 else { exit }
47 $VERSION = 3.02; # set version for version checking
48 $debug = $ENV{DEBUG};
49 @ISA = qw(Exporter);
50 @EXPORT = qw(&checkauth &get_template_and_user &haspermission &get_user_subpermissions);
51 @EXPORT_OK = qw(&check_api_auth &get_session &check_cookie_auth &checkpw &get_all_subpermissions &get_user_subpermissions);
52 %EXPORT_TAGS = ( EditPermissions => [qw(get_all_subpermissions get_user_subpermissions)] );
53 $ldap = C4::Context->config('useldapserver') || 0;
54 $cas = C4::Context->preference('casAuthentication');
55 $caslogout = C4::Context->preference('casLogout');
56 require C4::Auth_with_cas; # no import
57 if ($ldap) {
58 require C4::Auth_with_ldap;
59 import C4::Auth_with_ldap qw(checkpw_ldap);
61 if ($cas) {
62 import C4::Auth_with_cas qw(check_api_auth_cas checkpw_cas login_cas logout_cas login_cas_url);
64 $servers = C4::Context->config('memcached_servers');
65 if ($servers) {
66 require Cache::Memcached;
67 $memcached = Cache::Memcached->new({
68 servers => [ $servers ],
69 debug => 0,
70 compress_threshold => 10_000,
71 namespace => C4::Context->config('memcached_namespace') || 'koha',
72 });
76 =head1 NAME
78 C4::Auth - Authenticates Koha users
80 =head1 SYNOPSIS
82 use CGI;
83 use C4::Auth;
84 use C4::Output;
86 my $query = new CGI;
88 my ($template, $borrowernumber, $cookie)
89 = get_template_and_user(
91 template_name => "opac-main.tmpl",
92 query => $query,
93 type => "opac",
94 authnotrequired => 1,
95 flagsrequired => {borrow => 1, catalogue => '*', tools => 'import_patrons' },
99 output_html_with_http_headers $query, $cookie, $template->output;
101 =head1 DESCRIPTION
103 The main function of this module is to provide
104 authentification. However the get_template_and_user function has
105 been provided so that a users login information is passed along
106 automatically. This gets loaded into the template.
108 =head1 FUNCTIONS
110 =head2 get_template_and_user
112 my ($template, $borrowernumber, $cookie)
113 = get_template_and_user(
115 template_name => "opac-main.tmpl",
116 query => $query,
117 type => "opac",
118 authnotrequired => 1,
119 flagsrequired => {borrow => 1, catalogue => '*', tools => 'import_patrons' },
123 This call passes the C<query>, C<flagsrequired> and C<authnotrequired>
124 to C<&checkauth> (in this module) to perform authentification.
125 See C<&checkauth> for an explanation of these parameters.
127 The C<template_name> is then used to find the correct template for
128 the page. The authenticated users details are loaded onto the
129 template in the HTML::Template LOOP variable C<USER_INFO>. Also the
130 C<sessionID> is passed to the template. This can be used in templates
131 if cookies are disabled. It needs to be put as and input to every
132 authenticated page.
134 More information on the C<gettemplate> sub can be found in the
135 Output.pm module.
137 =cut
139 my $SEARCH_HISTORY_INSERT_SQL =<<EOQ;
140 INSERT INTO search_history(userid, sessionid, query_desc, query_cgi, total, time )
141 VALUES ( ?, ?, ?, ?, ?, FROM_UNIXTIME(?))
143 sub get_template_and_user {
144 my $in = shift;
145 my $template =
146 C4::Templates::gettemplate( $in->{'template_name'}, $in->{'type'}, $in->{'query'} );
147 my ( $user, $cookie, $sessionID, $flags );
148 if ( $in->{'template_name'} !~m/maintenance/ ) {
149 ( $user, $cookie, $sessionID, $flags ) = checkauth(
150 $in->{'query'},
151 $in->{'authnotrequired'},
152 $in->{'flagsrequired'},
153 $in->{'type'}
157 my $borrowernumber;
158 my $insecure = C4::Context->preference('insecure');
159 if ($user or $insecure) {
161 # load the template variables for stylesheets and JavaScript
162 $template->param( css_libs => $in->{'css_libs'} );
163 $template->param( css_module => $in->{'css_module'} );
164 $template->param( css_page => $in->{'css_page'} );
165 $template->param( css_widgets => $in->{'css_widgets'} );
167 $template->param( js_libs => $in->{'js_libs'} );
168 $template->param( js_module => $in->{'js_module'} );
169 $template->param( js_page => $in->{'js_page'} );
170 $template->param( js_widgets => $in->{'js_widgets'} );
172 # user info
173 $template->param( loggedinusername => $user );
174 $template->param( sessionID => $sessionID );
176 my ($total, $pubshelves, $barshelves) = C4::Context->get_shelves_userenv();
177 if (defined($pubshelves)) {
178 $template->param( pubshelves => scalar @{$pubshelves},
179 pubshelvesloop => $pubshelves,
181 $template->param( pubtotal => $total->{'pubtotal'}, ) if ($total->{'pubtotal'} > scalar @{$pubshelves});
183 if (defined($barshelves)) {
184 $template->param( barshelves => scalar @{$barshelves},
185 barshelvesloop => $barshelves,
187 $template->param( bartotal => $total->{'bartotal'}, ) if ($total->{'bartotal'} > scalar @{$barshelves});
190 $borrowernumber = getborrowernumber($user) if defined($user);
192 my ( $borr ) = GetMemberDetails( $borrowernumber );
193 my @bordat;
194 $bordat[0] = $borr;
195 $template->param( "USER_INFO" => \@bordat );
197 my $all_perms = get_all_subpermissions();
199 my @flagroots = qw(circulate catalogue parameters borrowers permissions reserveforothers borrow
200 editcatalogue updatecharges management tools editauthorities serials reports acquisition);
201 # We are going to use the $flags returned by checkauth
202 # to create the template's parameters that will indicate
203 # which menus the user can access.
204 if (( $flags && $flags->{superlibrarian}==1) or $insecure==1) {
205 $template->param( CAN_user_circulate => 1 );
206 $template->param( CAN_user_catalogue => 1 );
207 $template->param( CAN_user_parameters => 1 );
208 $template->param( CAN_user_borrowers => 1 );
209 $template->param( CAN_user_permissions => 1 );
210 $template->param( CAN_user_reserveforothers => 1 );
211 $template->param( CAN_user_borrow => 1 );
212 $template->param( CAN_user_editcatalogue => 1 );
213 $template->param( CAN_user_updatecharges => 1 );
214 $template->param( CAN_user_acquisition => 1 );
215 $template->param( CAN_user_management => 1 );
216 $template->param( CAN_user_tools => 1 );
217 $template->param( CAN_user_editauthorities => 1 );
218 $template->param( CAN_user_serials => 1 );
219 $template->param( CAN_user_reports => 1 );
220 $template->param( CAN_user_staffaccess => 1 );
221 foreach my $module (keys %$all_perms) {
222 foreach my $subperm (keys %{ $all_perms->{$module} }) {
223 $template->param( "CAN_user_${module}_${subperm}" => 1 );
228 if ( $flags ) {
229 foreach my $module (keys %$all_perms) {
230 if ( $flags->{$module} == 1) {
231 foreach my $subperm (keys %{ $all_perms->{$module} }) {
232 $template->param( "CAN_user_${module}_${subperm}" => 1 );
234 } elsif ( ref($flags->{$module}) ) {
235 foreach my $subperm (keys %{ $flags->{$module} } ) {
236 $template->param( "CAN_user_${module}_${subperm}" => 1 );
242 if ($flags) {
243 foreach my $module (keys %$flags) {
244 if ( $flags->{$module} == 1 or ref($flags->{$module}) ) {
245 $template->param( "CAN_user_$module" => 1 );
246 if ($module eq "parameters") {
247 $template->param( CAN_user_management => 1 );
252 # Logged-in opac search history
253 # If the requested template is an opac one and opac search history is enabled
254 if ($in->{type} eq 'opac' && C4::Context->preference('EnableOpacSearchHistory')) {
255 my $dbh = C4::Context->dbh;
256 my $query = "SELECT COUNT(*) FROM search_history WHERE userid=?";
257 my $sth = $dbh->prepare($query);
258 $sth->execute($borrowernumber);
260 # If at least one search has already been performed
261 if ($sth->fetchrow_array > 0) {
262 # We show the link in opac
263 $template->param(ShowOpacRecentSearchLink => 1);
266 # And if there's a cookie with searches performed when the user was not logged in,
267 # we add them to the logged-in search history
268 my $searchcookie = $in->{'query'}->cookie('KohaOpacRecentSearches');
269 if ($searchcookie){
270 $searchcookie = uri_unescape($searchcookie);
271 my @recentSearches = @{thaw($searchcookie) || []};
272 if (@recentSearches) {
273 my $sth = $dbh->prepare($SEARCH_HISTORY_INSERT_SQL);
274 $sth->execute( $borrowernumber,
275 $in->{'query'}->cookie("CGISESSID"),
276 $_->{'query_desc'},
277 $_->{'query_cgi'},
278 $_->{'total'},
279 $_->{'time'},
280 ) foreach @recentSearches;
282 # And then, delete the cookie's content
283 my $newsearchcookie = $in->{'query'}->cookie(
284 -name => 'KohaOpacRecentSearches',
285 -value => freeze([]),
286 -expires => ''
288 $cookie = [$cookie, $newsearchcookie];
293 else { # if this is an anonymous session, setup to display public lists...
295 # load the template variables for stylesheets and JavaScript
296 $template->param( css_libs => $in->{'css_libs'} );
297 $template->param( css_module => $in->{'css_module'} );
298 $template->param( css_page => $in->{'css_page'} );
299 $template->param( css_widgets => $in->{'css_widgets'} );
301 $template->param( js_libs => $in->{'js_libs'} );
302 $template->param( js_module => $in->{'js_module'} );
303 $template->param( js_page => $in->{'js_page'} );
304 $template->param( js_widgets => $in->{'js_widgets'} );
306 $template->param( sessionID => $sessionID );
308 my ($total, $pubshelves) = C4::Context->get_shelves_userenv(); # an anonymous user has no 'barshelves'...
309 if (defined $pubshelves) {
310 $template->param( pubshelves => scalar @{$pubshelves},
311 pubshelvesloop => $pubshelves,
313 $template->param( pubtotal => $total->{'pubtotal'}, ) if ($total->{'pubtotal'} > scalar @{$pubshelves});
317 # Anonymous opac search history
318 # If opac search history is enabled and at least one search has already been performed
319 if (C4::Context->preference('EnableOpacSearchHistory')) {
320 my $searchcookie = $in->{'query'}->cookie('KohaOpacRecentSearches');
321 if ($searchcookie){
322 $searchcookie = uri_unescape($searchcookie);
323 my @recentSearches = @{thaw($searchcookie) || []};
324 # We show the link in opac
325 if (@recentSearches) {
326 $template->param(ShowOpacRecentSearchLink => 1);
331 if(C4::Context->preference('dateformat')){
332 if(C4::Context->preference('dateformat') eq "metric"){
333 $template->param(dateformat_metric => 1);
334 } elsif(C4::Context->preference('dateformat') eq "us"){
335 $template->param(dateformat_us => 1);
336 } else {
337 $template->param(dateformat_iso => 1);
339 } else {
340 $template->param(dateformat_iso => 1);
343 # these template parameters are set the same regardless of $in->{'type'}
344 $template->param(
345 "BiblioDefaultView".C4::Context->preference("BiblioDefaultView") => 1,
346 EnhancedMessagingPreferences => C4::Context->preference('EnhancedMessagingPreferences'),
347 GoogleJackets => C4::Context->preference("GoogleJackets"),
348 OpenLibraryCovers => C4::Context->preference("OpenLibraryCovers"),
349 KohaAdminEmailAddress => "" . C4::Context->preference("KohaAdminEmailAddress"),
350 LoginBranchcode => (C4::Context->userenv?C4::Context->userenv->{"branch"}:"insecure"),
351 LoginFirstname => (C4::Context->userenv?C4::Context->userenv->{"firstname"}:"Bel"),
352 LoginSurname => C4::Context->userenv?C4::Context->userenv->{"surname"}:"Inconnu",
353 TagsEnabled => C4::Context->preference("TagsEnabled"),
354 hide_marc => C4::Context->preference("hide_marc"),
355 item_level_itypes => C4::Context->preference('item-level_itypes'),
356 patronimages => C4::Context->preference("patronimages"),
357 singleBranchMode => C4::Context->preference("singleBranchMode"),
358 XSLTDetailsDisplay => C4::Context->preference("XSLTDetailsDisplay"),
359 XSLTResultsDisplay => C4::Context->preference("XSLTResultsDisplay"),
360 using_https => $in->{'query'}->https() ? 1 : 0,
361 noItemTypeImages => C4::Context->preference("noItemTypeImages"),
364 if ( $in->{'type'} eq "intranet" ) {
365 $template->param(
366 AmazonContent => C4::Context->preference("AmazonContent"),
367 AmazonCoverImages => C4::Context->preference("AmazonCoverImages"),
368 AmazonEnabled => C4::Context->preference("AmazonEnabled"),
369 AmazonSimilarItems => C4::Context->preference("AmazonSimilarItems"),
370 AutoLocation => C4::Context->preference("AutoLocation"),
371 "BiblioDefaultView".C4::Context->preference("IntranetBiblioDefaultView") => 1,
372 CircAutocompl => C4::Context->preference("CircAutocompl"),
373 FRBRizeEditions => C4::Context->preference("FRBRizeEditions"),
374 IndependantBranches => C4::Context->preference("IndependantBranches"),
375 IntranetNav => C4::Context->preference("IntranetNav"),
376 IntranetmainUserblock => C4::Context->preference("IntranetmainUserblock"),
377 LibraryName => C4::Context->preference("LibraryName"),
378 LoginBranchname => (C4::Context->userenv?C4::Context->userenv->{"branchname"}:"insecure"),
379 advancedMARCEditor => C4::Context->preference("advancedMARCEditor"),
380 canreservefromotherbranches => C4::Context->preference('canreservefromotherbranches'),
381 intranetcolorstylesheet => C4::Context->preference("intranetcolorstylesheet"),
382 IntranetFavicon => C4::Context->preference("IntranetFavicon"),
383 intranetreadinghistory => C4::Context->preference("intranetreadinghistory"),
384 intranetstylesheet => C4::Context->preference("intranetstylesheet"),
385 IntranetUserCSS => C4::Context->preference("IntranetUserCSS"),
386 intranetuserjs => C4::Context->preference("intranetuserjs"),
387 intranetbookbag => C4::Context->preference("intranetbookbag"),
388 suggestion => C4::Context->preference("suggestion"),
389 virtualshelves => C4::Context->preference("virtualshelves"),
390 StaffSerialIssueDisplayCount => C4::Context->preference("StaffSerialIssueDisplayCount"),
391 NoZebra => C4::Context->preference('NoZebra'),
392 EasyAnalyticalRecords => C4::Context->preference('EasyAnalyticalRecords'),
395 else {
396 warn "template type should be OPAC, here it is=[" . $in->{'type'} . "]" unless ( $in->{'type'} eq 'opac' );
397 #TODO : replace LibraryName syspref with 'system name', and remove this html processing
398 my $LibraryNameTitle = C4::Context->preference("LibraryName");
399 $LibraryNameTitle =~ s/<(?:\/?)(?:br|p)\s*(?:\/?)>/ /sgi;
400 $LibraryNameTitle =~ s/<(?:[^<>'"]|'(?:[^']*)'|"(?:[^"]*)")*>//sg;
401 # variables passed from CGI: opac_css_override and opac_search_limits.
402 my $opac_search_limit = $ENV{'OPAC_SEARCH_LIMIT'};
403 my $opac_limit_override = $ENV{'OPAC_LIMIT_OVERRIDE'};
404 my $opac_name = '';
405 if (($opac_search_limit =~ /branch:(\w+)/ && $opac_limit_override) || $in->{'query'}->param('limit') =~ /branch:(\w+)/){
406 $opac_name = $1; # opac_search_limit is a branch, so we use it.
407 } elsif (C4::Context->preference("SearchMyLibraryFirst") && C4::Context->userenv && C4::Context->userenv->{'branch'}) {
408 $opac_name = C4::Context->userenv->{'branch'};
410 my $checkstyle = C4::Context->preference("opaccolorstylesheet");
411 if ($checkstyle =~ /http/)
413 $template->param( opacexternalsheet => $checkstyle);
414 } else
416 my $opaccolorstylesheet = C4::Context->preference("opaccolorstylesheet");
417 $template->param( opaccolorstylesheet => $opaccolorstylesheet);
419 $template->param(
420 AmazonContent => "" . C4::Context->preference("AmazonContent"),
421 AnonSuggestions => "" . C4::Context->preference("AnonSuggestions"),
422 AuthorisedValueImages => C4::Context->preference("AuthorisedValueImages"),
423 BranchesLoop => GetBranchesLoop($opac_name),
424 LibraryName => "" . C4::Context->preference("LibraryName"),
425 LibraryNameTitle => "" . $LibraryNameTitle,
426 LoginBranchname => C4::Context->userenv?C4::Context->userenv->{"branchname"}:"",
427 OPACAmazonEnabled => C4::Context->preference("OPACAmazonEnabled"),
428 OPACAmazonSimilarItems => C4::Context->preference("OPACAmazonSimilarItems"),
429 OPACAmazonCoverImages => C4::Context->preference("OPACAmazonCoverImages"),
430 OPACAmazonReviews => C4::Context->preference("OPACAmazonReviews"),
431 OPACFRBRizeEditions => C4::Context->preference("OPACFRBRizeEditions"),
432 OpacHighlightedWords => C4::Context->preference("OpacHighlightedWords"),
433 OPACItemHolds => C4::Context->preference("OPACItemHolds"),
434 OPACShelfBrowser => "". C4::Context->preference("OPACShelfBrowser"),
435 OPACURLOpenInNewWindow => "" . C4::Context->preference("OPACURLOpenInNewWindow"),
436 OPACUserCSS => "". C4::Context->preference("OPACUserCSS"),
437 OPACViewOthersSuggestions => "" . C4::Context->preference("OPACViewOthersSuggestions"),
438 OpacAuthorities => C4::Context->preference("OpacAuthorities"),
439 OPACBaseURL => ($in->{'query'}->https() ? "https://" : "http://") . $ENV{'SERVER_NAME'} .
440 ($ENV{'SERVER_PORT'} eq ($in->{'query'}->https() ? "443" : "80") ? '' : ":$ENV{'SERVER_PORT'}"),
441 opac_css_override => $ENV{'OPAC_CSS_OVERRIDE'},
442 opac_search_limit => $opac_search_limit,
443 opac_limit_override => $opac_limit_override,
444 OpacBrowser => C4::Context->preference("OpacBrowser"),
445 OpacCloud => C4::Context->preference("OpacCloud"),
446 OpacKohaUrl => C4::Context->preference("OpacKohaUrl"),
447 OpacMainUserBlock => "" . C4::Context->preference("OpacMainUserBlock"),
448 OpacNav => "" . C4::Context->preference("OpacNav"),
449 OpacNavBottom => "" . C4::Context->preference("OpacNavBottom"),
450 OpacPasswordChange => C4::Context->preference("OpacPasswordChange"),
451 OPACPatronDetails => C4::Context->preference("OPACPatronDetails"),
452 OPACPrivacy => C4::Context->preference("OPACPrivacy"),
453 OPACFinesTab => C4::Context->preference("OPACFinesTab"),
454 OpacTopissue => C4::Context->preference("OpacTopissue"),
455 RequestOnOpac => C4::Context->preference("RequestOnOpac"),
456 'Version' => C4::Context->preference('Version'),
457 hidelostitems => C4::Context->preference("hidelostitems"),
458 mylibraryfirst => (C4::Context->preference("SearchMyLibraryFirst") && C4::Context->userenv) ? C4::Context->userenv->{'branch'} : '',
459 opaclayoutstylesheet => "" . C4::Context->preference("opaclayoutstylesheet"),
460 opacstylesheet => "" . C4::Context->preference("opacstylesheet"),
461 opacbookbag => "" . C4::Context->preference("opacbookbag"),
462 opaccredits => "" . C4::Context->preference("opaccredits"),
463 OpacFavicon => C4::Context->preference("OpacFavicon"),
464 opacheader => "" . C4::Context->preference("opacheader"),
465 opaclanguagesdisplay => "" . C4::Context->preference("opaclanguagesdisplay"),
466 opacreadinghistory => C4::Context->preference("opacreadinghistory"),
467 opacsmallimage => "" . C4::Context->preference("opacsmallimage"),
468 opacuserjs => C4::Context->preference("opacuserjs"),
469 opacuserlogin => "" . C4::Context->preference("opacuserlogin"),
470 reviewson => C4::Context->preference("reviewson"),
471 ShowReviewer => C4::Context->preference("ShowReviewer"),
472 ShowReviewerPhoto => C4::Context->preference("ShowReviewerPhoto"),
473 suggestion => "" . C4::Context->preference("suggestion"),
474 virtualshelves => "" . C4::Context->preference("virtualshelves"),
475 OPACSerialIssueDisplayCount => C4::Context->preference("OPACSerialIssueDisplayCount"),
476 OpacAddMastheadLibraryPulldown => C4::Context->preference("OpacAddMastheadLibraryPulldown"),
477 OPACXSLTDetailsDisplay => C4::Context->preference("OPACXSLTDetailsDisplay"),
478 OPACXSLTResultsDisplay => C4::Context->preference("OPACXSLTResultsDisplay"),
479 SyndeticsClientCode => C4::Context->preference("SyndeticsClientCode"),
480 SyndeticsEnabled => C4::Context->preference("SyndeticsEnabled"),
481 SyndeticsCoverImages => C4::Context->preference("SyndeticsCoverImages"),
482 SyndeticsTOC => C4::Context->preference("SyndeticsTOC"),
483 SyndeticsSummary => C4::Context->preference("SyndeticsSummary"),
484 SyndeticsEditions => C4::Context->preference("SyndeticsEditions"),
485 SyndeticsExcerpt => C4::Context->preference("SyndeticsExcerpt"),
486 SyndeticsReviews => C4::Context->preference("SyndeticsReviews"),
487 SyndeticsAuthorNotes => C4::Context->preference("SyndeticsAuthorNotes"),
488 SyndeticsAwards => C4::Context->preference("SyndeticsAwards"),
489 SyndeticsSeries => C4::Context->preference("SyndeticsSeries"),
490 SyndeticsCoverImageSize => C4::Context->preference("SyndeticsCoverImageSize"),
493 $template->param(OpacPublic => '1') if ($user || C4::Context->preference("OpacPublic"));
495 $template->param(listloop=>[{shelfname=>"Freelist", shelfnumber=>110}]);
496 return ( $template, $borrowernumber, $cookie, $flags);
499 =head2 checkauth
501 ($userid, $cookie, $sessionID) = &checkauth($query, $noauth, $flagsrequired, $type);
503 Verifies that the user is authorized to run this script. If
504 the user is authorized, a (userid, cookie, session-id, flags)
505 quadruple is returned. If the user is not authorized but does
506 not have the required privilege (see $flagsrequired below), it
507 displays an error page and exits. Otherwise, it displays the
508 login page and exits.
510 Note that C<&checkauth> will return if and only if the user
511 is authorized, so it should be called early on, before any
512 unfinished operations (e.g., if you've opened a file, then
513 C<&checkauth> won't close it for you).
515 C<$query> is the CGI object for the script calling C<&checkauth>.
517 The C<$noauth> argument is optional. If it is set, then no
518 authorization is required for the script.
520 C<&checkauth> fetches user and session information from C<$query> and
521 ensures that the user is authorized to run scripts that require
522 authorization.
524 The C<$flagsrequired> argument specifies the required privileges
525 the user must have if the username and password are correct.
526 It should be specified as a reference-to-hash; keys in the hash
527 should be the "flags" for the user, as specified in the Members
528 intranet module. Any key specified must correspond to a "flag"
529 in the userflags table. E.g., { circulate => 1 } would specify
530 that the user must have the "circulate" privilege in order to
531 proceed. To make sure that access control is correct, the
532 C<$flagsrequired> parameter must be specified correctly.
534 Koha also has a concept of sub-permissions, also known as
535 granular permissions. This makes the value of each key
536 in the C<flagsrequired> hash take on an additional
537 meaning, i.e.,
541 The user must have access to all subfunctions of the module
542 specified by the hash key.
546 The user must have access to at least one subfunction of the module
547 specified by the hash key.
549 specific permission, e.g., 'export_catalog'
551 The user must have access to the specific subfunction list, which
552 must correspond to a row in the permissions table.
554 The C<$type> argument specifies whether the template should be
555 retrieved from the opac or intranet directory tree. "opac" is
556 assumed if it is not specified; however, if C<$type> is specified,
557 "intranet" is assumed if it is not "opac".
559 If C<$query> does not have a valid session ID associated with it
560 (i.e., the user has not logged in) or if the session has expired,
561 C<&checkauth> presents the user with a login page (from the point of
562 view of the original script, C<&checkauth> does not return). Once the
563 user has authenticated, C<&checkauth> restarts the original script
564 (this time, C<&checkauth> returns).
566 The login page is provided using a HTML::Template, which is set in the
567 systempreferences table or at the top of this file. The variable C<$type>
568 selects which template to use, either the opac or the intranet
569 authentification template.
571 C<&checkauth> returns a user ID, a cookie, and a session ID. The
572 cookie should be sent back to the browser; it verifies that the user
573 has authenticated.
575 =cut
577 sub _version_check ($$) {
578 my $type = shift;
579 my $query = shift;
580 my $version;
581 # If Version syspref is unavailable, it means Koha is beeing installed,
582 # and so we must redirect to OPAC maintenance page or to the WebInstaller
583 # also, if OpacMaintenance is ON, OPAC should redirect to maintenance
584 if (C4::Context->preference('OpacMaintenance') && $type eq 'opac') {
585 warn "OPAC Install required, redirecting to maintenance";
586 print $query->redirect("/cgi-bin/koha/maintenance.pl");
588 unless ( $version = C4::Context->preference('Version') ) { # assignment, not comparison
589 if ( $type ne 'opac' ) {
590 warn "Install required, redirecting to Installer";
591 print $query->redirect("/cgi-bin/koha/installer/install.pl");
592 } else {
593 warn "OPAC Install required, redirecting to maintenance";
594 print $query->redirect("/cgi-bin/koha/maintenance.pl");
596 safe_exit;
599 # check that database and koha version are the same
600 # there is no DB version, it's a fresh install,
601 # go to web installer
602 # there is a DB version, compare it to the code version
603 my $kohaversion=C4::Context::KOHAVERSION;
604 # remove the 3 last . to have a Perl number
605 $kohaversion =~ s/(.*\..*)\.(.*)\.(.*)/$1$2$3/;
606 $debug and print STDERR "kohaversion : $kohaversion\n";
607 if ($version < $kohaversion){
608 my $warning = "Database update needed, redirecting to %s. Database is $version and Koha is $kohaversion";
609 if ($type ne 'opac'){
610 warn sprintf($warning, 'Installer');
611 print $query->redirect("/cgi-bin/koha/installer/install.pl?step=3");
612 } else {
613 warn sprintf("OPAC: " . $warning, 'maintenance');
614 print $query->redirect("/cgi-bin/koha/maintenance.pl");
616 safe_exit;
620 sub _session_log {
621 (@_) or return 0;
622 open L, ">>/tmp/sessionlog" or warn "ERROR: Cannot append to /tmp/sessionlog";
623 printf L join("\n",@_);
624 close L;
627 sub checkauth {
628 my $query = shift;
629 $debug and warn "Checking Auth";
630 # $authnotrequired will be set for scripts which will run without authentication
631 my $authnotrequired = shift;
632 my $flagsrequired = shift;
633 my $type = shift;
634 $type = 'opac' unless $type;
636 my $dbh = C4::Context->dbh;
637 my $timeout = C4::Context->preference('timeout');
638 # days
639 if ($timeout =~ /(\d+)[dD]/) {
640 $timeout = $1 * 86400;
642 $timeout = 600 unless $timeout;
644 _version_check($type,$query);
645 # state variables
646 my $loggedin = 0;
647 my %info;
648 my ( $userid, $cookie, $sessionID, $flags, $barshelves, $pubshelves );
649 my $logout = $query->param('logout.x');
651 # This parameter is the name of the CAS server we want to authenticate against,
652 # when using authentication against multiple CAS servers, as configured in Auth_cas_servers.yaml
653 my $casparam = $query->param('cas');
655 if ( $userid = $ENV{'REMOTE_USER'} ) {
656 # Using Basic Authentication, no cookies required
657 $cookie = $query->cookie(
658 -name => 'CGISESSID',
659 -value => '',
660 -expires => ''
662 $loggedin = 1;
664 elsif ( $sessionID = $query->cookie("CGISESSID")) { # assignment, not comparison
665 my $session = get_session($sessionID);
666 C4::Context->_new_userenv($sessionID);
667 my ($ip, $lasttime, $sessiontype);
668 if ($session){
669 C4::Context::set_userenv(
670 $session->param('number'), $session->param('id'),
671 $session->param('cardnumber'), $session->param('firstname'),
672 $session->param('surname'), $session->param('branch'),
673 $session->param('branchname'), $session->param('flags'),
674 $session->param('emailaddress'), $session->param('branchprinter')
676 C4::Context::set_shelves_userenv('bar',$session->param('barshelves'));
677 C4::Context::set_shelves_userenv('pub',$session->param('pubshelves'));
678 C4::Context::set_shelves_userenv('tot',$session->param('totshelves'));
679 $debug and printf STDERR "AUTH_SESSION: (%s)\t%s %s - %s\n", map {$session->param($_)} qw(cardnumber firstname surname branch) ;
680 $ip = $session->param('ip');
681 $lasttime = $session->param('lasttime');
682 $userid = $session->param('id');
683 $sessiontype = $session->param('sessiontype');
685 if ( ($query->param('koha_login_context')) && ($query->param('userid') ne $session->param('id')) ) {
686 #if a user enters an id ne to the id in the current session, we need to log them in...
687 #first we need to clear the anonymous session...
688 $debug and warn "query id = " . $query->param('userid') . " but session id = " . $session->param('id');
689 $session->flush;
690 $session->delete();
691 C4::Context->_unset_userenv($sessionID);
692 $sessionID = undef;
693 $userid = undef;
695 elsif ($logout) {
696 # voluntary logout the user
697 $session->flush;
698 $session->delete();
699 C4::Context->_unset_userenv($sessionID);
700 _session_log(sprintf "%20s from %16s logged out at %30s (manually).\n", $userid,$ip,(strftime "%c",localtime));
701 $sessionID = undef;
702 $userid = undef;
704 if ($cas and $caslogout) {
705 logout_cas($query);
708 elsif ( $lasttime < time() - $timeout ) {
709 # timed logout
710 $info{'timed_out'} = 1;
711 $session->delete();
712 C4::Context->_unset_userenv($sessionID);
713 _session_log(sprintf "%20s from %16s logged out at %30s (inactivity).\n", $userid,$ip,(strftime "%c",localtime));
714 $userid = undef;
715 $sessionID = undef;
717 elsif ( $ip ne $ENV{'REMOTE_ADDR'} ) {
718 # Different ip than originally logged in from
719 $info{'oldip'} = $ip;
720 $info{'newip'} = $ENV{'REMOTE_ADDR'};
721 $info{'different_ip'} = 1;
722 $session->delete();
723 C4::Context->_unset_userenv($sessionID);
724 _session_log(sprintf "%20s from %16s logged out at %30s (ip changed to %16s).\n", $userid,$ip,(strftime "%c",localtime), $info{'newip'});
725 $sessionID = undef;
726 $userid = undef;
728 else {
729 $cookie = $query->cookie( CGISESSID => $session->id );
730 $session->param('lasttime',time());
731 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...
732 $flags = haspermission($userid, $flagsrequired);
733 if ($flags) {
734 $loggedin = 1;
735 } else {
736 $info{'nopermission'} = 1;
741 unless ($userid || $sessionID) {
742 #we initiate a session prior to checking for a username to allow for anonymous sessions...
743 my $session = get_session("") or die "Auth ERROR: Cannot get_session()";
744 my $sessionID = $session->id;
745 C4::Context->_new_userenv($sessionID);
746 $cookie = $query->cookie(CGISESSID => $sessionID);
747 $userid = $query->param('userid');
748 if ($cas || $userid) {
749 my $password = $query->param('password');
750 my ($return, $cardnumber);
751 if ($cas && $query->param('ticket')) {
752 my $retuserid;
753 ( $return, $cardnumber, $retuserid ) = checkpw( $dbh, $userid, $password, $query );
754 $userid = $retuserid;
755 $info{'invalidCasLogin'} = 1 unless ($return);
756 } else {
757 my $retuserid;
758 ( $return, $retuserid ) = checkpw( $dbh, $userid, $password, $query );
759 $userid = $retuserid if ($retuserid ne '');
761 if ($return) {
762 _session_log(sprintf "%20s from %16s logged in at %30s.\n", $userid,$ENV{'REMOTE_ADDR'},(strftime '%c', localtime));
763 if ( $flags = haspermission( $userid, $flagsrequired ) ) {
764 $loggedin = 1;
766 else {
767 $info{'nopermission'} = 1;
768 C4::Context->_unset_userenv($sessionID);
771 my ($borrowernumber, $firstname, $surname, $userflags,
772 $branchcode, $branchname, $branchprinter, $emailaddress);
774 if ( $return == 1 ) {
775 my $select = "
776 SELECT borrowernumber, firstname, surname, flags, borrowers.branchcode,
777 branches.branchname as branchname,
778 branches.branchprinter as branchprinter,
779 email
780 FROM borrowers
781 LEFT JOIN branches on borrowers.branchcode=branches.branchcode
783 my $sth = $dbh->prepare("$select where userid=?");
784 $sth->execute($userid);
785 unless ($sth->rows) {
786 $debug and print STDERR "AUTH_1: no rows for userid='$userid'\n";
787 $sth = $dbh->prepare("$select where cardnumber=?");
788 $sth->execute($cardnumber);
790 unless ($sth->rows) {
791 $debug and print STDERR "AUTH_2a: no rows for cardnumber='$cardnumber'\n";
792 $sth->execute($userid);
793 unless ($sth->rows) {
794 $debug and print STDERR "AUTH_2b: no rows for userid='$userid' AS cardnumber\n";
798 if ($sth->rows) {
799 ($borrowernumber, $firstname, $surname, $userflags,
800 $branchcode, $branchname, $branchprinter, $emailaddress) = $sth->fetchrow;
801 $debug and print STDERR "AUTH_3 results: " .
802 "$cardnumber,$borrowernumber,$userid,$firstname,$surname,$userflags,$branchcode,$emailaddress\n";
803 } else {
804 print STDERR "AUTH_3: no results for userid='$userid', cardnumber='$cardnumber'.\n";
807 # launch a sequence to check if we have a ip for the branch, i
808 # if we have one we replace the branchcode of the userenv by the branch bound in the ip.
810 my $ip = $ENV{'REMOTE_ADDR'};
811 # if they specify at login, use that
812 if ($query->param('branch')) {
813 $branchcode = $query->param('branch');
814 $branchname = GetBranchName($branchcode);
816 my $branches = GetBranches();
817 if (C4::Context->boolean_preference('IndependantBranches') && C4::Context->boolean_preference('Autolocation')){
818 # we have to check they are coming from the right ip range
819 my $domain = $branches->{$branchcode}->{'branchip'};
820 if ($ip !~ /^$domain/){
821 $loggedin=0;
822 $info{'wrongip'} = 1;
826 my @branchesloop;
827 foreach my $br ( keys %$branches ) {
828 # now we work with the treatment of ip
829 my $domain = $branches->{$br}->{'branchip'};
830 if ( $domain && $ip =~ /^$domain/ ) {
831 $branchcode = $branches->{$br}->{'branchcode'};
833 # new op dev : add the branchprinter and branchname in the cookie
834 $branchprinter = $branches->{$br}->{'branchprinter'};
835 $branchname = $branches->{$br}->{'branchname'};
838 $session->param('number',$borrowernumber);
839 $session->param('id',$userid);
840 $session->param('cardnumber',$cardnumber);
841 $session->param('firstname',$firstname);
842 $session->param('surname',$surname);
843 $session->param('branch',$branchcode);
844 $session->param('branchname',$branchname);
845 $session->param('flags',$userflags);
846 $session->param('emailaddress',$emailaddress);
847 $session->param('ip',$session->remote_addr());
848 $session->param('lasttime',time());
849 $debug and printf STDERR "AUTH_4: (%s)\t%s %s - %s\n", map {$session->param($_)} qw(cardnumber firstname surname branch) ;
851 elsif ( $return == 2 ) {
852 #We suppose the user is the superlibrarian
853 $borrowernumber = 0;
854 $session->param('number',0);
855 $session->param('id',C4::Context->config('user'));
856 $session->param('cardnumber',C4::Context->config('user'));
857 $session->param('firstname',C4::Context->config('user'));
858 $session->param('surname',C4::Context->config('user'));
859 $session->param('branch','NO_LIBRARY_SET');
860 $session->param('branchname','NO_LIBRARY_SET');
861 $session->param('flags',1);
862 $session->param('emailaddress', C4::Context->preference('KohaAdminEmailAddress'));
863 $session->param('ip',$session->remote_addr());
864 $session->param('lasttime',time());
866 C4::Context::set_userenv(
867 $session->param('number'), $session->param('id'),
868 $session->param('cardnumber'), $session->param('firstname'),
869 $session->param('surname'), $session->param('branch'),
870 $session->param('branchname'), $session->param('flags'),
871 $session->param('emailaddress'), $session->param('branchprinter')
874 # Grab borrower's shelves and public shelves and add them to the session
875 # $row_count determines how many records are returned from the db query
876 # and the number of lists to be displayed of each type in the 'Lists' button drop down
877 my $row_count = 10; # FIXME:This probably should be a syspref
878 my ($total, $totshelves, $barshelves, $pubshelves);
879 ($barshelves, $totshelves) = C4::VirtualShelves::GetRecentShelves(1, $row_count, $borrowernumber);
880 $total->{'bartotal'} = $totshelves;
881 ($pubshelves, $totshelves) = C4::VirtualShelves::GetRecentShelves(2, $row_count, undef);
882 $total->{'pubtotal'} = $totshelves;
883 $session->param('barshelves', $barshelves);
884 $session->param('pubshelves', $pubshelves);
885 $session->param('totshelves', $total);
887 C4::Context::set_shelves_userenv('bar',$barshelves);
888 C4::Context::set_shelves_userenv('pub',$pubshelves);
889 C4::Context::set_shelves_userenv('tot',$total);
891 else {
892 if ($userid) {
893 $info{'invalid_username_or_password'} = 1;
894 C4::Context->_unset_userenv($sessionID);
897 } # END if ( $userid = $query->param('userid') )
898 elsif ($type eq "opac") {
899 # if we are here this is an anonymous session; add public lists to it and a few other items...
900 # anonymous sessions are created only for the OPAC
901 $debug and warn "Initiating an anonymous session...";
903 # Grab the public shelves and add to the session...
904 my $row_count = 20; # FIXME:This probably should be a syspref
905 my ($total, $totshelves, $pubshelves);
906 ($pubshelves, $totshelves) = C4::VirtualShelves::GetRecentShelves(2, $row_count, undef);
907 $total->{'pubtotal'} = $totshelves;
908 $session->param('pubshelves', $pubshelves);
909 $session->param('totshelves', $total);
910 C4::Context::set_shelves_userenv('pub',$pubshelves);
911 C4::Context::set_shelves_userenv('tot',$total);
913 # setting a couple of other session vars...
914 $session->param('ip',$session->remote_addr());
915 $session->param('lasttime',time());
916 $session->param('sessiontype','anon');
918 } # END unless ($userid)
919 my $insecure = C4::Context->boolean_preference('insecure');
921 # finished authentification, now respond
922 if ( $loggedin || $authnotrequired || ( defined($insecure) && $insecure ) )
924 # successful login
925 unless ($cookie) {
926 $cookie = $query->cookie( CGISESSID => '' );
928 return ( $userid, $cookie, $sessionID, $flags );
933 # AUTH rejected, show the login/password template, after checking the DB.
937 # get the inputs from the incoming query
938 my @inputs = ();
939 foreach my $name ( param $query) {
940 (next) if ( $name eq 'userid' || $name eq 'password' || $name eq 'ticket' );
941 my $value = $query->param($name);
942 push @inputs, { name => $name, value => $value };
944 # get the branchloop, which we need for authentication
945 my $branches = GetBranches();
946 my @branch_loop;
947 for my $branch_hash (sort keys %$branches) {
948 push @branch_loop, {branchcode => "$branch_hash", branchname => $branches->{$branch_hash}->{'branchname'}, };
951 my $template_name = ( $type eq 'opac' ) ? 'opac-auth.tmpl' : 'auth.tmpl';
952 my $template = C4::Templates::gettemplate( $template_name, $type, $query );
953 $template->param(branchloop => \@branch_loop,);
954 my $checkstyle = C4::Context->preference("opaccolorstylesheet");
955 if ($checkstyle =~ /\//)
957 $template->param( opacexternalsheet => $checkstyle);
958 } else
960 my $opaccolorstylesheet = C4::Context->preference("opaccolorstylesheet");
961 $template->param( opaccolorstylesheet => $opaccolorstylesheet);
963 $template->param(
964 login => 1,
965 INPUTS => \@inputs,
966 casAuthentication => C4::Context->preference("casAuthentication"),
967 suggestion => C4::Context->preference("suggestion"),
968 virtualshelves => C4::Context->preference("virtualshelves"),
969 LibraryName => C4::Context->preference("LibraryName"),
970 opacuserlogin => C4::Context->preference("opacuserlogin"),
971 OpacNav => C4::Context->preference("OpacNav"),
972 OpacNavBottom => C4::Context->preference("OpacNavBottom"),
973 opaccredits => C4::Context->preference("opaccredits"),
974 OpacFavicon => C4::Context->preference("OpacFavicon"),
975 opacreadinghistory => C4::Context->preference("opacreadinghistory"),
976 opacsmallimage => C4::Context->preference("opacsmallimage"),
977 opaclayoutstylesheet => C4::Context->preference("opaclayoutstylesheet"),
978 opaclanguagesdisplay => C4::Context->preference("opaclanguagesdisplay"),
979 opacuserjs => C4::Context->preference("opacuserjs"),
980 opacbookbag => "" . C4::Context->preference("opacbookbag"),
981 OpacCloud => C4::Context->preference("OpacCloud"),
982 OpacTopissue => C4::Context->preference("OpacTopissue"),
983 OpacAuthorities => C4::Context->preference("OpacAuthorities"),
984 OpacBrowser => C4::Context->preference("OpacBrowser"),
985 opacheader => C4::Context->preference("opacheader"),
986 TagsEnabled => C4::Context->preference("TagsEnabled"),
987 OPACUserCSS => C4::Context->preference("OPACUserCSS"),
988 opacstylesheet => C4::Context->preference("opacstylesheet"),
989 intranetcolorstylesheet =>
990 C4::Context->preference("intranetcolorstylesheet"),
991 intranetstylesheet => C4::Context->preference("intranetstylesheet"),
992 intranetbookbag => C4::Context->preference("intranetbookbag"),
993 IntranetNav => C4::Context->preference("IntranetNav"),
994 intranetuserjs => C4::Context->preference("intranetuserjs"),
995 IndependantBranches=> C4::Context->preference("IndependantBranches"),
996 AutoLocation => C4::Context->preference("AutoLocation"),
997 wrongip => $info{'wrongip'},
1000 $template->param( OpacPublic => C4::Context->preference("OpacPublic"));
1001 $template->param( loginprompt => 1 ) unless $info{'nopermission'};
1003 if ($cas) {
1005 # Is authentication against multiple CAS servers enabled?
1006 if (C4::Auth_with_cas::multipleAuth && !$casparam) {
1007 my $casservers = C4::Auth_with_cas::getMultipleAuth();
1008 my @tmplservers;
1009 foreach my $key (keys %$casservers) {
1010 push @tmplservers, {name => $key, value => login_cas_url($query, $key) . "?cas=$key" };
1012 #warn Data::Dumper::Dumper(\@tmplservers);
1013 $template->param(
1014 casServersLoop => \@tmplservers
1016 } else {
1017 $template->param(
1018 casServerUrl => login_cas_url($query),
1022 $template->param(
1023 invalidCasLogin => $info{'invalidCasLogin'}
1027 my $self_url = $query->url( -absolute => 1 );
1028 $template->param(
1029 url => $self_url,
1030 LibraryName => C4::Context->preference("LibraryName"),
1032 $template->param( %info );
1033 # $cookie = $query->cookie(CGISESSID => $session->id
1034 # );
1035 print $query->header(
1036 -type => 'text/html',
1037 -charset => 'utf-8',
1038 -cookie => $cookie
1040 $template->output;
1041 safe_exit;
1044 =head2 check_api_auth
1046 ($status, $cookie, $sessionId) = check_api_auth($query, $userflags);
1048 Given a CGI query containing the parameters 'userid' and 'password' and/or a session
1049 cookie, determine if the user has the privileges specified by C<$userflags>.
1051 C<check_api_auth> is is meant for authenticating users of web services, and
1052 consequently will always return and will not attempt to redirect the user
1053 agent.
1055 If a valid session cookie is already present, check_api_auth will return a status
1056 of "ok", the cookie, and the Koha session ID.
1058 If no session cookie is present, check_api_auth will check the 'userid' and 'password
1059 parameters and create a session cookie and Koha session if the supplied credentials
1060 are OK.
1062 Possible return values in C<$status> are:
1064 =over
1066 =item "ok" -- user authenticated; C<$cookie> and C<$sessionid> have valid values.
1068 =item "failed" -- credentials are not correct; C<$cookie> and C<$sessionid> are undef
1070 =item "maintenance" -- DB is in maintenance mode; no login possible at the moment
1072 =item "expired -- session cookie has expired; API user should resubmit userid and password
1074 =back
1076 =cut
1078 sub check_api_auth {
1079 my $query = shift;
1080 my $flagsrequired = shift;
1082 my $dbh = C4::Context->dbh;
1083 my $timeout = C4::Context->preference('timeout');
1084 $timeout = 600 unless $timeout;
1086 unless (C4::Context->preference('Version')) {
1087 # database has not been installed yet
1088 return ("maintenance", undef, undef);
1090 my $kohaversion=C4::Context::KOHAVERSION;
1091 $kohaversion =~ s/(.*\..*)\.(.*)\.(.*)/$1$2$3/;
1092 if (C4::Context->preference('Version') < $kohaversion) {
1093 # database in need of version update; assume that
1094 # no API should be called while databsae is in
1095 # this condition.
1096 return ("maintenance", undef, undef);
1099 # FIXME -- most of what follows is a copy-and-paste
1100 # of code from checkauth. There is an obvious need
1101 # for refactoring to separate the various parts of
1102 # the authentication code, but as of 2007-11-19 this
1103 # is deferred so as to not introduce bugs into the
1104 # regular authentication code for Koha 3.0.
1106 # see if we have a valid session cookie already
1107 # however, if a userid parameter is present (i.e., from
1108 # a form submission, assume that any current cookie
1109 # is to be ignored
1110 my $sessionID = undef;
1111 unless ($query->param('userid')) {
1112 $sessionID = $query->cookie("CGISESSID");
1114 if ($sessionID && not $cas) {
1115 my $session = get_session($sessionID);
1116 C4::Context->_new_userenv($sessionID);
1117 if ($session) {
1118 C4::Context::set_userenv(
1119 $session->param('number'), $session->param('id'),
1120 $session->param('cardnumber'), $session->param('firstname'),
1121 $session->param('surname'), $session->param('branch'),
1122 $session->param('branchname'), $session->param('flags'),
1123 $session->param('emailaddress'), $session->param('branchprinter')
1126 my $ip = $session->param('ip');
1127 my $lasttime = $session->param('lasttime');
1128 my $userid = $session->param('id');
1129 if ( $lasttime < time() - $timeout ) {
1130 # time out
1131 $session->delete();
1132 C4::Context->_unset_userenv($sessionID);
1133 $userid = undef;
1134 $sessionID = undef;
1135 return ("expired", undef, undef);
1136 } elsif ( $ip ne $ENV{'REMOTE_ADDR'} ) {
1137 # IP address changed
1138 $session->delete();
1139 C4::Context->_unset_userenv($sessionID);
1140 $userid = undef;
1141 $sessionID = undef;
1142 return ("expired", undef, undef);
1143 } else {
1144 my $cookie = $query->cookie( CGISESSID => $session->id );
1145 $session->param('lasttime',time());
1146 my $flags = haspermission($userid, $flagsrequired);
1147 if ($flags) {
1148 return ("ok", $cookie, $sessionID);
1149 } else {
1150 $session->delete();
1151 C4::Context->_unset_userenv($sessionID);
1152 $userid = undef;
1153 $sessionID = undef;
1154 return ("failed", undef, undef);
1157 } else {
1158 return ("expired", undef, undef);
1160 } else {
1161 # new login
1162 my $userid = $query->param('userid');
1163 my $password = $query->param('password');
1164 my ($return, $cardnumber);
1166 # Proxy CAS auth
1167 if ($cas && $query->param('PT')) {
1168 my $retuserid;
1169 $debug and print STDERR "## check_api_auth - checking CAS\n";
1170 # In case of a CAS authentication, we use the ticket instead of the password
1171 my $PT = $query->param('PT');
1172 ($return,$cardnumber,$userid) = check_api_auth_cas($dbh, $PT, $query); # EXTERNAL AUTH
1173 } else {
1174 # User / password auth
1175 unless ($userid and $password) {
1176 # caller did something wrong, fail the authenticateion
1177 return ("failed", undef, undef);
1179 ( $return, $cardnumber ) = checkpw( $dbh, $userid, $password, $query );
1182 if ($return and haspermission( $userid, $flagsrequired)) {
1183 my $session = get_session("");
1184 return ("failed", undef, undef) unless $session;
1186 my $sessionID = $session->id;
1187 C4::Context->_new_userenv($sessionID);
1188 my $cookie = $query->cookie(CGISESSID => $sessionID);
1189 if ( $return == 1 ) {
1190 my (
1191 $borrowernumber, $firstname, $surname,
1192 $userflags, $branchcode, $branchname,
1193 $branchprinter, $emailaddress
1195 my $sth =
1196 $dbh->prepare(
1197 "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=?"
1199 $sth->execute($userid);
1201 $borrowernumber, $firstname, $surname,
1202 $userflags, $branchcode, $branchname,
1203 $branchprinter, $emailaddress
1204 ) = $sth->fetchrow if ( $sth->rows );
1206 unless ($sth->rows ) {
1207 my $sth = $dbh->prepare(
1208 "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=?"
1210 $sth->execute($cardnumber);
1212 $borrowernumber, $firstname, $surname,
1213 $userflags, $branchcode, $branchname,
1214 $branchprinter, $emailaddress
1215 ) = $sth->fetchrow if ( $sth->rows );
1217 unless ( $sth->rows ) {
1218 $sth->execute($userid);
1220 $borrowernumber, $firstname, $surname, $userflags,
1221 $branchcode, $branchname, $branchprinter, $emailaddress
1222 ) = $sth->fetchrow if ( $sth->rows );
1226 my $ip = $ENV{'REMOTE_ADDR'};
1227 # if they specify at login, use that
1228 if ($query->param('branch')) {
1229 $branchcode = $query->param('branch');
1230 $branchname = GetBranchName($branchcode);
1232 my $branches = GetBranches();
1233 my @branchesloop;
1234 foreach my $br ( keys %$branches ) {
1235 # now we work with the treatment of ip
1236 my $domain = $branches->{$br}->{'branchip'};
1237 if ( $domain && $ip =~ /^$domain/ ) {
1238 $branchcode = $branches->{$br}->{'branchcode'};
1240 # new op dev : add the branchprinter and branchname in the cookie
1241 $branchprinter = $branches->{$br}->{'branchprinter'};
1242 $branchname = $branches->{$br}->{'branchname'};
1245 $session->param('number',$borrowernumber);
1246 $session->param('id',$userid);
1247 $session->param('cardnumber',$cardnumber);
1248 $session->param('firstname',$firstname);
1249 $session->param('surname',$surname);
1250 $session->param('branch',$branchcode);
1251 $session->param('branchname',$branchname);
1252 $session->param('flags',$userflags);
1253 $session->param('emailaddress',$emailaddress);
1254 $session->param('ip',$session->remote_addr());
1255 $session->param('lasttime',time());
1256 } elsif ( $return == 2 ) {
1257 #We suppose the user is the superlibrarian
1258 $session->param('number',0);
1259 $session->param('id',C4::Context->config('user'));
1260 $session->param('cardnumber',C4::Context->config('user'));
1261 $session->param('firstname',C4::Context->config('user'));
1262 $session->param('surname',C4::Context->config('user'));
1263 $session->param('branch','NO_LIBRARY_SET');
1264 $session->param('branchname','NO_LIBRARY_SET');
1265 $session->param('flags',1);
1266 $session->param('emailaddress', C4::Context->preference('KohaAdminEmailAddress'));
1267 $session->param('ip',$session->remote_addr());
1268 $session->param('lasttime',time());
1270 C4::Context::set_userenv(
1271 $session->param('number'), $session->param('id'),
1272 $session->param('cardnumber'), $session->param('firstname'),
1273 $session->param('surname'), $session->param('branch'),
1274 $session->param('branchname'), $session->param('flags'),
1275 $session->param('emailaddress'), $session->param('branchprinter')
1277 return ("ok", $cookie, $sessionID);
1278 } else {
1279 return ("failed", undef, undef);
1284 =head2 check_cookie_auth
1286 ($status, $sessionId) = check_api_auth($cookie, $userflags);
1288 Given a CGISESSID cookie set during a previous login to Koha, determine
1289 if the user has the privileges specified by C<$userflags>.
1291 C<check_cookie_auth> is meant for authenticating special services
1292 such as tools/upload-file.pl that are invoked by other pages that
1293 have been authenticated in the usual way.
1295 Possible return values in C<$status> are:
1297 =over
1299 =item "ok" -- user authenticated; C<$sessionID> have valid values.
1301 =item "failed" -- credentials are not correct; C<$sessionid> are undef
1303 =item "maintenance" -- DB is in maintenance mode; no login possible at the moment
1305 =item "expired -- session cookie has expired; API user should resubmit userid and password
1307 =back
1309 =cut
1311 sub check_cookie_auth {
1312 my $cookie = shift;
1313 my $flagsrequired = shift;
1315 my $dbh = C4::Context->dbh;
1316 my $timeout = C4::Context->preference('timeout');
1317 $timeout = 600 unless $timeout;
1319 unless (C4::Context->preference('Version')) {
1320 # database has not been installed yet
1321 return ("maintenance", undef);
1323 my $kohaversion=C4::Context::KOHAVERSION;
1324 $kohaversion =~ s/(.*\..*)\.(.*)\.(.*)/$1$2$3/;
1325 if (C4::Context->preference('Version') < $kohaversion) {
1326 # database in need of version update; assume that
1327 # no API should be called while databsae is in
1328 # this condition.
1329 return ("maintenance", undef);
1332 # FIXME -- most of what follows is a copy-and-paste
1333 # of code from checkauth. There is an obvious need
1334 # for refactoring to separate the various parts of
1335 # the authentication code, but as of 2007-11-23 this
1336 # is deferred so as to not introduce bugs into the
1337 # regular authentication code for Koha 3.0.
1339 # see if we have a valid session cookie already
1340 # however, if a userid parameter is present (i.e., from
1341 # a form submission, assume that any current cookie
1342 # is to be ignored
1343 unless (defined $cookie and $cookie) {
1344 return ("failed", undef);
1346 my $sessionID = $cookie;
1347 my $session = get_session($sessionID);
1348 C4::Context->_new_userenv($sessionID);
1349 if ($session) {
1350 C4::Context::set_userenv(
1351 $session->param('number'), $session->param('id'),
1352 $session->param('cardnumber'), $session->param('firstname'),
1353 $session->param('surname'), $session->param('branch'),
1354 $session->param('branchname'), $session->param('flags'),
1355 $session->param('emailaddress'), $session->param('branchprinter')
1358 my $ip = $session->param('ip');
1359 my $lasttime = $session->param('lasttime');
1360 my $userid = $session->param('id');
1361 if ( $lasttime < time() - $timeout ) {
1362 # time out
1363 $session->delete();
1364 C4::Context->_unset_userenv($sessionID);
1365 $userid = undef;
1366 $sessionID = undef;
1367 return ("expired", undef);
1368 } elsif ( $ip ne $ENV{'REMOTE_ADDR'} ) {
1369 # IP address changed
1370 $session->delete();
1371 C4::Context->_unset_userenv($sessionID);
1372 $userid = undef;
1373 $sessionID = undef;
1374 return ("expired", undef);
1375 } else {
1376 $session->param('lasttime',time());
1377 my $flags = haspermission($userid, $flagsrequired);
1378 if ($flags) {
1379 return ("ok", $sessionID);
1380 } else {
1381 $session->delete();
1382 C4::Context->_unset_userenv($sessionID);
1383 $userid = undef;
1384 $sessionID = undef;
1385 return ("failed", undef);
1388 } else {
1389 return ("expired", undef);
1393 =head2 get_session
1395 use CGI::Session;
1396 my $session = get_session($sessionID);
1398 Given a session ID, retrieve the CGI::Session object used to store
1399 the session's state. The session object can be used to store
1400 data that needs to be accessed by different scripts during a
1401 user's session.
1403 If the C<$sessionID> parameter is an empty string, a new session
1404 will be created.
1406 =cut
1408 sub get_session {
1409 my $sessionID = shift;
1410 my $storage_method = C4::Context->preference('SessionStorage');
1411 my $dbh = C4::Context->dbh;
1412 my $session;
1413 if ($storage_method eq 'mysql'){
1414 $session = new CGI::Session("driver:MySQL;serializer:yaml;id:md5", $sessionID, {Handle=>$dbh});
1416 elsif ($storage_method eq 'Pg') {
1417 $session = new CGI::Session("driver:PostgreSQL;serializer:yaml;id:md5", $sessionID, {Handle=>$dbh});
1419 elsif ($storage_method eq 'memcached' && $servers){
1420 $session = new CGI::Session("driver:memcached;serializer:yaml;id:md5", $sessionID, { Memcached => $memcached } );
1422 else {
1423 # catch all defaults to tmp should work on all systems
1424 $session = new CGI::Session("driver:File;serializer:yaml;id:md5", $sessionID, {Directory=>'/tmp'});
1426 return $session;
1429 sub checkpw {
1431 my ( $dbh, $userid, $password, $query ) = @_;
1432 if ($ldap) {
1433 $debug and print "## checkpw - checking LDAP\n";
1434 my ($retval,$retcard) = checkpw_ldap(@_); # EXTERNAL AUTH
1435 ($retval) and return ($retval,$retcard);
1438 if ($cas && $query && $query->param('ticket')) {
1439 $debug and print STDERR "## checkpw - checking CAS\n";
1440 # In case of a CAS authentication, we use the ticket instead of the password
1441 my $ticket = $query->param('ticket');
1442 my ($retval,$retcard,$retuserid) = checkpw_cas($dbh, $ticket, $query); # EXTERNAL AUTH
1443 ($retval) and return ($retval,$retcard,$retuserid);
1444 return 0;
1447 # INTERNAL AUTH
1448 my $sth =
1449 $dbh->prepare(
1450 "select password,cardnumber,borrowernumber,userid,firstname,surname,branchcode,flags from borrowers where userid=?"
1452 $sth->execute($userid);
1453 if ( $sth->rows ) {
1454 my ( $md5password, $cardnumber, $borrowernumber, $userid, $firstname,
1455 $surname, $branchcode, $flags )
1456 = $sth->fetchrow;
1457 if ( md5_base64($password) eq $md5password and $md5password ne "!") {
1459 C4::Context->set_userenv( "$borrowernumber", $userid, $cardnumber,
1460 $firstname, $surname, $branchcode, $flags );
1461 return 1, $userid;
1464 $sth =
1465 $dbh->prepare(
1466 "select password,cardnumber,borrowernumber,userid, firstname,surname,branchcode,flags from borrowers where cardnumber=?"
1468 $sth->execute($userid);
1469 if ( $sth->rows ) {
1470 my ( $md5password, $cardnumber, $borrowernumber, $userid, $firstname,
1471 $surname, $branchcode, $flags )
1472 = $sth->fetchrow;
1473 if ( md5_base64($password) eq $md5password ) {
1475 C4::Context->set_userenv( $borrowernumber, $userid, $cardnumber,
1476 $firstname, $surname, $branchcode, $flags );
1477 return 1, $userid;
1480 if ( $userid && $userid eq C4::Context->config('user')
1481 && "$password" eq C4::Context->config('pass') )
1484 # Koha superuser account
1485 # C4::Context->set_userenv(0,0,C4::Context->config('user'),C4::Context->config('user'),C4::Context->config('user'),"",1);
1486 return 2;
1488 if ( $userid && $userid eq 'demo'
1489 && "$password" eq 'demo'
1490 && C4::Context->config('demo') )
1493 # DEMO => the demo user is allowed to do everything (if demo set to 1 in koha.conf
1494 # some features won't be effective : modify systempref, modify MARC structure,
1495 return 2;
1497 return 0;
1500 =head2 getuserflags
1502 my $authflags = getuserflags($flags, $userid, [$dbh]);
1504 Translates integer flags into permissions strings hash.
1506 C<$flags> is the integer userflags value ( borrowers.userflags )
1507 C<$userid> is the members.userid, used for building subpermissions
1508 C<$authflags> is a hashref of permissions
1510 =cut
1512 sub getuserflags {
1513 my $flags = shift;
1514 my $userid = shift;
1515 my $dbh = @_ ? shift : C4::Context->dbh;
1516 my $userflags;
1517 $flags = 0 unless $flags;
1518 my $sth = $dbh->prepare("SELECT bit, flag, defaulton FROM userflags");
1519 $sth->execute;
1521 while ( my ( $bit, $flag, $defaulton ) = $sth->fetchrow ) {
1522 if ( ( $flags & ( 2**$bit ) ) || $defaulton ) {
1523 $userflags->{$flag} = 1;
1525 else {
1526 $userflags->{$flag} = 0;
1530 # get subpermissions and merge with top-level permissions
1531 my $user_subperms = get_user_subpermissions($userid);
1532 foreach my $module (keys %$user_subperms) {
1533 next if $userflags->{$module} == 1; # user already has permission for everything in this module
1534 $userflags->{$module} = $user_subperms->{$module};
1537 return $userflags;
1540 =head2 get_user_subpermissions
1542 $user_perm_hashref = get_user_subpermissions($userid);
1544 Given the userid (note, not the borrowernumber) of a staff user,
1545 return a hashref of hashrefs of the specific subpermissions
1546 accorded to the user. An example return is
1549 tools => {
1550 export_catalog => 1,
1551 import_patrons => 1,
1555 The top-level hash-key is a module or function code from
1556 userflags.flag, while the second-level key is a code
1557 from permissions.
1559 The results of this function do not give a complete picture
1560 of the functions that a staff user can access; it is also
1561 necessary to check borrowers.flags.
1563 =cut
1565 sub get_user_subpermissions {
1566 my $userid = shift;
1568 my $dbh = C4::Context->dbh;
1569 my $sth = $dbh->prepare("SELECT flag, user_permissions.code
1570 FROM user_permissions
1571 JOIN permissions USING (module_bit, code)
1572 JOIN userflags ON (module_bit = bit)
1573 JOIN borrowers USING (borrowernumber)
1574 WHERE userid = ?");
1575 $sth->execute($userid);
1577 my $user_perms = {};
1578 while (my $perm = $sth->fetchrow_hashref) {
1579 $user_perms->{$perm->{'flag'}}->{$perm->{'code'}} = 1;
1581 return $user_perms;
1584 =head2 get_all_subpermissions
1586 my $perm_hashref = get_all_subpermissions();
1588 Returns a hashref of hashrefs defining all specific
1589 permissions currently defined. The return value
1590 has the same structure as that of C<get_user_subpermissions>,
1591 except that the innermost hash value is the description
1592 of the subpermission.
1594 =cut
1596 sub get_all_subpermissions {
1597 my $dbh = C4::Context->dbh;
1598 my $sth = $dbh->prepare("SELECT flag, code, description
1599 FROM permissions
1600 JOIN userflags ON (module_bit = bit)");
1601 $sth->execute();
1603 my $all_perms = {};
1604 while (my $perm = $sth->fetchrow_hashref) {
1605 $all_perms->{$perm->{'flag'}}->{$perm->{'code'}} = $perm->{'description'};
1607 return $all_perms;
1610 =head2 haspermission
1612 $flags = ($userid, $flagsrequired);
1614 C<$userid> the userid of the member
1615 C<$flags> is a hashref of required flags like C<$borrower-&lt;{authflags}>
1617 Returns member's flags or 0 if a permission is not met.
1619 =cut
1621 sub haspermission {
1622 my ($userid, $flagsrequired) = @_;
1623 my $sth = C4::Context->dbh->prepare("SELECT flags FROM borrowers WHERE userid=?");
1624 $sth->execute($userid);
1625 my $flags = getuserflags($sth->fetchrow(), $userid);
1626 if ( $userid eq C4::Context->config('user') ) {
1627 # Super User Account from /etc/koha.conf
1628 $flags->{'superlibrarian'} = 1;
1630 elsif ( $userid eq 'demo' && C4::Context->config('demo') ) {
1631 # Demo user that can do "anything" (demo=1 in /etc/koha.conf)
1632 $flags->{'superlibrarian'} = 1;
1635 return $flags if $flags->{superlibrarian};
1637 foreach my $module ( keys %$flagsrequired ) {
1638 my $subperm = $flagsrequired->{$module};
1639 if ($subperm eq '*') {
1640 return 0 unless ( $flags->{$module} == 1 or ref($flags->{$module}) );
1641 } else {
1642 return 0 unless ( $flags->{$module} == 1 or
1643 ( ref($flags->{$module}) and
1644 exists $flags->{$module}->{$subperm} and
1645 $flags->{$module}->{$subperm} == 1
1650 return $flags;
1651 #FIXME - This fcn should return the failed permission so a suitable error msg can be delivered.
1655 sub getborrowernumber {
1656 my ($userid) = @_;
1657 my $userenv = C4::Context->userenv;
1658 if ( defined( $userenv ) && ref( $userenv ) eq 'HASH' && $userenv->{number} ) {
1659 return $userenv->{number};
1661 my $dbh = C4::Context->dbh;
1662 for my $field ( 'userid', 'cardnumber' ) {
1663 my $sth =
1664 $dbh->prepare("select borrowernumber from borrowers where $field=?");
1665 $sth->execute($userid);
1666 if ( $sth->rows ) {
1667 my ($bnumber) = $sth->fetchrow;
1668 return $bnumber;
1671 return 0;
1674 END { } # module clean-up code here (global destructor)
1676 __END__
1678 =head1 SEE ALSO
1680 CGI(3)
1682 C4::Output(3)
1684 Digest::MD5(3)
1686 =cut