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