Bugfixing interface issues. Also enabling contextual menu highlighting for catalogue...
[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                 AutoLocation       => C4::Context->preference("AutoLocation"),
739     );
740     $template->param( loginprompt => 1 ) unless $info{'nopermission'};
741
742     my $self_url = $query->url( -absolute => 1 );
743     $template->param(
744         url         => $self_url,
745         LibraryName => => C4::Context->preference("LibraryName"),
746     );
747     $template->param( \%info );
748 #    $cookie = $query->cookie(CGISESSID => $session->id
749 #   );
750     print $query->header(
751                 -type   => 'text/html',
752         -charset => 'utf-8',
753         -cookie => $cookie
754       ),
755       $template->output;
756     exit;
757 }
758
759 =item check_api_auth
760
761   ($status, $cookie, $sessionId) = check_api_auth($query, $userflags);
762
763 Given a CGI query containing the parameters 'userid' and 'password' and/or a session
764 cookie, determine if the user has the privileges specified by C<$userflags>.
765
766 C<check_api_auth> is is meant for authenticating users of web services, and
767 consequently will always return and will not attempt to redirect the user
768 agent.
769
770 If a valid session cookie is already present, check_api_auth will return a status
771 of "ok", the cookie, and the Koha session ID.
772
773 If no session cookie is present, check_api_auth will check the 'userid' and 'password
774 parameters and create a session cookie and Koha session if the supplied credentials
775 are OK.
776
777 Possible return values in C<$status> are:
778
779 =over 4
780
781 =item "ok" -- user authenticated; C<$cookie> and C<$sessionid> have valid values.
782
783 =item "failed" -- credentials are not correct; C<$cookie> and C<$sessionid> are undef
784
785 =item "maintenance" -- DB is in maintenance mode; no login possible at the moment
786
787 =item "expired -- session cookie has expired; API user should resubmit userid and password
788
789 =back
790
791 =cut
792
793 sub check_api_auth {
794     my $query = shift;
795     my $flagsrequired = shift;
796
797     my $dbh     = C4::Context->dbh;
798     my $timeout = C4::Context->preference('timeout');
799     $timeout = 600 unless $timeout;
800
801     unless (C4::Context->preference('Version')) {
802         # database has not been installed yet
803         return ("maintenance", undef, undef);
804     }
805     my $kohaversion=C4::Context::KOHAVERSION;
806     $kohaversion =~ s/(.*\..*)\.(.*)\.(.*)/$1$2$3/;
807     if (C4::Context->preference('Version') < $kohaversion) {
808         # database in need of version update; assume that
809         # no API should be called while databsae is in
810         # this condition.
811         return ("maintenance", undef, undef);
812     }
813
814     # FIXME -- most of what follows is a copy-and-paste
815     # of code from checkauth.  There is an obvious need
816     # for refactoring to separate the various parts of
817     # the authentication code, but as of 2007-11-19 this
818     # is deferred so as to not introduce bugs into the
819     # regular authentication code for Koha 3.0.
820
821     # see if we have a valid session cookie already
822     # however, if a userid parameter is present (i.e., from
823     # a form submission, assume that any current cookie
824     # is to be ignored
825     my $sessionID = undef;
826     unless ($query->param('userid')) {
827         $sessionID = $query->cookie("CGISESSID");
828     }
829     if ($sessionID) {
830         my $storage_method = C4::Context->preference('SessionStorage');
831         my $session;
832         if ($storage_method eq 'mysql'){
833             $session = new CGI::Session("driver:MySQL", $sessionID, {Handle=>$dbh});
834         }
835         elsif ($storage_method eq 'Pg') {
836             $session = new CGI::Session("driver:PostgreSQL", $sessionID, {Handle=>$dbh});
837         }
838         else {
839             # catch all defaults to tmp should work on all systems
840             $session = new CGI::Session("driver:File", $sessionID, {Directory=>'/tmp'});
841         }
842         C4::Context->_new_userenv($sessionID);
843         if ($session) {
844             C4::Context::set_userenv(
845                 $session->param('number'),       $session->param('id'),
846                 $session->param('cardnumber'),   $session->param('firstname'),
847                 $session->param('surname'),      $session->param('branch'),
848                 $session->param('branchname'),   $session->param('flags'),
849                 $session->param('emailaddress'), $session->param('branchprinter')
850             );
851
852             my $ip = $session->param('ip');
853             my $lasttime = $session->param('lasttime');
854             my $userid = $session->param('id');
855             if ( $lasttime < time() - $timeout ) {
856                 # time out
857                 $session->delete();
858                 C4::Context->_unset_userenv($sessionID);
859                 $userid    = undef;
860                 $sessionID = undef;
861                 return ("expired", undef, undef);
862             } elsif ( $ip ne $ENV{'REMOTE_ADDR'} ) {
863                 # IP address changed
864                 $session->delete();
865                 C4::Context->_unset_userenv($sessionID);
866                 $userid    = undef;
867                 $sessionID = undef;
868                 return ("expired", undef, undef);
869             } else {
870                 my $cookie = $query->cookie( CGISESSID => $session->id );
871                 $session->param('lasttime',time());
872                 my $flags = haspermission( $dbh, $userid, $flagsrequired );
873                 if ($flags) {
874                     return ("ok", $cookie, $sessionID);
875                 } else {
876                     $session->delete();
877                     C4::Context->_unset_userenv($sessionID);
878                     $userid    = undef;
879                     $sessionID = undef;
880                     return ("failed", undef, undef);
881                 }
882             }
883         } else {
884             return ("expired", undef, undef);
885         }
886     } else {
887         # new login
888         my $userid = $query->param('userid');   
889         my $password = $query->param('password');   
890         unless ($userid and $password) {
891             # caller did something wrong, fail the authenticateion
892             return ("failed", undef, undef);
893         }
894         my ( $return, $cardnumber ) = checkpw( $dbh, $userid, $password );
895         if ($return and haspermission( $dbh, $userid, $flagsrequired)) {
896             my $storage_method = C4::Context->preference('SessionStorage');
897             my $session;
898             if ($storage_method eq 'mysql'){
899                 $session = new CGI::Session("driver:MySQL", $sessionID, {Handle=>$dbh});
900             } elsif ($storage_method eq 'Pg') {
901                 $session = new CGI::Session("driver:PostgreSQL", $sessionID, {Handle=>$dbh});
902             } else {
903                 # catch all defaults to tmp should work on all systems
904                 $session = new CGI::Session("driver:File", $sessionID, {Directory=>'/tmp'});
905             }
906             return ("failed", undef, undef) unless $session;
907
908             my $sessionID = $session->id;
909             C4::Context->_new_userenv($sessionID);
910             my $cookie = $query->cookie(CGISESSID => $sessionID);
911             if ( $return == 1 ) {
912                 my (
913                     $borrowernumber, $firstname,  $surname,
914                     $userflags,      $branchcode, $branchname,
915                     $branchprinter,  $emailaddress
916                 );
917                 my $sth =
918                   $dbh->prepare(
919 "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=?"
920                   );
921                 $sth->execute($userid);
922                 (
923                     $borrowernumber, $firstname,  $surname,
924                     $userflags,      $branchcode, $branchname,
925                     $branchprinter,  $emailaddress
926                 ) = $sth->fetchrow if ( $sth->rows );
927
928                 unless ($sth->rows ) {
929                     my $sth = $dbh->prepare(
930 "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=?"
931                       );
932                     $sth->execute($cardnumber);
933                     (
934                         $borrowernumber, $firstname,  $surname,
935                         $userflags,      $branchcode, $branchname,
936                         $branchprinter,  $emailaddress
937                     ) = $sth->fetchrow if ( $sth->rows );
938
939                     unless ( $sth->rows ) {
940                         $sth->execute($userid);
941                         (
942                             $borrowernumber, $firstname, $surname, $userflags,
943                             $branchcode, $branchname, $branchprinter, $emailaddress
944                         ) = $sth->fetchrow if ( $sth->rows );
945                     }
946                 }
947
948                 my $ip       = $ENV{'REMOTE_ADDR'};
949                 # if they specify at login, use that
950                 if ($query->param('branch')) {
951                     $branchcode  = $query->param('branch');
952                     $branchname = GetBranchName($branchcode);
953                 }
954                 my $branches = GetBranches();
955                 my @branchesloop;
956                 foreach my $br ( keys %$branches ) {
957                     #     now we work with the treatment of ip
958                     my $domain = $branches->{$br}->{'branchip'};
959                     if ( $domain && $ip =~ /^$domain/ ) {
960                         $branchcode = $branches->{$br}->{'branchcode'};
961
962                         # new op dev : add the branchprinter and branchname in the cookie
963                         $branchprinter = $branches->{$br}->{'branchprinter'};
964                         $branchname    = $branches->{$br}->{'branchname'};
965                     }
966                 }
967                 $session->param('number',$borrowernumber);
968                 $session->param('id',$userid);
969                 $session->param('cardnumber',$cardnumber);
970                 $session->param('firstname',$firstname);
971                 $session->param('surname',$surname);
972                 $session->param('branch',$branchcode);
973                 $session->param('branchname',$branchname);
974                 $session->param('flags',$userflags);
975                 $session->param('emailaddress',$emailaddress);
976                 $session->param('ip',$session->remote_addr());
977                 $session->param('lasttime',time());
978             } elsif ( $return == 2 ) {
979                 #We suppose the user is the superlibrarian
980                 $session->param('number',0);
981                 $session->param('id',C4::Context->config('user'));
982                 $session->param('cardnumber',C4::Context->config('user'));
983                 $session->param('firstname',C4::Context->config('user'));
984                 $session->param('surname',C4::Context->config('user'));
985                 $session->param('branch','NO_LIBRARY_SET');
986                 $session->param('branchname','NO_LIBRARY_SET');
987                 $session->param('flags',1);
988                 $session->param('emailaddress', C4::Context->preference('KohaAdminEmailAddress'));
989                 $session->param('ip',$session->remote_addr());
990                 $session->param('lasttime',time());
991             } 
992             C4::Context::set_userenv(
993                 $session->param('number'),       $session->param('id'),
994                 $session->param('cardnumber'),   $session->param('firstname'),
995                 $session->param('surname'),      $session->param('branch'),
996                 $session->param('branchname'),   $session->param('flags'),
997                 $session->param('emailaddress'), $session->param('branchprinter')
998             );
999             return ("ok", $cookie, $sessionID);
1000         } else {
1001             return ("failed", undef, undef);
1002         }
1003     } 
1004 }
1005
1006 sub checkpw {
1007
1008     my ( $dbh, $userid, $password ) = @_;
1009
1010     # INTERNAL AUTH
1011     my $sth =
1012       $dbh->prepare(
1013 "select password,cardnumber,borrowernumber,userid,firstname,surname,branchcode,flags from borrowers where userid=?"
1014       );
1015     $sth->execute($userid);
1016     if ( $sth->rows ) {
1017         my ( $md5password, $cardnumber, $borrowernumber, $userid, $firstname,
1018             $surname, $branchcode, $flags )
1019           = $sth->fetchrow;
1020         if ( md5_base64($password) eq $md5password ) {
1021
1022             C4::Context->set_userenv( "$borrowernumber", $userid, $cardnumber,
1023                 $firstname, $surname, $branchcode, $flags );
1024             return 1, $cardnumber;
1025         }
1026     }
1027     $sth =
1028       $dbh->prepare(
1029 "select password,cardnumber,borrowernumber,userid, firstname,surname,branchcode,flags from borrowers where cardnumber=?"
1030       );
1031     $sth->execute($userid);
1032     if ( $sth->rows ) {
1033         my ( $md5password, $cardnumber, $borrowernumber, $userid, $firstname,
1034             $surname, $branchcode, $flags )
1035           = $sth->fetchrow;
1036         if ( md5_base64($password) eq $md5password ) {
1037
1038             C4::Context->set_userenv( $borrowernumber, $userid, $cardnumber,
1039                 $firstname, $surname, $branchcode, $flags );
1040             return 1, $userid;
1041         }
1042     }
1043     if (   $userid && $userid eq C4::Context->config('user')
1044         && "$password" eq C4::Context->config('pass') )
1045     {
1046
1047 # Koha superuser account
1048 #     C4::Context->set_userenv(0,0,C4::Context->config('user'),C4::Context->config('user'),C4::Context->config('user'),"",1);
1049         return 2;
1050     }
1051     if (   $userid && $userid eq 'demo'
1052         && "$password" eq 'demo'
1053         && C4::Context->config('demo') )
1054     {
1055
1056 # DEMO => the demo user is allowed to do everything (if demo set to 1 in koha.conf
1057 # some features won't be effective : modify systempref, modify MARC structure,
1058         return 2;
1059     }
1060     return 0;
1061 }
1062
1063 sub getuserflags {
1064     my $cardnumber = shift;
1065     my $dbh        = shift;
1066     my $userflags;
1067     my $sth = $dbh->prepare("SELECT flags FROM borrowers WHERE cardnumber=?");
1068     $sth->execute($cardnumber);
1069     my ($flags) = $sth->fetchrow;
1070     $flags = 0 unless $flags;
1071     $sth = $dbh->prepare("SELECT bit, flag, defaulton FROM userflags");
1072     $sth->execute;
1073
1074     while ( my ( $bit, $flag, $defaulton ) = $sth->fetchrow ) {
1075         if ( ( $flags & ( 2**$bit ) ) || $defaulton ) {
1076             $userflags->{$flag} = 1;
1077         }
1078         else {
1079             $userflags->{$flag} = 0;
1080         }
1081     }
1082     return $userflags;
1083 }
1084
1085 sub haspermission {
1086     my ( $dbh, $userid, $flagsrequired ) = @_;
1087     my $sth = $dbh->prepare("SELECT cardnumber FROM borrowers WHERE userid=?");
1088     $sth->execute($userid);
1089     my ($cardnumber) = $sth->fetchrow;
1090     ($cardnumber) || ( $cardnumber = $userid );
1091     my $flags = getuserflags( $cardnumber, $dbh );
1092     my $configfile;
1093     if ( $userid eq C4::Context->config('user') ) {
1094
1095         # Super User Account from /etc/koha.conf
1096         $flags->{'superlibrarian'} = 1;
1097     }
1098     if ( $userid eq 'demo' && C4::Context->config('demo') ) {
1099
1100         # Demo user that can do "anything" (demo=1 in /etc/koha.conf)
1101         $flags->{'superlibrarian'} = 1;
1102     }
1103     return $flags if $flags->{superlibrarian};
1104     foreach ( keys %$flagsrequired ) {
1105         return $flags if $flags->{$_};
1106     }
1107     return 0;
1108 }
1109
1110 sub getborrowernumber {
1111     my ($userid) = @_;
1112     my $dbh = C4::Context->dbh;
1113     for my $field ( 'userid', 'cardnumber' ) {
1114         my $sth =
1115           $dbh->prepare("select borrowernumber from borrowers where $field=?");
1116         $sth->execute($userid);
1117         if ( $sth->rows ) {
1118             my ($bnumber) = $sth->fetchrow;
1119             return $bnumber;
1120         }
1121     }
1122     return 0;
1123 }
1124
1125 END { }    # module clean-up code here (global destructor)
1126 1;
1127 __END__
1128
1129 =back
1130
1131 =head1 SEE ALSO
1132
1133 CGI(3)
1134
1135 C4::Output(3)
1136
1137 Digest::MD5(3)
1138
1139 =cut