Remove ticket parameter from auto added hidden fields.
[koha.git] / C4 / Auth.pm
blob3c34abc2e96984897f7478e3a256d7ad781abe92
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 with
17 # Koha; if not, write to the Free Software Foundation, Inc., 59 Temple Place,
18 # Suite 330, Boston, MA 02111-1307 USA
20 use strict;
21 use Digest::MD5 qw(md5_base64);
22 use Storable qw(thaw freeze);
23 use URI::Escape;
24 use CGI::Session;
26 require Exporter;
27 use C4::Context;
28 use C4::Output; # to get the template
29 use C4::Members;
30 use C4::Koha;
31 use C4::Branch; # GetBranches
32 use C4::VirtualShelves;
33 use POSIX qw/strftime/;
35 # use utf8;
36 use vars qw($VERSION @ISA @EXPORT @EXPORT_OK %EXPORT_TAGS $debug $ldap $cas $caslogout);
38 BEGIN {
39 $VERSION = 3.02; # set version for version checking
40 $debug = $ENV{DEBUG};
41 @ISA = qw(Exporter);
42 @EXPORT = qw(&checkauth &get_template_and_user &haspermission &get_user_subpermissions);
43 @EXPORT_OK = qw(&check_api_auth &get_session &check_cookie_auth &checkpw &get_all_subpermissions &get_user_subpermissions);
44 %EXPORT_TAGS = (EditPermissions => [qw(get_all_subpermissions get_user_subpermissions)]);
45 $ldap = C4::Context->config('useldapserver') || 0;
46 $cas = C4::Context->preference('casAuthentication');
47 $caslogout = C4::Context->preference('casLogout');
48 if ($ldap) {
49 require C4::Auth_with_ldap; # no import
50 import C4::Auth_with_ldap qw(checkpw_ldap);
52 if ($cas) {
53 require C4::Auth_with_cas; # no import
54 import C4::Auth_with_cas qw(checkpw_cas login_cas logout_cas login_cas_url);
59 =head1 NAME
61 C4::Auth - Authenticates Koha users
63 =head1 SYNOPSIS
65 use CGI;
66 use C4::Auth;
67 use C4::Output;
69 my $query = new CGI;
71 my ($template, $borrowernumber, $cookie)
72 = get_template_and_user(
74 template_name => "opac-main.tmpl",
75 query => $query,
76 type => "opac",
77 authnotrequired => 1,
78 flagsrequired => {borrow => 1, catalogue => '*', tools => 'import_patrons' },
82 output_html_with_http_headers $query, $cookie, $template->output;
84 =head1 DESCRIPTION
86 The main function of this module is to provide
87 authentification. However the get_template_and_user function has
88 been provided so that a users login information is passed along
89 automatically. This gets loaded into the template.
91 =head1 FUNCTIONS
93 =over 2
95 =item get_template_and_user
97 my ($template, $borrowernumber, $cookie)
98 = get_template_and_user(
100 template_name => "opac-main.tmpl",
101 query => $query,
102 type => "opac",
103 authnotrequired => 1,
104 flagsrequired => {borrow => 1, catalogue => '*', tools => 'import_patrons' },
108 This call passes the C<query>, C<flagsrequired> and C<authnotrequired>
109 to C<&checkauth> (in this module) to perform authentification.
110 See C<&checkauth> for an explanation of these parameters.
112 The C<template_name> is then used to find the correct template for
113 the page. The authenticated users details are loaded onto the
114 template in the HTML::Template LOOP variable C<USER_INFO>. Also the
115 C<sessionID> is passed to the template. This can be used in templates
116 if cookies are disabled. It needs to be put as and input to every
117 authenticated page.
119 More information on the C<gettemplate> sub can be found in the
120 Output.pm module.
122 =cut
124 sub get_template_and_user {
125 my $in = shift;
126 my $template =
127 gettemplate( $in->{'template_name'}, $in->{'type'}, $in->{'query'} );
128 my ( $user, $cookie, $sessionID, $flags ) = checkauth(
129 $in->{'query'},
130 $in->{'authnotrequired'},
131 $in->{'flagsrequired'},
132 $in->{'type'}
133 ) unless ($in->{'template_name'}=~/maintenance/);
135 my $borrowernumber;
136 my $insecure = C4::Context->preference('insecure');
137 if ($user or $insecure) {
139 # load the template variables for stylesheets and JavaScript
140 $template->param( css_libs => $in->{'css_libs'} );
141 $template->param( css_module => $in->{'css_module'} );
142 $template->param( css_page => $in->{'css_page'} );
143 $template->param( css_widgets => $in->{'css_widgets'} );
145 $template->param( js_libs => $in->{'js_libs'} );
146 $template->param( js_module => $in->{'js_module'} );
147 $template->param( js_page => $in->{'js_page'} );
148 $template->param( js_widgets => $in->{'js_widgets'} );
150 # user info
151 $template->param( loggedinusername => $user );
152 $template->param( sessionID => $sessionID );
154 my ($total, $pubshelves, $barshelves) = C4::Context->get_shelves_userenv();
155 if (defined($pubshelves)) {
156 $template->param( pubshelves => scalar (@$pubshelves),
157 pubshelvesloop => $pubshelves,
159 $template->param( pubtotal => $total->{'pubtotal'}, ) if ($total->{'pubtotal'} > scalar (@$pubshelves));
161 if (defined($barshelves)) {
162 $template->param( barshelves => scalar (@$barshelves),
163 barshelvesloop => $barshelves,
165 $template->param( bartotal => $total->{'bartotal'}, ) if ($total->{'bartotal'} > scalar (@$barshelves));
168 $borrowernumber = getborrowernumber($user);
169 my ( $borr ) = GetMemberDetails( $borrowernumber );
170 my @bordat;
171 $bordat[0] = $borr;
172 $template->param( "USER_INFO" => \@bordat );
174 my $all_perms = get_all_subpermissions();
176 my @flagroots = qw(circulate catalogue parameters borrowers permissions reserveforothers borrow
177 editcatalogue updatecharges management tools editauthorities serials reports acquisition);
178 # We are going to use the $flags returned by checkauth
179 # to create the template's parameters that will indicate
180 # which menus the user can access.
181 if (( $flags && $flags->{superlibrarian}==1) or $insecure==1) {
182 $template->param( CAN_user_circulate => 1 );
183 $template->param( CAN_user_catalogue => 1 );
184 $template->param( CAN_user_parameters => 1 );
185 $template->param( CAN_user_borrowers => 1 );
186 $template->param( CAN_user_permissions => 1 );
187 $template->param( CAN_user_reserveforothers => 1 );
188 $template->param( CAN_user_borrow => 1 );
189 $template->param( CAN_user_editcatalogue => 1 );
190 $template->param( CAN_user_updatecharges => 1 );
191 $template->param( CAN_user_acquisition => 1 );
192 $template->param( CAN_user_management => 1 );
193 $template->param( CAN_user_tools => 1 );
194 $template->param( CAN_user_editauthorities => 1 );
195 $template->param( CAN_user_serials => 1 );
196 $template->param( CAN_user_reports => 1 );
197 $template->param( CAN_user_staffaccess => 1 );
198 foreach my $module (keys %$all_perms) {
199 foreach my $subperm (keys %{ $all_perms->{$module} }) {
200 $template->param( "CAN_user_${module}_${subperm}" => 1 );
205 if (C4::Context->preference('GranularPermissions')) {
206 if ( $flags ) {
207 foreach my $module (keys %$all_perms) {
208 if ( $flags->{$module} == 1) {
209 foreach my $subperm (keys %{ $all_perms->{$module} }) {
210 $template->param( "CAN_user_${module}_${subperm}" => 1 );
212 } elsif ( ref($flags->{$module}) ) {
213 foreach my $subperm (keys %{ $flags->{$module} } ) {
214 $template->param( "CAN_user_${module}_${subperm}" => 1 );
219 } else {
220 foreach my $module (keys %$all_perms) {
221 foreach my $subperm (keys %{ $all_perms->{$module} }) {
222 $template->param( "CAN_user_${module}_${subperm}" => 1 );
227 if ($flags) {
228 foreach my $module (keys %$flags) {
229 if ( $flags->{$module} == 1 or ref($flags->{$module}) ) {
230 $template->param( "CAN_user_$module" => 1 );
231 if ($module eq "parameters") {
232 $template->param( CAN_user_management => 1 );
237 # Logged-in opac search history
238 # If the requested template is an opac one and opac search history is enabled
239 if ($in->{'type'} == "opac" && C4::Context->preference('EnableOpacSearchHistory')) {
240 my $dbh = C4::Context->dbh;
241 my $query = "SELECT COUNT(*) FROM search_history WHERE userid=?";
242 my $sth = $dbh->prepare($query);
243 $sth->execute($borrowernumber);
245 # If at least one search has already been performed
246 if ($sth->fetchrow_array > 0) {
247 # We show the link in opac
248 $template->param(ShowOpacRecentSearchLink => 1);
251 # And if there's a cookie with searches performed when the user was not logged in,
252 # we add them to the logged-in search history
253 my @recentSearches;
254 my $searchcookie = $in->{'query'}->cookie('KohaOpacRecentSearches');
255 if ($searchcookie){
256 $searchcookie = uri_unescape($searchcookie);
257 if (thaw($searchcookie)) {
258 @recentSearches = @{thaw($searchcookie)};
261 if (@recentSearches > 0) {
262 my $query = "INSERT INTO search_history(userid, sessionid, query_desc, query_cgi, total, time) VALUES";
263 my $icount = 1;
264 foreach my $asearch (@recentSearches) {
265 $query .= "(";
266 $query .= $borrowernumber . ", ";
267 $query .= '"' . $in->{'query'}->cookie("CGISESSID") . "\", ";
268 $query .= '"' . $asearch->{'query_desc'} . "\", ";
269 $query .= '"' . $asearch->{'query_cgi'} . "\", ";
270 $query .= $asearch->{'total'} . ", ";
271 $query .= 'FROM_UNIXTIME(' . $asearch->{'time'} . "))";
272 if ($icount < @recentSearches) { $query .= ", ";}
273 $icount++;
276 my $sth = $dbh->prepare($query);
277 $sth->execute;
279 # And then, delete the cookie's content
280 my $newsearchcookie = $in->{'query'}->cookie(
281 -name => 'KohaOpacRecentSearches',
282 -value => freeze([]),
283 -expires => ''
285 $cookie = [$cookie, $newsearchcookie];
290 else { # if this is an anonymous session, setup to display public lists...
292 # load the template variables for stylesheets and JavaScript
293 $template->param( css_libs => $in->{'css_libs'} );
294 $template->param( css_module => $in->{'css_module'} );
295 $template->param( css_page => $in->{'css_page'} );
296 $template->param( css_widgets => $in->{'css_widgets'} );
298 $template->param( js_libs => $in->{'js_libs'} );
299 $template->param( js_module => $in->{'js_module'} );
300 $template->param( js_page => $in->{'js_page'} );
301 $template->param( js_widgets => $in->{'js_widgets'} );
303 $template->param( sessionID => $sessionID );
305 my ($total, $pubshelves) = C4::Context->get_shelves_userenv(); # an anonymous user has no 'barshelves'...
306 if (defined(($pubshelves))) {
307 $template->param( pubshelves => scalar (@$pubshelves),
308 pubshelvesloop => $pubshelves,
310 $template->param( pubtotal => $total->{'pubtotal'}, ) if ($total->{'pubtotal'} > scalar (@$pubshelves));
314 # Anonymous opac search history
315 # If opac search history is enabled and at least one search has already been performed
316 if (C4::Context->preference('EnableOpacSearchHistory') && $in->{'query'}->cookie('KohaOpacRecentSearches')) {
317 # We show the link in opac
318 if (thaw(uri_unescape($in->{'query'}->cookie('KohaOpacRecentSearches')))) {
319 my @recentSearches = @{thaw(uri_unescape($in->{'query'}->cookie('KohaOpacRecentSearches')))};
320 if (@recentSearches > 0) {
321 $template->param(ShowOpacRecentSearchLink => 1);
326 # these template parameters are set the same regardless of $in->{'type'}
327 $template->param(
328 "BiblioDefaultView".C4::Context->preference("BiblioDefaultView") => 1,
329 EnhancedMessagingPreferences => C4::Context->preference('EnhancedMessagingPreferences'),
330 GoogleJackets => C4::Context->preference("GoogleJackets"),
331 KohaAdminEmailAddress => "" . C4::Context->preference("KohaAdminEmailAddress"),
332 LoginBranchcode => (C4::Context->userenv?C4::Context->userenv->{"branch"}:"insecure"),
333 LoginFirstname => (C4::Context->userenv?C4::Context->userenv->{"firstname"}:"Bel"),
334 LoginSurname => C4::Context->userenv?C4::Context->userenv->{"surname"}:"Inconnu",
335 TagsEnabled => C4::Context->preference("TagsEnabled"),
336 hide_marc => C4::Context->preference("hide_marc"),
337 'item-level_itypes' => C4::Context->preference('item-level_itypes'),
338 patronimages => C4::Context->preference("patronimages"),
339 singleBranchMode => C4::Context->preference("singleBranchMode"),
340 XSLTDetailsDisplay => C4::Context->preference("XSLTDetailsDisplay"),
341 XSLTResultsDisplay => C4::Context->preference("XSLTResultsDisplay"),
344 if ( $in->{'type'} eq "intranet" ) {
345 $template->param(
346 AmazonContent => C4::Context->preference("AmazonContent"),
347 AmazonSimilarItems => C4::Context->preference("AmazonSimilarItems"),
348 AutoLocation => C4::Context->preference("AutoLocation"),
349 "BiblioDefaultView".C4::Context->preference("IntranetBiblioDefaultView") => 1,
350 CircAutocompl => C4::Context->preference("CircAutocompl"),
351 FRBRizeEditions => C4::Context->preference("FRBRizeEditions"),
352 IndependantBranches => C4::Context->preference("IndependantBranches"),
353 IntranetNav => C4::Context->preference("IntranetNav"),
354 IntranetmainUserblock => C4::Context->preference("IntranetmainUserblock"),
355 LibraryName => C4::Context->preference("LibraryName"),
356 LoginBranchname => (C4::Context->userenv?C4::Context->userenv->{"branchname"}:"insecure"),
357 TemplateEncoding => C4::Context->preference("TemplateEncoding"),
358 advancedMARCEditor => C4::Context->preference("advancedMARCEditor"),
359 canreservefromotherbranches => C4::Context->preference('canreservefromotherbranches'),
360 intranetcolorstylesheet => C4::Context->preference("intranetcolorstylesheet"),
361 intranetreadinghistory => C4::Context->preference("intranetreadinghistory"),
362 intranetstylesheet => C4::Context->preference("intranetstylesheet"),
363 intranetuserjs => C4::Context->preference("intranetuserjs"),
364 intranetbookbag => C4::Context->preference("intranetbookbag"),
365 noItemTypeImages => C4::Context->preference("noItemTypeImages"),
366 suggestion => C4::Context->preference("suggestion"),
367 virtualshelves => C4::Context->preference("virtualshelves"),
368 StaffSerialIssueDisplayCount => C4::Context->preference("StaffSerialIssueDisplayCount"),
369 NoZebra => C4::Context->preference('NoZebra'),
372 else {
373 warn "template type should be OPAC, here it is=[" . $in->{'type'} . "]" unless ( $in->{'type'} eq 'opac' );
374 #TODO : replace LibraryName syspref with 'system name', and remove this html processing
375 my $LibraryNameTitle = C4::Context->preference("LibraryName");
376 $LibraryNameTitle =~ s/<(?:\/?)(?:br|p)\s*(?:\/?)>/ /sgi;
377 $LibraryNameTitle =~ s/<(?:[^<>'"]|'(?:[^']*)'|"(?:[^"]*)")*>//sg;
378 # variables passed from CGI: opac_css_override and opac_search_limits.
379 my $opac_search_limit = $ENV{'OPAC_SEARCH_LIMIT'};
380 my $opac_limit_override = $ENV{'OPAC_LIMIT_OVERRIDE'};
381 my $mylibraryfirst = C4::Context->preference("SearchMyLibraryFirst");
382 my $opac_name;
383 if($opac_limit_override && ($opac_search_limit =~ /branch:(\w+)/) ){
384 $opac_name = C4::Branch::GetBranchName($1) # opac_search_limit is a branch, so we use it.
385 } elsif($mylibraryfirst){
386 $opac_name = C4::Branch::GetBranchName($mylibraryfirst);
388 $template->param(
389 AmazonContent => "" . C4::Context->preference("AmazonContent"),
390 AnonSuggestions => "" . C4::Context->preference("AnonSuggestions"),
391 AuthorisedValueImages => C4::Context->preference("AuthorisedValueImages"),
392 LibraryName => "" . C4::Context->preference("LibraryName"),
393 LibraryNameTitle => "" . $LibraryNameTitle,
394 LoginBranchname => C4::Context->userenv?C4::Context->userenv->{"branchname"}:"",
395 OPACAmazonSimilarItems => "" . C4::Context->preference("OPACAmazonSimilarItems"),
396 OPACFRBRizeEditions => C4::Context->preference("OPACFRBRizeEditions"),
397 OPACItemHolds => C4::Context->preference("OPACItemHolds"),
398 OPACShelfBrowser => "". C4::Context->preference("OPACShelfBrowser"),
399 OPACURLOpenInNewWindow => "" . C4::Context->preference("OPACURLOpenInNewWindow"),
400 OPACUserCSS => "". C4::Context->preference("OPACUserCSS"),
401 OPACViewOthersSuggestions => "" . C4::Context->preference("OPACViewOthersSuggestions"),
402 OpacAuthorities => C4::Context->preference("OpacAuthorities"),
403 OPACBaseURL => ($in->{'query'}->https() ? "https://" : "http://") . $ENV{'SERVER_NAME'} .
404 ($ENV{'SERVER_PORT'} eq ($in->{'query'}->https() ? "443" : "80") ? '' : ":$ENV{'SERVER_PORT'}"),
405 opac_name => $opac_name,
406 opac_css_override => $ENV{'OPAC_CSS_OVERRIDE'},
407 opac_search_limit => $opac_search_limit,
408 opac_limit_override => $opac_limit_override,
409 OpacBrowser => C4::Context->preference("OpacBrowser"),
410 OpacCloud => C4::Context->preference("OpacCloud"),
411 OpacMainUserBlock => "" . C4::Context->preference("OpacMainUserBlock"),
412 OpacNav => "" . C4::Context->preference("OpacNav"),
413 OpacPasswordChange => C4::Context->preference("OpacPasswordChange"),
414 OPACPatronDetails => C4::Context->preference("OPACPatronDetails"),
415 OPACFinesTab => C4::Context->preference("OPACFinesTab"),
416 OpacTopissue => C4::Context->preference("OpacTopissue"),
417 RequestOnOpac => C4::Context->preference("RequestOnOpac"),
418 TemplateEncoding => "". C4::Context->preference("TemplateEncoding"),
419 'Version' => C4::Context->preference('Version'),
420 hidelostitems => C4::Context->preference("hidelostitems"),
421 mylibraryfirst => (C4::Context->preference("SearchMyLibraryFirst") && C4::Context->userenv) ? C4::Context->userenv->{'branch'} : '',
422 opaclayoutstylesheet => "" . C4::Context->preference("opaclayoutstylesheet"),
423 opaccolorstylesheet => "" . C4::Context->preference("opaccolorstylesheet"),
424 opacstylesheet => "" . C4::Context->preference("opacstylesheet"),
425 opacbookbag => "" . C4::Context->preference("opacbookbag"),
426 opaccredits => "" . C4::Context->preference("opaccredits"),
427 opacheader => "" . C4::Context->preference("opacheader"),
428 opaclanguagesdisplay => "" . C4::Context->preference("opaclanguagesdisplay"),
429 opacreadinghistory => C4::Context->preference("opacreadinghistory"),
430 opacsmallimage => "" . C4::Context->preference("opacsmallimage"),
431 opacuserjs => C4::Context->preference("opacuserjs"),
432 opacuserlogin => "" . C4::Context->preference("opacuserlogin"),
433 reviewson => C4::Context->preference("reviewson"),
434 suggestion => "" . C4::Context->preference("suggestion"),
435 virtualshelves => "" . C4::Context->preference("virtualshelves"),
436 OPACSerialIssueDisplayCount => C4::Context->preference("OPACSerialIssueDisplayCount"),
439 $template->param(listloop=>[{shelfname=>"Freelist", shelfnumber=>110}]);
440 return ( $template, $borrowernumber, $cookie, $flags);
443 =item checkauth
445 ($userid, $cookie, $sessionID) = &checkauth($query, $noauth, $flagsrequired, $type);
447 Verifies that the user is authorized to run this script. If
448 the user is authorized, a (userid, cookie, session-id, flags)
449 quadruple is returned. If the user is not authorized but does
450 not have the required privilege (see $flagsrequired below), it
451 displays an error page and exits. Otherwise, it displays the
452 login page and exits.
454 Note that C<&checkauth> will return if and only if the user
455 is authorized, so it should be called early on, before any
456 unfinished operations (e.g., if you've opened a file, then
457 C<&checkauth> won't close it for you).
459 C<$query> is the CGI object for the script calling C<&checkauth>.
461 The C<$noauth> argument is optional. If it is set, then no
462 authorization is required for the script.
464 C<&checkauth> fetches user and session information from C<$query> and
465 ensures that the user is authorized to run scripts that require
466 authorization.
468 The C<$flagsrequired> argument specifies the required privileges
469 the user must have if the username and password are correct.
470 It should be specified as a reference-to-hash; keys in the hash
471 should be the "flags" for the user, as specified in the Members
472 intranet module. Any key specified must correspond to a "flag"
473 in the userflags table. E.g., { circulate => 1 } would specify
474 that the user must have the "circulate" privilege in order to
475 proceed. To make sure that access control is correct, the
476 C<$flagsrequired> parameter must be specified correctly.
478 If the GranularPermissions system preference is ON, the
479 value of each key in the C<flagsrequired> hash takes on an additional
480 meaning, e.g.,
482 =item 1
484 The user must have access to all subfunctions of the module
485 specified by the hash key.
487 =item *
489 The user must have access to at least one subfunction of the module
490 specified by the hash key.
492 =item specific permission, e.g., 'export_catalog'
494 The user must have access to the specific subfunction list, which
495 must correspond to a row in the permissions table.
497 The C<$type> argument specifies whether the template should be
498 retrieved from the opac or intranet directory tree. "opac" is
499 assumed if it is not specified; however, if C<$type> is specified,
500 "intranet" is assumed if it is not "opac".
502 If C<$query> does not have a valid session ID associated with it
503 (i.e., the user has not logged in) or if the session has expired,
504 C<&checkauth> presents the user with a login page (from the point of
505 view of the original script, C<&checkauth> does not return). Once the
506 user has authenticated, C<&checkauth> restarts the original script
507 (this time, C<&checkauth> returns).
509 The login page is provided using a HTML::Template, which is set in the
510 systempreferences table or at the top of this file. The variable C<$type>
511 selects which template to use, either the opac or the intranet
512 authentification template.
514 C<&checkauth> returns a user ID, a cookie, and a session ID. The
515 cookie should be sent back to the browser; it verifies that the user
516 has authenticated.
518 =cut
520 sub _version_check ($$) {
521 my $type = shift;
522 my $query = shift;
523 my $version;
524 # If Version syspref is unavailable, it means Koha is beeing installed,
525 # and so we must redirect to OPAC maintenance page or to the WebInstaller
526 # also, if OpacMaintenance is ON, OPAC should redirect to maintenance
527 if (C4::Context->preference('OpacMaintenance') && $type eq 'opac') {
528 warn "OPAC Install required, redirecting to maintenance";
529 print $query->redirect("/cgi-bin/koha/maintenance.pl");
531 unless ($version = C4::Context->preference('Version')) { # assignment, not comparison
532 if ($type ne 'opac') {
533 warn "Install required, redirecting to Installer";
534 print $query->redirect("/cgi-bin/koha/installer/install.pl");
536 else {
537 warn "OPAC Install required, redirecting to maintenance";
538 print $query->redirect("/cgi-bin/koha/maintenance.pl");
540 exit;
543 # check that database and koha version are the same
544 # there is no DB version, it's a fresh install,
545 # go to web installer
546 # there is a DB version, compare it to the code version
547 my $kohaversion=C4::Context::KOHAVERSION;
548 # remove the 3 last . to have a Perl number
549 $kohaversion =~ s/(.*\..*)\.(.*)\.(.*)/$1$2$3/;
550 $debug and print STDERR "kohaversion : $kohaversion\n";
551 if ($version < $kohaversion){
552 my $warning = "Database update needed, redirecting to %s. Database is $version and Koha is $kohaversion";
553 if ($type ne 'opac'){
554 warn sprintf($warning, 'Installer');
555 print $query->redirect("/cgi-bin/koha/installer/install.pl?step=3");
556 } else {
557 warn sprintf("OPAC: " . $warning, 'maintenance');
558 print $query->redirect("/cgi-bin/koha/maintenance.pl");
560 exit;
564 sub _session_log {
565 (@_) or return 0;
566 open L, ">>/tmp/sessionlog" or warn "ERROR: Cannot append to /tmp/sessionlog";
567 printf L join("\n",@_);
568 close L;
571 sub checkauth {
572 my $query = shift;
573 $debug and warn "Checking Auth";
574 # $authnotrequired will be set for scripts which will run without authentication
575 my $authnotrequired = shift;
576 my $flagsrequired = shift;
577 my $type = shift;
578 $type = 'opac' unless $type;
580 my $dbh = C4::Context->dbh;
581 my $timeout = C4::Context->preference('timeout');
582 # days
583 if ($timeout =~ /(\d+)[dD]/) {
584 $timeout = $1 * 86400;
586 $timeout = 600 unless $timeout;
588 _version_check($type,$query);
589 # state variables
590 my $loggedin = 0;
591 my %info;
592 my ( $userid, $cookie, $sessionID, $flags, $barshelves, $pubshelves );
593 my $logout = $query->param('logout.x');
595 if ( $userid = $ENV{'REMOTE_USER'} ) {
596 # Using Basic Authentication, no cookies required
597 $cookie = $query->cookie(
598 -name => 'CGISESSID',
599 -value => '',
600 -expires => ''
602 $loggedin = 1;
604 elsif ( $sessionID = $query->cookie("CGISESSID")) { # assignment, not comparison
605 my $session = get_session($sessionID);
606 C4::Context->_new_userenv($sessionID);
607 my ($ip, $lasttime, $sessiontype);
608 if ($session){
609 C4::Context::set_userenv(
610 $session->param('number'), $session->param('id'),
611 $session->param('cardnumber'), $session->param('firstname'),
612 $session->param('surname'), $session->param('branch'),
613 $session->param('branchname'), $session->param('flags'),
614 $session->param('emailaddress'), $session->param('branchprinter')
616 C4::Context::set_shelves_userenv('bar',$session->param('barshelves'));
617 C4::Context::set_shelves_userenv('pub',$session->param('pubshelves'));
618 C4::Context::set_shelves_userenv('tot',$session->param('totshelves'));
619 $debug and printf STDERR "AUTH_SESSION: (%s)\t%s %s - %s\n", map {$session->param($_)} qw(cardnumber firstname surname branch) ;
620 $ip = $session->param('ip');
621 $lasttime = $session->param('lasttime');
622 $userid = $session->param('id');
623 $sessiontype = $session->param('sessiontype');
625 if ( ($query->param('koha_login_context')) && ($query->param('userid') ne $session->param('id')) ) {
626 #if a user enters an id ne to the id in the current session, we need to log them in...
627 #first we need to clear the anonymous session...
628 $debug and warn "query id = " . $query->param('userid') . " but session id = " . $session->param('id');
629 $session->flush;
630 $session->delete();
631 C4::Context->_unset_userenv($sessionID);
632 $sessionID = undef;
633 $userid = undef;
635 elsif ($logout) {
636 # voluntary logout the user
637 $session->flush;
638 $session->delete();
639 C4::Context->_unset_userenv($sessionID);
640 _session_log(sprintf "%20s from %16s logged out at %30s (manually).\n", $userid,$ip,(strftime "%c",localtime));
641 $sessionID = undef;
642 $userid = undef;
644 if ($cas and $caslogout) {
645 logout_cas($query);
648 elsif ( $lasttime < time() - $timeout ) {
649 # timed logout
650 $info{'timed_out'} = 1;
651 $session->delete();
652 C4::Context->_unset_userenv($sessionID);
653 _session_log(sprintf "%20s from %16s logged out at %30s (inactivity).\n", $userid,$ip,(strftime "%c",localtime));
654 $userid = undef;
655 $sessionID = undef;
657 elsif ( $ip ne $ENV{'REMOTE_ADDR'} ) {
658 # Different ip than originally logged in from
659 $info{'oldip'} = $ip;
660 $info{'newip'} = $ENV{'REMOTE_ADDR'};
661 $info{'different_ip'} = 1;
662 $session->delete();
663 C4::Context->_unset_userenv($sessionID);
664 _session_log(sprintf "%20s from %16s logged out at %30s (ip changed to %16s).\n", $userid,$ip,(strftime "%c",localtime), $info{'newip'});
665 $sessionID = undef;
666 $userid = undef;
668 else {
669 $cookie = $query->cookie( CGISESSID => $session->id );
670 $session->param('lasttime',time());
671 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...
672 $flags = haspermission($userid, $flagsrequired);
673 if ($flags) {
674 $loggedin = 1;
675 } else {
676 $info{'nopermission'} = 1;
681 unless ($userid || $sessionID) {
682 #we initiate a session prior to checking for a username to allow for anonymous sessions...
683 my $session = get_session("") or die "Auth ERROR: Cannot get_session()";
684 my $sessionID = $session->id;
685 C4::Context->_new_userenv($sessionID);
686 $cookie = $query->cookie(CGISESSID => $sessionID);
687 $userid = $query->param('userid');
688 if ($cas || $userid) {
689 my $password = $query->param('password');
690 my ($return, $cardnumber);
691 if ($cas && $query->param('ticket')) {
692 my $retuserid;
693 ( $return, $cardnumber, $retuserid ) = checkpw( $dbh, $userid, $password, $query );
694 $userid = $retuserid;
695 $info{'invalidCasLogin'} = 1 unless ($return);
696 } else {
697 ( $return, $cardnumber ) = checkpw( $dbh, $userid, $password, $query );
699 if ($return) {
700 _session_log(sprintf "%20s from %16s logged in at %30s.\n", $userid,$ENV{'REMOTE_ADDR'},localtime);
701 if ( $flags = haspermission( $dbh, $userid, $flagsrequired ) ) {
702 $loggedin = 1;
704 else {
705 $info{'nopermission'} = 1;
706 C4::Context->_unset_userenv($sessionID);
709 my ($borrowernumber, $firstname, $surname, $userflags,
710 $branchcode, $branchname, $branchprinter, $emailaddress);
712 if ( $return == 1 ) {
713 my $select = "
714 SELECT borrowernumber, firstname, surname, flags, borrowers.branchcode,
715 branches.branchname as branchname,
716 branches.branchprinter as branchprinter,
717 email
718 FROM borrowers
719 LEFT JOIN branches on borrowers.branchcode=branches.branchcode
721 my $sth = $dbh->prepare("$select where userid=?");
722 $sth->execute($userid);
723 unless ($sth->rows) {
724 $debug and print STDERR "AUTH_1: no rows for userid='$userid'\n";
725 $sth = $dbh->prepare("$select where cardnumber=?");
726 $sth->execute($cardnumber);
727 unless ($sth->rows) {
728 $debug and print STDERR "AUTH_2a: no rows for cardnumber='$cardnumber'\n";
729 $sth->execute($userid);
730 unless ($sth->rows) {
731 $debug and print STDERR "AUTH_2b: no rows for userid='$userid' AS cardnumber\n";
735 if ($sth->rows) {
736 ($borrowernumber, $firstname, $surname, $userflags,
737 $branchcode, $branchname, $branchprinter, $emailaddress) = $sth->fetchrow;
738 $debug and print STDERR "AUTH_3 results: " .
739 "$cardnumber,$borrowernumber,$userid,$firstname,$surname,$userflags,$branchcode,$emailaddress\n";
740 } else {
741 print STDERR "AUTH_3: no results for userid='$userid', cardnumber='$cardnumber'.\n";
744 # launch a sequence to check if we have a ip for the branch, i
745 # if we have one we replace the branchcode of the userenv by the branch bound in the ip.
747 my $ip = $ENV{'REMOTE_ADDR'};
748 # if they specify at login, use that
749 if ($query->param('branch')) {
750 $branchcode = $query->param('branch');
751 $branchname = GetBranchName($branchcode);
753 my $branches = GetBranches();
754 if (C4::Context->boolean_preference('IndependantBranches') && C4::Context->boolean_preference('Autolocation')){
755 # we have to check they are coming from the right ip range
756 my $domain = $branches->{$branchcode}->{'branchip'};
757 if ($ip !~ /^$domain/){
758 $loggedin=0;
759 $info{'wrongip'} = 1;
763 my @branchesloop;
764 foreach my $br ( keys %$branches ) {
765 # now we work with the treatment of ip
766 my $domain = $branches->{$br}->{'branchip'};
767 if ( $domain && $ip =~ /^$domain/ ) {
768 $branchcode = $branches->{$br}->{'branchcode'};
770 # new op dev : add the branchprinter and branchname in the cookie
771 $branchprinter = $branches->{$br}->{'branchprinter'};
772 $branchname = $branches->{$br}->{'branchname'};
775 $session->param('number',$borrowernumber);
776 $session->param('id',$userid);
777 $session->param('cardnumber',$cardnumber);
778 $session->param('firstname',$firstname);
779 $session->param('surname',$surname);
780 $session->param('branch',$branchcode);
781 $session->param('branchname',$branchname);
782 $session->param('flags',$userflags);
783 $session->param('emailaddress',$emailaddress);
784 $session->param('ip',$session->remote_addr());
785 $session->param('lasttime',time());
786 $debug and printf STDERR "AUTH_4: (%s)\t%s %s - %s\n", map {$session->param($_)} qw(cardnumber firstname surname branch) ;
788 elsif ( $return == 2 ) {
789 #We suppose the user is the superlibrarian
790 $borrowernumber = 0;
791 $session->param('number',0);
792 $session->param('id',C4::Context->config('user'));
793 $session->param('cardnumber',C4::Context->config('user'));
794 $session->param('firstname',C4::Context->config('user'));
795 $session->param('surname',C4::Context->config('user'));
796 $session->param('branch','NO_LIBRARY_SET');
797 $session->param('branchname','NO_LIBRARY_SET');
798 $session->param('flags',1);
799 $session->param('emailaddress', C4::Context->preference('KohaAdminEmailAddress'));
800 $session->param('ip',$session->remote_addr());
801 $session->param('lasttime',time());
803 C4::Context::set_userenv(
804 $session->param('number'), $session->param('id'),
805 $session->param('cardnumber'), $session->param('firstname'),
806 $session->param('surname'), $session->param('branch'),
807 $session->param('branchname'), $session->param('flags'),
808 $session->param('emailaddress'), $session->param('branchprinter')
811 # Grab borrower's shelves and public shelves and add them to the session
812 # $row_count determines how many records are returned from the db query
813 # and the number of lists to be displayed of each type in the 'Lists' button drop down
814 my $row_count = 10; # FIXME:This probably should be a syspref
815 my ($total, $totshelves, $barshelves, $pubshelves);
816 ($barshelves, $totshelves) = C4::VirtualShelves::GetRecentShelves(1, $row_count, $borrowernumber);
817 $total->{'bartotal'} = $totshelves;
818 ($pubshelves, $totshelves) = C4::VirtualShelves::GetRecentShelves(2, $row_count, undef);
819 $total->{'pubtotal'} = $totshelves;
820 $session->param('barshelves', $barshelves->[0]);
821 $session->param('pubshelves', $pubshelves->[0]);
822 $session->param('totshelves', $total);
824 C4::Context::set_shelves_userenv('bar',$barshelves->[0]);
825 C4::Context::set_shelves_userenv('pub',$pubshelves->[0]);
826 C4::Context::set_shelves_userenv('tot',$total);
828 else {
829 if ($userid) {
830 $info{'invalid_username_or_password'} = 1;
831 C4::Context->_unset_userenv($sessionID);
834 } # END if ( $userid = $query->param('userid') )
835 elsif ($type eq "opac") {
836 # if we are here this is an anonymous session; add public lists to it and a few other items...
837 # anonymous sessions are created only for the OPAC
838 $debug and warn "Initiating an anonymous session...";
840 # Grab the public shelves and add to the session...
841 my $row_count = 20; # FIXME:This probably should be a syspref
842 my ($total, $totshelves, $pubshelves);
843 ($pubshelves, $totshelves) = C4::VirtualShelves::GetRecentShelves(2, $row_count, undef);
844 $total->{'pubtotal'} = $totshelves;
845 $session->param('pubshelves', $pubshelves->[0]);
846 $session->param('totshelves', $total);
847 C4::Context::set_shelves_userenv('pub',$pubshelves->[0]);
848 C4::Context::set_shelves_userenv('tot',$total);
850 # setting a couple of other session vars...
851 $session->param('ip',$session->remote_addr());
852 $session->param('lasttime',time());
853 $session->param('sessiontype','anon');
855 } # END unless ($userid)
856 my $insecure = C4::Context->boolean_preference('insecure');
858 # finished authentification, now respond
859 if ( $loggedin || $authnotrequired || ( defined($insecure) && $insecure ) )
861 # successful login
862 unless ($cookie) {
863 $cookie = $query->cookie( CGISESSID => '' );
865 return ( $userid, $cookie, $sessionID, $flags );
870 # AUTH rejected, show the login/password template, after checking the DB.
874 # get the inputs from the incoming query
875 my @inputs = ();
876 foreach my $name ( param $query) {
877 (next) if ( $name eq 'userid' || $name eq 'password' || $name eq 'ticket' );
878 my $value = $query->param($name);
879 push @inputs, { name => $name, value => $value };
881 # get the branchloop, which we need for authentication
882 my $branches = GetBranches();
883 my @branch_loop;
884 for my $branch_hash (sort keys %$branches) {
885 push @branch_loop, {branchcode => "$branch_hash", branchname => $branches->{$branch_hash}->{'branchname'}, };
888 my $template_name = ( $type eq 'opac' ) ? 'opac-auth.tmpl' : 'auth.tmpl';
889 my $template = gettemplate( $template_name, $type, $query );
890 $template->param(branchloop => \@branch_loop,);
891 $template->param(
892 login => 1,
893 INPUTS => \@inputs,
894 casAuthentication => C4::Context->preference("casAuthentication"),
895 suggestion => C4::Context->preference("suggestion"),
896 virtualshelves => C4::Context->preference("virtualshelves"),
897 LibraryName => C4::Context->preference("LibraryName"),
898 opacuserlogin => C4::Context->preference("opacuserlogin"),
899 OpacNav => C4::Context->preference("OpacNav"),
900 opaccredits => C4::Context->preference("opaccredits"),
901 opacreadinghistory => C4::Context->preference("opacreadinghistory"),
902 opacsmallimage => C4::Context->preference("opacsmallimage"),
903 opaclayoutstylesheet => C4::Context->preference("opaclayoutstylesheet"),
904 opaccolorstylesheet => C4::Context->preference("opaccolorstylesheet"),
905 opaclanguagesdisplay => C4::Context->preference("opaclanguagesdisplay"),
906 opacuserjs => C4::Context->preference("opacuserjs"),
907 opacbookbag => "" . C4::Context->preference("opacbookbag"),
908 OpacCloud => C4::Context->preference("OpacCloud"),
909 OpacTopissue => C4::Context->preference("OpacTopissue"),
910 OpacAuthorities => C4::Context->preference("OpacAuthorities"),
911 OpacBrowser => C4::Context->preference("OpacBrowser"),
912 opacheader => C4::Context->preference("opacheader"),
913 TagsEnabled => C4::Context->preference("TagsEnabled"),
914 OPACUserCSS => C4::Context->preference("OPACUserCSS"),
915 intranetcolorstylesheet =>
916 C4::Context->preference("intranetcolorstylesheet"),
917 intranetstylesheet => C4::Context->preference("intranetstylesheet"),
918 intranetbookbag => C4::Context->preference("intranetbookbag"),
919 IntranetNav => C4::Context->preference("IntranetNav"),
920 intranetuserjs => C4::Context->preference("intranetuserjs"),
921 TemplateEncoding => C4::Context->preference("TemplateEncoding"),
922 IndependantBranches=> C4::Context->preference("IndependantBranches"),
923 AutoLocation => C4::Context->preference("AutoLocation"),
924 wrongip => $info{'wrongip'}
926 $template->param( loginprompt => 1 ) unless $info{'nopermission'};
928 if ($cas) {
929 $template->param(
930 casServerUrl => login_cas_url(),
931 invalidCasLogin => $info{'invalidCasLogin'}
935 my $self_url = $query->url( -absolute => 1 );
936 $template->param(
937 url => $self_url,
938 LibraryName => C4::Context->preference("LibraryName"),
940 $template->param( \%info );
941 # $cookie = $query->cookie(CGISESSID => $session->id
942 # );
943 print $query->header(
944 -type => 'text/html',
945 -charset => 'utf-8',
946 -cookie => $cookie
948 $template->output;
949 exit;
952 =item check_api_auth
954 ($status, $cookie, $sessionId) = check_api_auth($query, $userflags);
956 Given a CGI query containing the parameters 'userid' and 'password' and/or a session
957 cookie, determine if the user has the privileges specified by C<$userflags>.
959 C<check_api_auth> is is meant for authenticating users of web services, and
960 consequently will always return and will not attempt to redirect the user
961 agent.
963 If a valid session cookie is already present, check_api_auth will return a status
964 of "ok", the cookie, and the Koha session ID.
966 If no session cookie is present, check_api_auth will check the 'userid' and 'password
967 parameters and create a session cookie and Koha session if the supplied credentials
968 are OK.
970 Possible return values in C<$status> are:
972 =over 4
974 =item "ok" -- user authenticated; C<$cookie> and C<$sessionid> have valid values.
976 =item "failed" -- credentials are not correct; C<$cookie> and C<$sessionid> are undef
978 =item "maintenance" -- DB is in maintenance mode; no login possible at the moment
980 =item "expired -- session cookie has expired; API user should resubmit userid and password
982 =back
984 =cut
986 sub check_api_auth {
987 my $query = shift;
988 my $flagsrequired = shift;
990 my $dbh = C4::Context->dbh;
991 my $timeout = C4::Context->preference('timeout');
992 $timeout = 600 unless $timeout;
994 unless (C4::Context->preference('Version')) {
995 # database has not been installed yet
996 return ("maintenance", undef, undef);
998 my $kohaversion=C4::Context::KOHAVERSION;
999 $kohaversion =~ s/(.*\..*)\.(.*)\.(.*)/$1$2$3/;
1000 if (C4::Context->preference('Version') < $kohaversion) {
1001 # database in need of version update; assume that
1002 # no API should be called while databsae is in
1003 # this condition.
1004 return ("maintenance", undef, undef);
1007 # FIXME -- most of what follows is a copy-and-paste
1008 # of code from checkauth. There is an obvious need
1009 # for refactoring to separate the various parts of
1010 # the authentication code, but as of 2007-11-19 this
1011 # is deferred so as to not introduce bugs into the
1012 # regular authentication code for Koha 3.0.
1014 # see if we have a valid session cookie already
1015 # however, if a userid parameter is present (i.e., from
1016 # a form submission, assume that any current cookie
1017 # is to be ignored
1018 my $sessionID = undef;
1019 unless ($query->param('userid')) {
1020 $sessionID = $query->cookie("CGISESSID");
1022 if ($sessionID) {
1023 my $session = get_session($sessionID);
1024 C4::Context->_new_userenv($sessionID);
1025 if ($session) {
1026 C4::Context::set_userenv(
1027 $session->param('number'), $session->param('id'),
1028 $session->param('cardnumber'), $session->param('firstname'),
1029 $session->param('surname'), $session->param('branch'),
1030 $session->param('branchname'), $session->param('flags'),
1031 $session->param('emailaddress'), $session->param('branchprinter')
1034 my $ip = $session->param('ip');
1035 my $lasttime = $session->param('lasttime');
1036 my $userid = $session->param('id');
1037 if ( $lasttime < time() - $timeout ) {
1038 # time out
1039 $session->delete();
1040 C4::Context->_unset_userenv($sessionID);
1041 $userid = undef;
1042 $sessionID = undef;
1043 return ("expired", undef, undef);
1044 } elsif ( $ip ne $ENV{'REMOTE_ADDR'} ) {
1045 # IP address changed
1046 $session->delete();
1047 C4::Context->_unset_userenv($sessionID);
1048 $userid = undef;
1049 $sessionID = undef;
1050 return ("expired", undef, undef);
1051 } else {
1052 my $cookie = $query->cookie( CGISESSID => $session->id );
1053 $session->param('lasttime',time());
1054 my $flags = haspermission($userid, $flagsrequired);
1055 if ($flags) {
1056 return ("ok", $cookie, $sessionID);
1057 } else {
1058 $session->delete();
1059 C4::Context->_unset_userenv($sessionID);
1060 $userid = undef;
1061 $sessionID = undef;
1062 return ("failed", undef, undef);
1065 } else {
1066 return ("expired", undef, undef);
1068 } else {
1069 # new login
1070 my $userid = $query->param('userid');
1071 my $password = $query->param('password');
1072 unless ($userid and $password) {
1073 # caller did something wrong, fail the authenticateion
1074 return ("failed", undef, undef);
1076 my ($return, $cardnumber);
1077 if ($cas && $query->param('ticket')) {
1078 my $retuserid;
1079 ( $return, $cardnumber, $retuserid ) = checkpw( $dbh, $userid, $password, $query );
1080 $userid = $retuserid;
1081 } else {
1082 ( $return, $cardnumber ) = checkpw( $dbh, $userid, $password, $query );
1084 if ($return and haspermission( $dbh, $userid, $flagsrequired)) {
1085 my $session = get_session("");
1086 return ("failed", undef, undef) unless $session;
1088 my $sessionID = $session->id;
1089 C4::Context->_new_userenv($sessionID);
1090 my $cookie = $query->cookie(CGISESSID => $sessionID);
1091 if ( $return == 1 ) {
1092 my (
1093 $borrowernumber, $firstname, $surname,
1094 $userflags, $branchcode, $branchname,
1095 $branchprinter, $emailaddress
1097 my $sth =
1098 $dbh->prepare(
1099 "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=?"
1101 $sth->execute($userid);
1103 $borrowernumber, $firstname, $surname,
1104 $userflags, $branchcode, $branchname,
1105 $branchprinter, $emailaddress
1106 ) = $sth->fetchrow if ( $sth->rows );
1108 unless ($sth->rows ) {
1109 my $sth = $dbh->prepare(
1110 "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=?"
1112 $sth->execute($cardnumber);
1114 $borrowernumber, $firstname, $surname,
1115 $userflags, $branchcode, $branchname,
1116 $branchprinter, $emailaddress
1117 ) = $sth->fetchrow if ( $sth->rows );
1119 unless ( $sth->rows ) {
1120 $sth->execute($userid);
1122 $borrowernumber, $firstname, $surname, $userflags,
1123 $branchcode, $branchname, $branchprinter, $emailaddress
1124 ) = $sth->fetchrow if ( $sth->rows );
1128 my $ip = $ENV{'REMOTE_ADDR'};
1129 # if they specify at login, use that
1130 if ($query->param('branch')) {
1131 $branchcode = $query->param('branch');
1132 $branchname = GetBranchName($branchcode);
1134 my $branches = GetBranches();
1135 my @branchesloop;
1136 foreach my $br ( keys %$branches ) {
1137 # now we work with the treatment of ip
1138 my $domain = $branches->{$br}->{'branchip'};
1139 if ( $domain && $ip =~ /^$domain/ ) {
1140 $branchcode = $branches->{$br}->{'branchcode'};
1142 # new op dev : add the branchprinter and branchname in the cookie
1143 $branchprinter = $branches->{$br}->{'branchprinter'};
1144 $branchname = $branches->{$br}->{'branchname'};
1147 $session->param('number',$borrowernumber);
1148 $session->param('id',$userid);
1149 $session->param('cardnumber',$cardnumber);
1150 $session->param('firstname',$firstname);
1151 $session->param('surname',$surname);
1152 $session->param('branch',$branchcode);
1153 $session->param('branchname',$branchname);
1154 $session->param('flags',$userflags);
1155 $session->param('emailaddress',$emailaddress);
1156 $session->param('ip',$session->remote_addr());
1157 $session->param('lasttime',time());
1158 } elsif ( $return == 2 ) {
1159 #We suppose the user is the superlibrarian
1160 $session->param('number',0);
1161 $session->param('id',C4::Context->config('user'));
1162 $session->param('cardnumber',C4::Context->config('user'));
1163 $session->param('firstname',C4::Context->config('user'));
1164 $session->param('surname',C4::Context->config('user'));
1165 $session->param('branch','NO_LIBRARY_SET');
1166 $session->param('branchname','NO_LIBRARY_SET');
1167 $session->param('flags',1);
1168 $session->param('emailaddress', C4::Context->preference('KohaAdminEmailAddress'));
1169 $session->param('ip',$session->remote_addr());
1170 $session->param('lasttime',time());
1172 C4::Context::set_userenv(
1173 $session->param('number'), $session->param('id'),
1174 $session->param('cardnumber'), $session->param('firstname'),
1175 $session->param('surname'), $session->param('branch'),
1176 $session->param('branchname'), $session->param('flags'),
1177 $session->param('emailaddress'), $session->param('branchprinter')
1179 return ("ok", $cookie, $sessionID);
1180 } else {
1181 return ("failed", undef, undef);
1186 =item check_cookie_auth
1188 ($status, $sessionId) = check_api_auth($cookie, $userflags);
1190 Given a CGISESSID cookie set during a previous login to Koha, determine
1191 if the user has the privileges specified by C<$userflags>.
1193 C<check_cookie_auth> is meant for authenticating special services
1194 such as tools/upload-file.pl that are invoked by other pages that
1195 have been authenticated in the usual way.
1197 Possible return values in C<$status> are:
1199 =over 4
1201 =item "ok" -- user authenticated; C<$sessionID> have valid values.
1203 =item "failed" -- credentials are not correct; C<$sessionid> are undef
1205 =item "maintenance" -- DB is in maintenance mode; no login possible at the moment
1207 =item "expired -- session cookie has expired; API user should resubmit userid and password
1209 =back
1211 =cut
1213 sub check_cookie_auth {
1214 my $cookie = shift;
1215 my $flagsrequired = shift;
1217 my $dbh = C4::Context->dbh;
1218 my $timeout = C4::Context->preference('timeout');
1219 $timeout = 600 unless $timeout;
1221 unless (C4::Context->preference('Version')) {
1222 # database has not been installed yet
1223 return ("maintenance", undef);
1225 my $kohaversion=C4::Context::KOHAVERSION;
1226 $kohaversion =~ s/(.*\..*)\.(.*)\.(.*)/$1$2$3/;
1227 if (C4::Context->preference('Version') < $kohaversion) {
1228 # database in need of version update; assume that
1229 # no API should be called while databsae is in
1230 # this condition.
1231 return ("maintenance", undef);
1234 # FIXME -- most of what follows is a copy-and-paste
1235 # of code from checkauth. There is an obvious need
1236 # for refactoring to separate the various parts of
1237 # the authentication code, but as of 2007-11-23 this
1238 # is deferred so as to not introduce bugs into the
1239 # regular authentication code for Koha 3.0.
1241 # see if we have a valid session cookie already
1242 # however, if a userid parameter is present (i.e., from
1243 # a form submission, assume that any current cookie
1244 # is to be ignored
1245 unless (defined $cookie and $cookie) {
1246 return ("failed", undef);
1248 my $sessionID = $cookie;
1249 my $session = get_session($sessionID);
1250 C4::Context->_new_userenv($sessionID);
1251 if ($session) {
1252 C4::Context::set_userenv(
1253 $session->param('number'), $session->param('id'),
1254 $session->param('cardnumber'), $session->param('firstname'),
1255 $session->param('surname'), $session->param('branch'),
1256 $session->param('branchname'), $session->param('flags'),
1257 $session->param('emailaddress'), $session->param('branchprinter')
1260 my $ip = $session->param('ip');
1261 my $lasttime = $session->param('lasttime');
1262 my $userid = $session->param('id');
1263 if ( $lasttime < time() - $timeout ) {
1264 # time out
1265 $session->delete();
1266 C4::Context->_unset_userenv($sessionID);
1267 $userid = undef;
1268 $sessionID = undef;
1269 return ("expired", undef);
1270 } elsif ( $ip ne $ENV{'REMOTE_ADDR'} ) {
1271 # IP address changed
1272 $session->delete();
1273 C4::Context->_unset_userenv($sessionID);
1274 $userid = undef;
1275 $sessionID = undef;
1276 return ("expired", undef);
1277 } else {
1278 $session->param('lasttime',time());
1279 my $flags = haspermission($userid, $flagsrequired);
1280 if ($flags) {
1281 return ("ok", $sessionID);
1282 } else {
1283 $session->delete();
1284 C4::Context->_unset_userenv($sessionID);
1285 $userid = undef;
1286 $sessionID = undef;
1287 return ("failed", undef);
1290 } else {
1291 return ("expired", undef);
1295 =item get_session
1297 use CGI::Session;
1298 my $session = get_session($sessionID);
1300 Given a session ID, retrieve the CGI::Session object used to store
1301 the session's state. The session object can be used to store
1302 data that needs to be accessed by different scripts during a
1303 user's session.
1305 If the C<$sessionID> parameter is an empty string, a new session
1306 will be created.
1308 =cut
1310 sub get_session {
1311 my $sessionID = shift;
1312 my $storage_method = C4::Context->preference('SessionStorage');
1313 my $dbh = C4::Context->dbh;
1314 my $session;
1315 if ($storage_method eq 'mysql'){
1316 $session = new CGI::Session("driver:MySQL;serializer:yaml;id:md5", $sessionID, {Handle=>$dbh});
1318 elsif ($storage_method eq 'Pg') {
1319 $session = new CGI::Session("driver:PostgreSQL;serializer:yaml;id:md5", $sessionID, {Handle=>$dbh});
1321 else {
1322 # catch all defaults to tmp should work on all systems
1323 $session = new CGI::Session("driver:File;serializer:yaml;id:md5", $sessionID, {Directory=>'/tmp'});
1325 return $session;
1328 sub checkpw {
1330 my ( $dbh, $userid, $password, $query ) = @_;
1331 if ($ldap) {
1332 $debug and print "## checkpw - checking LDAP\n";
1333 my ($retval,$retcard) = checkpw_ldap(@_); # EXTERNAL AUTH
1334 ($retval) and return ($retval,$retcard);
1337 if ($cas && $query->param('ticket')) {
1338 $debug and print STDERR "## checkpw - checking CAS\n";
1339 # In case of a CAS authentication, we use the ticket instead of the password
1340 my $ticket = $query->param('ticket');
1341 my ($retval,$retcard,$retuserid) = checkpw_cas($dbh, $ticket, $query); # EXTERNAL AUTH
1342 ($retval) and return ($retval,$retcard,$retuserid);
1343 return 0;
1346 # INTERNAL AUTH
1347 my $sth =
1348 $dbh->prepare(
1349 "select password,cardnumber,borrowernumber,userid,firstname,surname,branchcode,flags from borrowers where userid=?"
1351 $sth->execute($userid);
1352 if ( $sth->rows ) {
1353 my ( $md5password, $cardnumber, $borrowernumber, $userid, $firstname,
1354 $surname, $branchcode, $flags )
1355 = $sth->fetchrow;
1356 if ( md5_base64($password) eq $md5password and $md5password ne "!") {
1358 C4::Context->set_userenv( "$borrowernumber", $userid, $cardnumber,
1359 $firstname, $surname, $branchcode, $flags );
1360 return 1, $cardnumber;
1363 $sth =
1364 $dbh->prepare(
1365 "select password,cardnumber,borrowernumber,userid, firstname,surname,branchcode,flags from borrowers where cardnumber=?"
1367 $sth->execute($userid);
1368 if ( $sth->rows ) {
1369 my ( $md5password, $cardnumber, $borrowernumber, $userid, $firstname,
1370 $surname, $branchcode, $flags )
1371 = $sth->fetchrow;
1372 if ( md5_base64($password) eq $md5password ) {
1374 C4::Context->set_userenv( $borrowernumber, $userid, $cardnumber,
1375 $firstname, $surname, $branchcode, $flags );
1376 return 1, $userid;
1379 if ( $userid && $userid eq C4::Context->config('user')
1380 && "$password" eq C4::Context->config('pass') )
1383 # Koha superuser account
1384 # C4::Context->set_userenv(0,0,C4::Context->config('user'),C4::Context->config('user'),C4::Context->config('user'),"",1);
1385 return 2;
1387 if ( $userid && $userid eq 'demo'
1388 && "$password" eq 'demo'
1389 && C4::Context->config('demo') )
1392 # DEMO => the demo user is allowed to do everything (if demo set to 1 in koha.conf
1393 # some features won't be effective : modify systempref, modify MARC structure,
1394 return 2;
1396 return 0;
1399 =item getuserflags
1401 my $authflags = getuserflags($flags, $userid, [$dbh]);
1403 Translates integer flags into permissions strings hash.
1405 C<$flags> is the integer userflags value ( borrowers.userflags )
1406 C<$userid> is the members.userid, used for building subpermissions
1407 C<$authflags> is a hashref of permissions
1409 =cut
1411 sub getuserflags {
1412 my $flags = shift;
1413 my $userid = shift;
1414 my $dbh = @_ ? shift : C4::Context->dbh;
1415 my $userflags;
1416 $flags = 0 unless $flags;
1417 my $sth = $dbh->prepare("SELECT bit, flag, defaulton FROM userflags");
1418 $sth->execute;
1420 while ( my ( $bit, $flag, $defaulton ) = $sth->fetchrow ) {
1421 if ( ( $flags & ( 2**$bit ) ) || $defaulton ) {
1422 $userflags->{$flag} = 1;
1424 else {
1425 $userflags->{$flag} = 0;
1429 # get subpermissions and merge with top-level permissions
1430 my $user_subperms = get_user_subpermissions($userid);
1431 foreach my $module (keys %$user_subperms) {
1432 next if $userflags->{$module} == 1; # user already has permission for everything in this module
1433 $userflags->{$module} = $user_subperms->{$module};
1436 return $userflags;
1439 =item get_user_subpermissions
1441 =over 4
1443 my $user_perm_hashref = get_user_subpermissions($userid);
1445 =back
1447 Given the userid (note, not the borrowernumber) of a staff user,
1448 return a hashref of hashrefs of the specific subpermissions
1449 accorded to the user. An example return is
1452 tools => {
1453 export_catalog => 1,
1454 import_patrons => 1,
1458 The top-level hash-key is a module or function code from
1459 userflags.flag, while the second-level key is a code
1460 from permissions.
1462 The results of this function do not give a complete picture
1463 of the functions that a staff user can access; it is also
1464 necessary to check borrowers.flags.
1466 =cut
1468 sub get_user_subpermissions {
1469 my $userid = shift;
1471 my $dbh = C4::Context->dbh;
1472 my $sth = $dbh->prepare("SELECT flag, user_permissions.code
1473 FROM user_permissions
1474 JOIN permissions USING (module_bit, code)
1475 JOIN userflags ON (module_bit = bit)
1476 JOIN borrowers USING (borrowernumber)
1477 WHERE userid = ?");
1478 $sth->execute($userid);
1480 my $user_perms = {};
1481 while (my $perm = $sth->fetchrow_hashref) {
1482 $user_perms->{$perm->{'flag'}}->{$perm->{'code'}} = 1;
1484 return $user_perms;
1487 =item get_all_subpermissions
1489 =over 4
1491 my $perm_hashref = get_all_subpermissions();
1493 =back
1495 Returns a hashref of hashrefs defining all specific
1496 permissions currently defined. The return value
1497 has the same structure as that of C<get_user_subpermissions>,
1498 except that the innermost hash value is the description
1499 of the subpermission.
1501 =cut
1503 sub get_all_subpermissions {
1504 my $dbh = C4::Context->dbh;
1505 my $sth = $dbh->prepare("SELECT flag, code, description
1506 FROM permissions
1507 JOIN userflags ON (module_bit = bit)");
1508 $sth->execute();
1510 my $all_perms = {};
1511 while (my $perm = $sth->fetchrow_hashref) {
1512 $all_perms->{$perm->{'flag'}}->{$perm->{'code'}} = $perm->{'description'};
1514 return $all_perms;
1517 =item haspermission
1519 $flags = ($userid, $flagsrequired);
1521 C<$userid> the userid of the member
1522 C<$flags> is a hashref of required flags like C<$borrower-&lt;{authflags}>
1524 Returns member's flags or 0 if a permission is not met.
1526 =cut
1528 sub haspermission {
1529 my ($userid, $flagsrequired) = @_;
1530 my $sth = C4::Context->dbh->prepare("SELECT flags FROM borrowers WHERE userid=?");
1531 $sth->execute($userid);
1532 my $flags = getuserflags( $sth->fetchrow(), $userid );
1533 if ( $userid eq C4::Context->config('user') ) {
1534 # Super User Account from /etc/koha.conf
1535 $flags->{'superlibrarian'} = 1;
1537 elsif ( $userid eq 'demo' && C4::Context->config('demo') ) {
1538 # Demo user that can do "anything" (demo=1 in /etc/koha.conf)
1539 $flags->{'superlibrarian'} = 1;
1541 return $flags if $flags->{superlibrarian};
1542 foreach my $module ( keys %$flagsrequired ) {
1543 if (C4::Context->preference('GranularPermissions')) {
1544 my $subperm = $flagsrequired->{$module};
1545 if ($subperm eq '*') {
1546 return 0 unless ( $flags->{$module} == 1 or ref($flags->{$module}) );
1547 } else {
1548 return 0 unless ( $flags->{$module} == 1 or
1549 ( ref($flags->{$module}) and
1550 exists $flags->{$module}->{$subperm} and
1551 $flags->{$module}->{$subperm} == 1
1555 } else {
1556 return 0 unless ( $flags->{$module} );
1559 return $flags;
1560 #FIXME - This fcn should return the failed permission so a suitable error msg can be delivered.
1564 sub getborrowernumber {
1565 my ($userid) = @_;
1566 my $userenv = C4::Context->userenv;
1567 if ( defined( $userenv ) && ref( $userenv ) eq 'HASH' && $userenv->{number} ) {
1568 return $userenv->{number};
1570 my $dbh = C4::Context->dbh;
1571 for my $field ( 'userid', 'cardnumber' ) {
1572 my $sth =
1573 $dbh->prepare("select borrowernumber from borrowers where $field=?");
1574 $sth->execute($userid);
1575 if ( $sth->rows ) {
1576 my ($bnumber) = $sth->fetchrow;
1577 return $bnumber;
1580 return 0;
1583 END { } # module clean-up code here (global destructor)
1585 __END__
1587 =back
1589 =head1 SEE ALSO
1591 CGI(3)
1593 C4::Output(3)
1595 Digest::MD5(3)
1597 =cut