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