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