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