new C4::Auth::get_session for single place to get CGI::Session object
[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
35 # use utf8;
36 # use Net::LDAP;
37 # use Net::LDAP qw(:all);
38
39 use vars qw($VERSION @ISA @EXPORT @EXPORT_OK %EXPORT_TAGS);
40
41 # set the version for version checking
42 $VERSION = 3.00;
43
44 =head1 NAME
45
46 C4::Auth - Authenticates Koha users
47
48 =head1 SYNOPSIS
49
50   use CGI;
51   use C4::Auth;
52
53   my $query = new CGI;
54
55   my ($template, $borrowernumber, $cookie) 
56     = get_template_and_user(
57         {
58             template_name   => "opac-main.tmpl",
59             query           => $query,
60       type            => "opac",
61       authnotrequired => 1,
62       flagsrequired   => {borrow => 1},
63   }
64     );
65
66   print $query->header(
67     -type => 'utf-8',
68     -cookie => $cookie
69   ), $template->output;
70
71
72 =head1 DESCRIPTION
73
74     The main function of this module is to provide
75     authentification. However the get_template_and_user function has
76     been provided so that a users login information is passed along
77     automatically. This gets loaded into the template.
78
79 =head1 FUNCTIONS
80
81 =over 2
82
83 =cut
84
85 @ISA    = qw(Exporter);
86 @EXPORT = qw(
87   &checkauth
88   &get_template_and_user
89 );
90 @EXPORT_OK = qw(
91   &check_api_auth
92   &get_session
93 );
94
95 =item get_template_and_user
96
97   my ($template, $borrowernumber, $cookie)
98     = get_template_and_user(
99         {
100            template_name   => "opac-main.tmpl",
101            query           => $query,
102      type            => "opac",
103      authnotrequired => 1,
104      flagsrequired   => {borrow => 1},
105   }
106     );
107
108     This call passes the C<query>, C<flagsrequired> and C<authnotrequired>
109     to C<&checkauth> (in this module) to perform authentification.
110     See C<&checkauth> for an explanation of these parameters.
111
112     The C<template_name> is then used to find the correct template for
113     the page. The authenticated users details are loaded onto the
114     template in the HTML::Template LOOP variable C<USER_INFO>. Also the
115     C<sessionID> is passed to the template. This can be used in templates
116     if cookies are disabled. It needs to be put as and input to every
117     authenticated page.
118
119     More information on the C<gettemplate> sub can be found in the
120     Output.pm module.
121
122 =cut
123
124 sub get_template_and_user {
125     my $in       = shift;
126     my $template =
127       gettemplate( $in->{'template_name'}, $in->{'type'}, $in->{'query'} );
128     my ( $user, $cookie, $sessionID, $flags ) = checkauth(
129         $in->{'query'},
130         $in->{'authnotrequired'},
131         $in->{'flagsrequired'},
132         $in->{'type'}
133     ) unless ($in->{'template_name'}=~/maintenance/);
134
135     my $borrowernumber;
136     my $insecure = C4::Context->preference('insecure');
137     if ($user or $insecure) {
138
139                 # load the template variables for stylesheets and JavaScript
140                 $template->param( css_libs => $in->{'css_libs'} );
141                 $template->param( css_module => $in->{'css_module'} );
142                 $template->param( css_page => $in->{'css_page'} );
143                 $template->param( css_widgets => $in->{'css_widgets'} );
144
145         $template->param( js_libs => $in->{'js_libs'} );
146         $template->param( js_module => $in->{'js_module'} );
147         $template->param( js_page => $in->{'js_page'} );
148         $template->param( js_widgets => $in->{'js_widgets'} );
149
150                 # user info
151         $template->param( loggedinusername => $user );
152         $template->param( sessionID        => $sessionID );
153
154         $borrowernumber = getborrowernumber($user);
155         my ( $borr, $alternativeflags ) =
156           GetMemberDetails( $borrowernumber );
157         my @bordat;
158         $bordat[0] = $borr;
159         $template->param( "USER_INFO" => \@bordat );
160
161         # We are going to use the $flags returned by checkauth
162         # to create the template's parameters that will indicate
163         # which menus the user can access.
164         if (( $flags && $flags->{superlibrarian}==1) or $insecure==1) {
165             $template->param( CAN_user_circulate        => 1 );
166             $template->param( CAN_user_catalogue        => 1 );
167             $template->param( CAN_user_parameters       => 1 );
168             $template->param( CAN_user_borrowers        => 1 );
169             $template->param( CAN_user_permission       => 1 );
170             $template->param( CAN_user_reserveforothers => 1 );
171             $template->param( CAN_user_borrow           => 1 );
172             $template->param( CAN_user_editcatalogue    => 1 );
173             $template->param( CAN_user_updatecharge     => 1 );
174             $template->param( CAN_user_acquisition      => 1 );
175             $template->param( CAN_user_management       => 1 );
176             $template->param( CAN_user_tools            => 1 ); 
177             $template->param( CAN_user_editauthorities  => 1 );
178             $template->param( CAN_user_serials          => 1 );
179             $template->param( CAN_user_reports          => 1 );
180         }
181
182         if ( $flags && $flags->{circulate} == 1 ) {
183             $template->param( CAN_user_circulate => 1 );
184         }
185
186         if ( $flags && $flags->{catalogue} == 1 ) {
187             $template->param( CAN_user_catalogue => 1 );
188         }
189
190         if ( $flags && $flags->{parameters} == 1 ) {
191             $template->param( CAN_user_parameters => 1 );
192             $template->param( CAN_user_management => 1 );
193         }
194
195         if ( $flags && $flags->{borrowers} == 1 ) {
196             $template->param( CAN_user_borrowers => 1 );
197         }
198
199         if ( $flags && $flags->{permissions} == 1 ) {
200             $template->param( CAN_user_permission => 1 );
201         }
202
203         if ( $flags && $flags->{reserveforothers} == 1 ) {
204             $template->param( CAN_user_reserveforothers => 1 );
205         }
206
207         if ( $flags && $flags->{borrow} == 1 ) {
208             $template->param( CAN_user_borrow => 1 );
209         }
210
211         if ( $flags && $flags->{editcatalogue} == 1 ) {
212             $template->param( CAN_user_editcatalogue => 1 );
213         }
214
215         if ( $flags && $flags->{updatecharges} == 1 ) {
216             $template->param( CAN_user_updatecharge => 1 );
217         }
218
219         if ( $flags && $flags->{acquisition} == 1 ) {
220             $template->param( CAN_user_acquisition => 1 );
221         }
222
223         if ( $flags && $flags->{tools} == 1 ) {
224             $template->param( CAN_user_tools => 1 );
225         }
226   
227         if ( $flags && $flags->{editauthorities} == 1 ) {
228             $template->param( CAN_user_editauthorities => 1 );
229         }
230     
231         if ( $flags && $flags->{serials} == 1 ) {
232             $template->param( CAN_user_serials => 1 );
233         }
234
235         if ( $flags && $flags->{reports} == 1 ) {
236             $template->param( CAN_user_reports => 1 );
237         }
238     }
239     if ( $in->{'type'} eq "intranet" ) {
240         $template->param(
241             intranetcolorstylesheet => C4::Context->preference("intranetcolorstylesheet"),
242             intranetstylesheet => C4::Context->preference("intranetstylesheet"),
243             IntranetNav        => C4::Context->preference("IntranetNav"),
244             intranetuserjs     => C4::Context->preference("intranetuserjs"),
245             TemplateEncoding   => C4::Context->preference("TemplateEncoding"),
246             AmazonContent      => C4::Context->preference("AmazonContent"),
247             LibraryName        => C4::Context->preference("LibraryName"),
248             LoginBranchcode    => (C4::Context->userenv?C4::Context->userenv->{"branch"}:"insecure"),
249             LoginBranchname    => (C4::Context->userenv?C4::Context->userenv->{"branchname"}:"insecure"),
250             LoginFirstname     => (C4::Context->userenv?C4::Context->userenv->{"firstname"}:"Bel"),
251             LoginSurname       => C4::Context->userenv?C4::Context->userenv->{"surname"}:"Inconnu", 
252             AutoLocation       => C4::Context->preference("AutoLocation"),
253             hide_marc          => C4::Context->preference("hide_marc"),
254             patronimages       => C4::Context->preference("patronimages"),
255             "BiblioDefaultView".C4::Context->preference("IntranetBiblioDefaultView") => 1,
256             advancedMARCEditor      => C4::Context->preference("advancedMARCEditor"),
257             suggestion              => C4::Context->preference("suggestion"),
258             virtualshelves          => C4::Context->preference("virtualshelves"),
259             LibraryName             => C4::Context->preference("LibraryName"),
260             KohaAdminEmailAddress   => "" . C4::Context->preference("KohaAdminEmailAddress"),
261             IntranetmainUserblock       => C4::Context->preference("IntranetmainUserblock"),
262             IndependantBranches     => C4::Context->preference("IndependantBranches"),
263                         CircAutocompl => C4::Context->preference("CircAutocompl"),
264                         yuipath => C4::Context->preference("yuipath"),
265         );
266     }
267     else {
268         warn "template type should be OPAC, here it is=[" . $in->{'type'} . "]" unless ( $in->{'type'} eq 'opac' );
269         my $LibraryNameTitle = C4::Context->preference("LibraryName");
270         $LibraryNameTitle =~ s/<(?:\/?)(?:br|p)\s*(?:\/?)>/ /sgi;
271         $LibraryNameTitle =~ s/<(?:[^<>'"]|'(?:[^']*)'|"(?:[^"]*)")*>//sg;
272   $template->param(
273             KohaAdminEmailAddress  => "" . C4::Context->preference("KohaAdminEmailAddress"),
274                         AnonSuggestions =>  "" . C4::Context->preference("AnonSuggestions"),
275             suggestion             => "" . C4::Context->preference("suggestion"),
276             virtualshelves         => "" . C4::Context->preference("virtualshelves"),
277             OpacNav                => "" . C4::Context->preference("OpacNav"),
278             opacheader             => "" . C4::Context->preference("opacheader"),
279             opaccredits            => "" . C4::Context->preference("opaccredits"),
280             opacsmallimage         => "" . C4::Context->preference("opacsmallimage"),
281             opaclargeimage         => "" . C4::Context->preference("opaclargeimage"),
282             opaclayoutstylesheet   => "". C4::Context->preference("opaclayoutstylesheet"),
283             opaccolorstylesheet    => "". C4::Context->preference("opaccolorstylesheet"),
284             opaclanguagesdisplay   => "". C4::Context->preference("opaclanguagesdisplay"),
285             opacuserlogin          => "" . C4::Context->preference("opacuserlogin"),
286             opacbookbag            => "" . C4::Context->preference("opacbookbag"),
287             TemplateEncoding       => "". C4::Context->preference("TemplateEncoding"),
288             AmazonContent          => "" . C4::Context->preference("AmazonContent"),
289             LibraryName            => "" . C4::Context->preference("LibraryName"),
290             LibraryNameTitle       => "" . $LibraryNameTitle,
291             LoginBranchcode        => (C4::Context->userenv?C4::Context->userenv->{"branch"}:"insecure"),
292             LoginBranchname        => C4::Context->userenv?C4::Context->userenv->{"branchname"}:"", 
293             LoginFirstname        => (C4::Context->userenv?C4::Context->userenv->{"firstname"}:"Bel"),
294             LoginSurname        => C4::Context->userenv?C4::Context->userenv->{"surname"}:"Inconnu", 
295             OpacPasswordChange     => C4::Context->preference("OpacPasswordChange"),
296             opacreadinghistory     => C4::Context->preference("opacreadinghistory"),
297             opacuserjs             => C4::Context->preference("opacuserjs"),
298             OpacCloud              => C4::Context->preference("OpacCloud"),
299             OpacTopissue           => C4::Context->preference("OpacTopissue"),
300             OpacAuthorities        => C4::Context->preference("OpacAuthorities"),
301             OpacBrowser            => C4::Context->preference("OpacBrowser"),
302             RequestOnOpac          => C4::Context->preference("RequestOnOpac"),
303             reviewson              => C4::Context->preference("reviewson"),
304             hide_marc              => C4::Context->preference("hide_marc"),
305             patronimages           => C4::Context->preference("patronimages"),
306             mylibraryfirst   => C4::Context->preference("SearchMyLibraryFirst"),
307             "BiblioDefaultView".C4::Context->preference("BiblioDefaultView") => 1,
308         );
309     }
310     return ( $template, $borrowernumber, $cookie );
311 }
312
313 =item checkauth
314
315   ($userid, $cookie, $sessionID) = &checkauth($query, $noauth, $flagsrequired, $type);
316
317 Verifies that the user is authorized to run this script.  If
318 the user is authorized, a (userid, cookie, session-id, flags)
319 quadruple is returned.  If the user is not authorized but does
320 not have the required privilege (see $flagsrequired below), it
321 displays an error page and exits.  Otherwise, it displays the
322 login page and exits.
323
324 Note that C<&checkauth> will return if and only if the user
325 is authorized, so it should be called early on, before any
326 unfinished operations (e.g., if you've opened a file, then
327 C<&checkauth> won't close it for you).
328
329 C<$query> is the CGI object for the script calling C<&checkauth>.
330
331 The C<$noauth> argument is optional. If it is set, then no
332 authorization is required for the script.
333
334 C<&checkauth> fetches user and session information from C<$query> and
335 ensures that the user is authorized to run scripts that require
336 authorization.
337
338 The C<$flagsrequired> argument specifies the required privileges
339 the user must have if the username and password are correct.
340 It should be specified as a reference-to-hash; keys in the hash
341 should be the "flags" for the user, as specified in the Members
342 intranet module. Any key specified must correspond to a "flag"
343 in the userflags table. E.g., { circulate => 1 } would specify
344 that the user must have the "circulate" privilege in order to
345 proceed. To make sure that access control is correct, the
346 C<$flagsrequired> parameter must be specified correctly.
347
348 The C<$type> argument specifies whether the template should be
349 retrieved from the opac or intranet directory tree.  "opac" is
350 assumed if it is not specified; however, if C<$type> is specified,
351 "intranet" is assumed if it is not "opac".
352
353 If C<$query> does not have a valid session ID associated with it
354 (i.e., the user has not logged in) or if the session has expired,
355 C<&checkauth> presents the user with a login page (from the point of
356 view of the original script, C<&checkauth> does not return). Once the
357 user has authenticated, C<&checkauth> restarts the original script
358 (this time, C<&checkauth> returns).
359
360 The login page is provided using a HTML::Template, which is set in the
361 systempreferences table or at the top of this file. The variable C<$type>
362 selects which template to use, either the opac or the intranet 
363 authentification template.
364
365 C<&checkauth> returns a user ID, a cookie, and a session ID. The
366 cookie should be sent back to the browser; it verifies that the user
367 has authenticated.
368
369 =cut
370
371 sub checkauth {
372     my $query = shift;
373   # warn "Checking Auth";
374     # $authnotrequired will be set for scripts which will run without authentication
375     my $authnotrequired = shift;
376     my $flagsrequired   = shift;
377     my $type            = shift;
378     $type = 'opac' unless $type;
379
380     my $dbh     = C4::Context->dbh;
381     my $timeout = C4::Context->preference('timeout');
382     $timeout = 600 unless $timeout;
383
384
385     # If Version syspref is unavailable, it means Koha is beeing installed,
386     # and so we must redirect to OPAC maintenance page or to the WebInstaller
387     #warn "about to check version";
388     unless (C4::Context->preference('Version')) {
389       if ($type ne 'opac') {
390         warn "Install required, redirecting to Installer";
391         print $query->redirect("/cgi-bin/koha/installer/install.pl");
392       } 
393       else {
394         warn "OPAC Install required, redirecting to maintenance";
395         print $query->redirect("/cgi-bin/koha/maintenance.pl");
396       }
397       exit;
398     }
399
400
401     # state variables
402     my $loggedin = 0;
403     my %info;
404     my ( $userid, $cookie, $sessionID, $flags );
405     my $logout = $query->param('logout.x');
406     if ( $userid = $ENV{'REMOTE_USER'} ) {
407         # Using Basic Authentication, no cookies required
408         $cookie = $query->cookie(
409             -name    => 'CGISESSID',
410             -value   => '',
411             -expires => ''
412         );
413         $loggedin = 1;
414     }
415     elsif ( $sessionID = $query->cookie("CGISESSID")) {
416         my $session = get_session($sessionID);
417         C4::Context->_new_userenv($sessionID);
418         if ($session){
419             C4::Context::set_userenv(
420                 $session->param('number'),       $session->param('id'),
421                 $session->param('cardnumber'),   $session->param('firstname'),
422                 $session->param('surname'),      $session->param('branch'),
423                 $session->param('branchname'),   $session->param('flags'),
424                 $session->param('emailaddress'), $session->param('branchprinter')
425             );
426 #             warn       "".$session->param('cardnumber').",   ".$session->param('firstname').",
427 #                 ".$session->param('surname').",      ".$session->param('branch');
428         }
429         my $ip;
430         my $lasttime;
431         if ($session) {
432           $ip = $session->param('ip');
433           $lasttime = $session->param('lasttime');
434                 $userid = $session->param('id');
435         }
436         
437     
438         if ($logout) {
439
440             # voluntary logout the user
441
442             $session->flush;      
443                         $session->delete();
444             C4::Context->_unset_userenv($sessionID);
445             $sessionID = undef;
446             $userid    = undef;
447             open L, ">>/tmp/sessionlog";
448             my $time = localtime( time() );
449             printf L "%20s from %16s logged out at %30s (manually).\n", $userid,
450               $ip, $time;
451             close L;
452         }
453         if ($userid) {
454             if ( $lasttime < time() - $timeout ) {
455                 # timed logout
456                 $info{'timed_out'} = 1;
457                 $session->delete();
458                 C4::Context->_unset_userenv($sessionID);
459                 $userid    = undef;
460                 $sessionID = undef;
461                 open L, ">>/tmp/sessionlog";
462                 my $time = localtime( time() );
463                 printf L "%20s from %16s logged out at %30s (inactivity).\n",
464                   $userid, $ip, $time;
465                 close L;
466             }
467             elsif ( $ip ne $ENV{'REMOTE_ADDR'} ) {
468                 # Different ip than originally logged in from
469                 $info{'oldip'}        = $ip;
470                 $info{'newip'}        = $ENV{'REMOTE_ADDR'};
471                 $info{'different_ip'} = 1;
472         $session->delete();
473                 C4::Context->_unset_userenv($sessionID);
474                 $sessionID = undef;
475                 $userid    = undef;
476                 open L, ">>/tmp/sessionlog";
477                 my $time = localtime( time() );
478                 printf L
479 "%20s from logged out at %30s (ip changed from %16s to %16s).\n",
480                   $userid, $time, $ip, $info{'newip'};
481                 close L;
482             }
483             else {
484                 $cookie = $query->cookie( CGISESSID => $session->id );
485                 $session->param('lasttime',time());
486                 $flags = haspermission( $dbh, $userid, $flagsrequired );
487                 if ($flags) {
488                     $loggedin = 1;
489                 }
490                 else {
491                     $info{'nopermission'} = 1;
492                 }
493             }
494         }
495     }
496     unless ($userid) {
497                 my $session = get_session("");
498
499         my $sessionID;
500                 if ($session) {
501                         $sessionID = $session->id;
502                 }
503         $userid    = $query->param('userid');
504         C4::Context->_new_userenv($sessionID);
505         my $password = $query->param('password');
506         C4::Context->_new_userenv($sessionID);
507         my ( $return, $cardnumber ) = checkpw( $dbh, $userid, $password );
508         if ($return) {
509             open L, ">>/tmp/sessionlog";
510             my $time = localtime( time() );
511             printf L "%20s from %16s logged in  at %30s.\n", $userid,
512               $ENV{'REMOTE_ADDR'}, $time;
513             close L;
514             $cookie = $query->cookie(CGISESSID => $sessionID);
515             if ( $flags = haspermission( $dbh, $userid, $flagsrequired ) ) {
516                 $loggedin = 1;
517             }
518             else {
519                 $info{'nopermission'} = 1;
520                 C4::Context->_unset_userenv($sessionID);
521             }
522             if ( $return == 1 ) {
523                 my (
524                     $borrowernumber, $firstname,  $surname,
525                     $userflags,      $branchcode, $branchname,
526                     $branchprinter,  $emailaddress
527                 );
528                 my $sth =
529                   $dbh->prepare(
530 "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=?"
531                   );
532                 $sth->execute($userid);
533                 (
534                     $borrowernumber, $firstname,  $surname,
535                     $userflags,      $branchcode, $branchname,
536                     $branchprinter,  $emailaddress
537                   )
538                   = $sth->fetchrow
539                   if ( $sth->rows );
540
541 #         warn "$cardnumber,$borrowernumber,$userid,$firstname,$surname,$userflags,$branchcode,$emailaddress";
542                 unless ( $sth->rows ) {
543                     my $sth =
544                       $dbh->prepare(
545 "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=?"
546                       );
547                     $sth->execute($cardnumber);
548                     (
549                         $borrowernumber, $firstname,  $surname,
550                         $userflags,      $branchcode, $branchname,
551                         $branchprinter,  $emailaddress
552                       )
553                       = $sth->fetchrow
554                       if ( $sth->rows );
555
556 #           warn "$cardnumber,$borrowernumber,$userid,$firstname,$surname,$userflags,$branchcode,$emailaddress";
557                     unless ( $sth->rows ) {
558                         $sth->execute($userid);
559                         (
560                             $borrowernumber, $firstname, $surname, $userflags,
561                             $branchcode, $branchname, $branchprinter, $emailaddress
562                           )
563                           = $sth->fetchrow
564                           if ( $sth->rows );
565                     }
566                 }
567
568 # launch a sequence to check if we have a ip for the branch, if we have one we replace the branchcode of the userenv by the branch bound in the ip.
569                 my $ip       = $ENV{'REMOTE_ADDR'};
570                 # if they specify at login, use that
571                 if ($query->param('branch')) {
572                     $branchcode  = $query->param('branch');
573                     $branchname = GetBranchName($branchcode);
574                 }
575                 my $branches = GetBranches();
576                 my @branchesloop;
577                 foreach my $br ( keys %$branches ) {
578                     #     now we work with the treatment of ip
579                     my $domain = $branches->{$br}->{'branchip'};
580                     if ( $domain && $ip =~ /^$domain/ ) {
581                         $branchcode = $branches->{$br}->{'branchcode'};
582
583                         # new op dev : add the branchprinter and branchname in the cookie
584                         $branchprinter = $branches->{$br}->{'branchprinter'};
585                         $branchname    = $branches->{$br}->{'branchname'};
586                     }
587                 }
588                 $session->param('number',$borrowernumber);
589                 $session->param('id',$userid);
590                 $session->param('cardnumber',$cardnumber);
591                 $session->param('firstname',$firstname);
592                 $session->param('surname',$surname);
593                 $session->param('branch',$branchcode);
594                 $session->param('branchname',$branchname);
595                 $session->param('flags',$userflags);
596                 $session->param('emailaddress',$emailaddress);
597                 $session->param('ip',$session->remote_addr());
598                 $session->param('lasttime',time());
599 #            warn       "".$session->param('cardnumber').",   ".$session->param('firstname').",
600 #                 ".$session->param('surname').",      ".$session->param('branch');
601             }
602             elsif ( $return == 2 ) {
603                 #We suppose the user is the superlibrarian
604                         $session->param('number',0);
605                         $session->param('id',C4::Context->config('user'));
606                         $session->param('cardnumber',C4::Context->config('user'));
607                         $session->param('firstname',C4::Context->config('user'));
608                         $session->param('surname',C4::Context->config('user'));
609                         $session->param('branch','NO_LIBRARY_SET');
610                         $session->param('branchname','NO_LIBRARY_SET');
611                         $session->param('flags',1);
612                         $session->param('emailaddress', C4::Context->preference('KohaAdminEmailAddress'));
613                         $session->param('ip',$session->remote_addr());
614                         $session->param('lasttime',time());
615                 }
616                 if ($session){
617                     C4::Context::set_userenv(
618                         $session->param('number'),       $session->param('id'),
619                         $session->param('cardnumber'),   $session->param('firstname'),
620                         $session->param('surname'),      $session->param('branch'),
621                         $session->param('branchname'),   $session->param('flags'),
622                         $session->param('emailaddress'), $session->param('branchprinter')
623                     );
624                 }
625         }
626
627         else {
628             if ($userid) {
629                 $info{'invalid_username_or_password'} = 1;
630                 C4::Context->_unset_userenv($sessionID);
631             }
632         }
633     }
634     my $insecure = C4::Context->boolean_preference('insecure');
635
636     # finished authentification, now respond
637     if ( $loggedin || $authnotrequired || ( defined($insecure) && $insecure ) )
638     {
639         # successful login
640         unless ($cookie) {
641             $cookie = $query->cookie( CGISESSID => ''
642             );
643         }
644     return ( $userid, $cookie, $sessionID, $flags );
645
646     }
647
648 #
649 #
650 # AUTH rejected, show the login/password template, after checking the DB.
651 #
652 #
653     
654     # get the inputs from the incoming query
655     my @inputs = ();
656     foreach my $name ( param $query) {
657         (next) if ( $name eq 'userid' || $name eq 'password' );
658         my $value = $query->param($name);
659         push @inputs, { name => $name, value => $value };
660     }
661     # get the branchloop, which we need for authentication
662     my $branches = GetBranches();
663     my @branch_loop;
664     for my $branch_hash (keys %$branches) {
665                 push @branch_loop, {branchcode => "$branch_hash", branchname => $branches->{$branch_hash}->{'branchname'}, };
666     }
667
668     # check that database and koha version are the same
669     # there is no DB version, it's a fresh install,
670     # go to web installer
671     # there is a DB version, compare it to the code version
672     my $kohaversion=C4::Context::KOHAVERSION;
673     # remove the 3 last . to have a Perl number
674     $kohaversion =~ s/(.*\..*)\.(.*)\.(.*)/$1$2$3/;
675 #     warn "kohaversion : $kohaversion";
676     if (C4::Context->preference('Version') < $kohaversion){
677       if ($type ne 'opac'){
678       warn "Database update needed, redirecting to Installer. Database is ".C4::Context->preference('Version')." and Koha is : ".C4::Context->config("kohaversion");
679         print $query->redirect("/cgi-bin/koha/installer/install.pl?step=3");
680       } else {
681       warn "OPAC :Database update needed, redirecting to maintenance. Database is ".C4::Context->preference('Version')." and Koha is : ".C4::Context->config("kohaversion");
682         print $query->redirect("/cgi-bin/koha/maintenance.pl");
683       }       
684       exit;
685     }
686     my $template_name;
687     if ( $type eq 'opac' ) {
688         $template_name = "opac-auth.tmpl";
689     }
690     else {
691         $template_name = "auth.tmpl";
692     }
693     my $template = gettemplate( $template_name, $type, $query );
694     $template->param(branchloop => \@branch_loop,);
695     $template->param(
696     login        => 1,
697         INPUTS               => \@inputs,
698         suggestion           => C4::Context->preference("suggestion"),
699         virtualshelves       => C4::Context->preference("virtualshelves"),
700         opaclargeimage       => C4::Context->preference("opaclargeimage"),
701         LibraryName          => C4::Context->preference("LibraryName"),
702         OpacNav              => C4::Context->preference("OpacNav"),
703         opaccredits          => C4::Context->preference("opaccredits"),
704         opacreadinghistory   => C4::Context->preference("opacreadinghistory"),
705         opacsmallimage       => C4::Context->preference("opacsmallimage"),
706         opaclayoutstylesheet => C4::Context->preference("opaclayoutstylesheet"),
707         opaccolorstylesheet  => C4::Context->preference("opaccolorstylesheet"),
708         opaclanguagesdisplay => C4::Context->preference("opaclanguagesdisplay"),
709         opacuserjs           => C4::Context->preference("opacuserjs"),
710
711         intranetcolorstylesheet =>
712           C4::Context->preference("intranetcolorstylesheet"),
713         intranetstylesheet => C4::Context->preference("intranetstylesheet"),
714         IntranetNav        => C4::Context->preference("IntranetNav"),
715         intranetuserjs     => C4::Context->preference("intranetuserjs"),
716         TemplateEncoding   => C4::Context->preference("TemplateEncoding"),
717         IndependantBranches     => C4::Context->preference("IndependantBranches"),
718                 AutoLocation       => C4::Context->preference("AutoLocation"),
719     );
720     $template->param( loginprompt => 1 ) unless $info{'nopermission'};
721
722     my $self_url = $query->url( -absolute => 1 );
723     $template->param(
724         url         => $self_url,
725         LibraryName => => C4::Context->preference("LibraryName"),
726     );
727     $template->param( \%info );
728 #    $cookie = $query->cookie(CGISESSID => $session->id
729 #   );
730     print $query->header(
731                 -type   => 'text/html',
732         -charset => 'utf-8',
733         -cookie => $cookie
734       ),
735       $template->output;
736     exit;
737 }
738
739 =item check_api_auth
740
741   ($status, $cookie, $sessionId) = check_api_auth($query, $userflags);
742
743 Given a CGI query containing the parameters 'userid' and 'password' and/or a session
744 cookie, determine if the user has the privileges specified by C<$userflags>.
745
746 C<check_api_auth> is is meant for authenticating users of web services, and
747 consequently will always return and will not attempt to redirect the user
748 agent.
749
750 If a valid session cookie is already present, check_api_auth will return a status
751 of "ok", the cookie, and the Koha session ID.
752
753 If no session cookie is present, check_api_auth will check the 'userid' and 'password
754 parameters and create a session cookie and Koha session if the supplied credentials
755 are OK.
756
757 Possible return values in C<$status> are:
758
759 =over 4
760
761 =item "ok" -- user authenticated; C<$cookie> and C<$sessionid> have valid values.
762
763 =item "failed" -- credentials are not correct; C<$cookie> and C<$sessionid> are undef
764
765 =item "maintenance" -- DB is in maintenance mode; no login possible at the moment
766
767 =item "expired -- session cookie has expired; API user should resubmit userid and password
768
769 =back
770
771 =cut
772
773 sub check_api_auth {
774     my $query = shift;
775     my $flagsrequired = shift;
776
777     my $dbh     = C4::Context->dbh;
778     my $timeout = C4::Context->preference('timeout');
779     $timeout = 600 unless $timeout;
780
781     unless (C4::Context->preference('Version')) {
782         # database has not been installed yet
783         return ("maintenance", undef, undef);
784     }
785     my $kohaversion=C4::Context::KOHAVERSION;
786     $kohaversion =~ s/(.*\..*)\.(.*)\.(.*)/$1$2$3/;
787     if (C4::Context->preference('Version') < $kohaversion) {
788         # database in need of version update; assume that
789         # no API should be called while databsae is in
790         # this condition.
791         return ("maintenance", undef, undef);
792     }
793
794     # FIXME -- most of what follows is a copy-and-paste
795     # of code from checkauth.  There is an obvious need
796     # for refactoring to separate the various parts of
797     # the authentication code, but as of 2007-11-19 this
798     # is deferred so as to not introduce bugs into the
799     # regular authentication code for Koha 3.0.
800
801     # see if we have a valid session cookie already
802     # however, if a userid parameter is present (i.e., from
803     # a form submission, assume that any current cookie
804     # is to be ignored
805     my $sessionID = undef;
806     unless ($query->param('userid')) {
807         $sessionID = $query->cookie("CGISESSID");
808     }
809     if ($sessionID) {
810         my $session = get_session($sessionID);
811         C4::Context->_new_userenv($sessionID);
812         if ($session) {
813             C4::Context::set_userenv(
814                 $session->param('number'),       $session->param('id'),
815                 $session->param('cardnumber'),   $session->param('firstname'),
816                 $session->param('surname'),      $session->param('branch'),
817                 $session->param('branchname'),   $session->param('flags'),
818                 $session->param('emailaddress'), $session->param('branchprinter')
819             );
820
821             my $ip = $session->param('ip');
822             my $lasttime = $session->param('lasttime');
823             my $userid = $session->param('id');
824             if ( $lasttime < time() - $timeout ) {
825                 # time out
826                 $session->delete();
827                 C4::Context->_unset_userenv($sessionID);
828                 $userid    = undef;
829                 $sessionID = undef;
830                 return ("expired", undef, undef);
831             } elsif ( $ip ne $ENV{'REMOTE_ADDR'} ) {
832                 # IP address changed
833                 $session->delete();
834                 C4::Context->_unset_userenv($sessionID);
835                 $userid    = undef;
836                 $sessionID = undef;
837                 return ("expired", undef, undef);
838             } else {
839                 my $cookie = $query->cookie( CGISESSID => $session->id );
840                 $session->param('lasttime',time());
841                 my $flags = haspermission( $dbh, $userid, $flagsrequired );
842                 if ($flags) {
843                     return ("ok", $cookie, $sessionID);
844                 } else {
845                     $session->delete();
846                     C4::Context->_unset_userenv($sessionID);
847                     $userid    = undef;
848                     $sessionID = undef;
849                     return ("failed", undef, undef);
850                 }
851             }
852         } else {
853             return ("expired", undef, undef);
854         }
855     } else {
856         # new login
857         my $userid = $query->param('userid');   
858         my $password = $query->param('password');   
859         unless ($userid and $password) {
860             # caller did something wrong, fail the authenticateion
861             return ("failed", undef, undef);
862         }
863         my ( $return, $cardnumber ) = checkpw( $dbh, $userid, $password );
864         if ($return and haspermission( $dbh, $userid, $flagsrequired)) {
865             my $session = get_session("");
866             return ("failed", undef, undef) unless $session;
867
868             my $sessionID = $session->id;
869             C4::Context->_new_userenv($sessionID);
870             my $cookie = $query->cookie(CGISESSID => $sessionID);
871             if ( $return == 1 ) {
872                 my (
873                     $borrowernumber, $firstname,  $surname,
874                     $userflags,      $branchcode, $branchname,
875                     $branchprinter,  $emailaddress
876                 );
877                 my $sth =
878                   $dbh->prepare(
879 "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=?"
880                   );
881                 $sth->execute($userid);
882                 (
883                     $borrowernumber, $firstname,  $surname,
884                     $userflags,      $branchcode, $branchname,
885                     $branchprinter,  $emailaddress
886                 ) = $sth->fetchrow if ( $sth->rows );
887
888                 unless ($sth->rows ) {
889                     my $sth = $dbh->prepare(
890 "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=?"
891                       );
892                     $sth->execute($cardnumber);
893                     (
894                         $borrowernumber, $firstname,  $surname,
895                         $userflags,      $branchcode, $branchname,
896                         $branchprinter,  $emailaddress
897                     ) = $sth->fetchrow if ( $sth->rows );
898
899                     unless ( $sth->rows ) {
900                         $sth->execute($userid);
901                         (
902                             $borrowernumber, $firstname, $surname, $userflags,
903                             $branchcode, $branchname, $branchprinter, $emailaddress
904                         ) = $sth->fetchrow if ( $sth->rows );
905                     }
906                 }
907
908                 my $ip       = $ENV{'REMOTE_ADDR'};
909                 # if they specify at login, use that
910                 if ($query->param('branch')) {
911                     $branchcode  = $query->param('branch');
912                     $branchname = GetBranchName($branchcode);
913                 }
914                 my $branches = GetBranches();
915                 my @branchesloop;
916                 foreach my $br ( keys %$branches ) {
917                     #     now we work with the treatment of ip
918                     my $domain = $branches->{$br}->{'branchip'};
919                     if ( $domain && $ip =~ /^$domain/ ) {
920                         $branchcode = $branches->{$br}->{'branchcode'};
921
922                         # new op dev : add the branchprinter and branchname in the cookie
923                         $branchprinter = $branches->{$br}->{'branchprinter'};
924                         $branchname    = $branches->{$br}->{'branchname'};
925                     }
926                 }
927                 $session->param('number',$borrowernumber);
928                 $session->param('id',$userid);
929                 $session->param('cardnumber',$cardnumber);
930                 $session->param('firstname',$firstname);
931                 $session->param('surname',$surname);
932                 $session->param('branch',$branchcode);
933                 $session->param('branchname',$branchname);
934                 $session->param('flags',$userflags);
935                 $session->param('emailaddress',$emailaddress);
936                 $session->param('ip',$session->remote_addr());
937                 $session->param('lasttime',time());
938             } elsif ( $return == 2 ) {
939                 #We suppose the user is the superlibrarian
940                 $session->param('number',0);
941                 $session->param('id',C4::Context->config('user'));
942                 $session->param('cardnumber',C4::Context->config('user'));
943                 $session->param('firstname',C4::Context->config('user'));
944                 $session->param('surname',C4::Context->config('user'));
945                 $session->param('branch','NO_LIBRARY_SET');
946                 $session->param('branchname','NO_LIBRARY_SET');
947                 $session->param('flags',1);
948                 $session->param('emailaddress', C4::Context->preference('KohaAdminEmailAddress'));
949                 $session->param('ip',$session->remote_addr());
950                 $session->param('lasttime',time());
951             } 
952             C4::Context::set_userenv(
953                 $session->param('number'),       $session->param('id'),
954                 $session->param('cardnumber'),   $session->param('firstname'),
955                 $session->param('surname'),      $session->param('branch'),
956                 $session->param('branchname'),   $session->param('flags'),
957                 $session->param('emailaddress'), $session->param('branchprinter')
958             );
959             return ("ok", $cookie, $sessionID);
960         } else {
961             return ("failed", undef, undef);
962         }
963     } 
964 }
965
966 =item get_session
967
968   use CGI::Session;
969   my $session = get_session($sessionID);
970
971 Given a session ID, retrieve the CGI::Session object used to store
972 the session's state.  The session object can be used to store 
973 data that needs to be accessed by different scripts during a
974 user's session.
975
976 If the C<$sessionID> parameter is an empty string, a new session
977 will be created.
978
979 =cut
980
981 sub get_session {
982     my $sessionID = shift;
983     my $storage_method = C4::Context->preference('SessionStorage');
984     my $dbh = C4::Context->dbh;
985     my $session;
986     if ($storage_method eq 'mysql'){
987         $session = new CGI::Session("driver:MySQL", $sessionID, {Handle=>$dbh});
988     }
989     elsif ($storage_method eq 'Pg') {
990         $session = new CGI::Session("driver:PostgreSQL", $sessionID, {Handle=>$dbh});
991     }
992     else {
993         # catch all defaults to tmp should work on all systems
994         $session = new CGI::Session("driver:File", $sessionID, {Directory=>'/tmp'});
995     }
996     return $session;
997 }
998
999 sub checkpw {
1000
1001     my ( $dbh, $userid, $password ) = @_;
1002
1003     # INTERNAL AUTH
1004     my $sth =
1005       $dbh->prepare(
1006 "select password,cardnumber,borrowernumber,userid,firstname,surname,branchcode,flags from borrowers where userid=?"
1007       );
1008     $sth->execute($userid);
1009     if ( $sth->rows ) {
1010         my ( $md5password, $cardnumber, $borrowernumber, $userid, $firstname,
1011             $surname, $branchcode, $flags )
1012           = $sth->fetchrow;
1013         if ( md5_base64($password) eq $md5password ) {
1014
1015             C4::Context->set_userenv( "$borrowernumber", $userid, $cardnumber,
1016                 $firstname, $surname, $branchcode, $flags );
1017             return 1, $cardnumber;
1018         }
1019     }
1020     $sth =
1021       $dbh->prepare(
1022 "select password,cardnumber,borrowernumber,userid, firstname,surname,branchcode,flags from borrowers where cardnumber=?"
1023       );
1024     $sth->execute($userid);
1025     if ( $sth->rows ) {
1026         my ( $md5password, $cardnumber, $borrowernumber, $userid, $firstname,
1027             $surname, $branchcode, $flags )
1028           = $sth->fetchrow;
1029         if ( md5_base64($password) eq $md5password ) {
1030
1031             C4::Context->set_userenv( $borrowernumber, $userid, $cardnumber,
1032                 $firstname, $surname, $branchcode, $flags );
1033             return 1, $userid;
1034         }
1035     }
1036     if (   $userid && $userid eq C4::Context->config('user')
1037         && "$password" eq C4::Context->config('pass') )
1038     {
1039
1040 # Koha superuser account
1041 #     C4::Context->set_userenv(0,0,C4::Context->config('user'),C4::Context->config('user'),C4::Context->config('user'),"",1);
1042         return 2;
1043     }
1044     if (   $userid && $userid eq 'demo'
1045         && "$password" eq 'demo'
1046         && C4::Context->config('demo') )
1047     {
1048
1049 # DEMO => the demo user is allowed to do everything (if demo set to 1 in koha.conf
1050 # some features won't be effective : modify systempref, modify MARC structure,
1051         return 2;
1052     }
1053     return 0;
1054 }
1055
1056 sub getuserflags {
1057     my $cardnumber = shift;
1058     my $dbh        = shift;
1059     my $userflags;
1060     my $sth = $dbh->prepare("SELECT flags FROM borrowers WHERE cardnumber=?");
1061     $sth->execute($cardnumber);
1062     my ($flags) = $sth->fetchrow;
1063     $flags = 0 unless $flags;
1064     $sth = $dbh->prepare("SELECT bit, flag, defaulton FROM userflags");
1065     $sth->execute;
1066
1067     while ( my ( $bit, $flag, $defaulton ) = $sth->fetchrow ) {
1068         if ( ( $flags & ( 2**$bit ) ) || $defaulton ) {
1069             $userflags->{$flag} = 1;
1070         }
1071         else {
1072             $userflags->{$flag} = 0;
1073         }
1074     }
1075     return $userflags;
1076 }
1077
1078 sub haspermission {
1079     my ( $dbh, $userid, $flagsrequired ) = @_;
1080     my $sth = $dbh->prepare("SELECT cardnumber FROM borrowers WHERE userid=?");
1081     $sth->execute($userid);
1082     my ($cardnumber) = $sth->fetchrow;
1083     ($cardnumber) || ( $cardnumber = $userid );
1084     my $flags = getuserflags( $cardnumber, $dbh );
1085     my $configfile;
1086     if ( $userid eq C4::Context->config('user') ) {
1087
1088         # Super User Account from /etc/koha.conf
1089         $flags->{'superlibrarian'} = 1;
1090     }
1091     if ( $userid eq 'demo' && C4::Context->config('demo') ) {
1092
1093         # Demo user that can do "anything" (demo=1 in /etc/koha.conf)
1094         $flags->{'superlibrarian'} = 1;
1095     }
1096     return $flags if $flags->{superlibrarian};
1097     foreach ( keys %$flagsrequired ) {
1098         return $flags if $flags->{$_};
1099     }
1100     return 0;
1101 }
1102
1103 sub getborrowernumber {
1104     my ($userid) = @_;
1105     my $dbh = C4::Context->dbh;
1106     for my $field ( 'userid', 'cardnumber' ) {
1107         my $sth =
1108           $dbh->prepare("select borrowernumber from borrowers where $field=?");
1109         $sth->execute($userid);
1110         if ( $sth->rows ) {
1111             my ($bnumber) = $sth->fetchrow;
1112             return $bnumber;
1113         }
1114     }
1115     return 0;
1116 }
1117
1118 END { }    # module clean-up code here (global destructor)
1119 1;
1120 __END__
1121
1122 =back
1123
1124 =head1 SEE ALSO
1125
1126 CGI(3)
1127
1128 C4::Output(3)
1129
1130 Digest::MD5(3)
1131
1132 =cut