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