committing new feature ip and printer management
[koha.git] / C4 / Auth.pm
1 # -*- tab-width: 8 -*-
2 # NOTE: This file uses 8-character tabs; do not change the tab size!
3
4 package C4::Auth;
5
6 # Copyright 2000-2002 Katipo Communications
7 #
8 # This file is part of Koha.
9 #
10 # Koha is free software; you can redistribute it and/or modify it under the
11 # terms of the GNU General Public License as published by the Free Software
12 # Foundation; either version 2 of the License, or (at your option) any later
13 # version.
14 #
15 # Koha is distributed in the hope that it will be useful, but WITHOUT ANY
16 # WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
17 # A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
18 #
19 # You should have received a copy of the GNU General Public License along with
20 # Koha; if not, write to the Free Software Foundation, Inc., 59 Temple Place,
21 # Suite 330, Boston, MA  02111-1307 USA
22
23 use strict;
24 use Digest::MD5 qw(md5_base64);
25
26 require Exporter;
27 use C4::Context;
28 use C4::Output;              # to get the template
29 use C4::Interface::CGI::Output;
30 use C4::Circulation::Circ2;  # getpatroninformation
31 use C4::Koha;
32 # use Net::LDAP;
33 # use Net::LDAP qw(:all);
34
35 use vars qw($VERSION @ISA @EXPORT @EXPORT_OK %EXPORT_TAGS);
36
37 # set the version for version checking
38 $VERSION = 0.01;
39
40 =head1 NAME
41
42 C4::Auth - Authenticates Koha users
43
44 =head1 SYNOPSIS
45
46   use CGI;
47   use C4::Auth;
48
49   my $query = new CGI;
50
51   my ($template, $borrowernumber, $cookie) 
52     = get_template_and_user({template_name   => "opac-main.tmpl",
53                              query           => $query,
54                              type            => "opac",
55                              authnotrequired => 1,
56                              flagsrequired   => {borrow => 1},
57                           });
58
59   print $query->header(
60     -type => guesstype($template->output),
61     -cookie => $cookie
62   ), $template->output;
63
64
65 =head1 DESCRIPTION
66
67     The main function of this module is to provide
68     authentification. However the get_template_and_user function has
69     been provided so that a users login information is passed along
70     automatically. This gets loaded into the template.
71
72 =head1 FUNCTIONS
73
74 =over 2
75
76 =cut
77
78
79
80 @ISA = qw(Exporter);
81 @EXPORT = qw(
82              &checkauth
83              &get_template_and_user
84 );
85
86 =item get_template_and_user
87
88   my ($template, $borrowernumber, $cookie)
89     = get_template_and_user({template_name   => "opac-main.tmpl",
90                              query           => $query,
91                              type            => "opac",
92                              authnotrequired => 1,
93                              flagsrequired   => {borrow => 1},
94                           });
95
96     This call passes the C<query>, C<flagsrequired> and C<authnotrequired>
97     to C<&checkauth> (in this module) to perform authentification.
98     See C<&checkauth> for an explanation of these parameters.
99
100     The C<template_name> is then used to find the correct template for
101     the page. The authenticated users details are loaded onto the
102     template in the HTML::Template LOOP variable C<USER_INFO>. Also the
103     C<sessionID> is passed to the template. This can be used in templates
104     if cookies are disabled. It needs to be put as and input to every
105     authenticated page.
106
107     More information on the C<gettemplate> sub can be found in the
108     Output.pm module.
109
110 =cut
111
112
113 sub get_template_and_user {
114         my $in = shift;
115         my $template = gettemplate($in->{'template_name'}, $in->{'type'},$in->{'query'});
116         my ($user, $cookie, $sessionID, $flags)
117                 = checkauth($in->{'query'}, $in->{'authnotrequired'}, $in->{'flagsrequired'}, $in->{'type'});
118
119         my $borrowernumber;
120         if ($user) {
121                 $template->param(loggedinusername => $user);
122                 $template->param(sessionID => $sessionID);
123
124                 $borrowernumber = getborrowernumber($user);
125                 my ($borr, $alternativeflags) = getpatroninformation(undef, $borrowernumber);
126                 my @bordat;
127                 $bordat[0] = $borr;
128                 $template->param(USER_INFO => \@bordat,
129                 );
130                 
131                 # We are going to use the $flags returned by checkauth
132                 # to create the template's parameters that will indicate
133                 # which menus the user can access.
134                 if ($flags && $flags->{superlibrarian} == 1)
135                 {
136                         $template->param(CAN_user_circulate => 1);
137                         $template->param(CAN_user_catalogue => 1);
138                         $template->param(CAN_user_parameters => 1);
139                         $template->param(CAN_user_borrowers => 1);
140                         $template->param(CAN_user_permission => 1);
141                         $template->param(CAN_user_reserveforothers => 1);
142                         $template->param(CAN_user_borrow => 1);
143                         $template->param(CAN_user_reserveforself => 1);
144                         $template->param(CAN_user_editcatalogue => 1);
145                         $template->param(CAN_user_updatecharge => 1);
146                         $template->param(CAN_user_acquisition => 1);
147                         $template->param(CAN_user_management => 1);
148                         $template->param(CAN_user_tools => 1); }
149                 
150                 if ($flags && $flags->{circulate} eq 1) {
151                         $template->param(CAN_user_circulate => 1); }
152
153                 if ($flags && $flags->{catalogue} eq 1) {
154                         $template->param(CAN_user_catalogue => 1); }
155                 
156
157                 if ($flags && $flags->{parameters} eq 1) {
158                         $template->param(CAN_user_parameters => 1);     
159                         $template->param(CAN_user_management => 1);
160                         $template->param(CAN_user_tools => 1); }
161                 
162
163                 if ($flags && $flags->{borrowers} eq 1) {
164                         $template->param(CAN_user_borrowers => 1); }
165                 
166
167                 if ($flags && $flags->{permissions} eq 1) {
168                         $template->param(CAN_user_permission => 1); }
169                 
170                 if ($flags && $flags->{reserveforothers} eq 1) {
171                         $template->param(CAN_user_reserveforothers => 1); }
172                 
173
174                 if ($flags && $flags->{borrow} eq 1) {
175                         $template->param(CAN_user_borrow => 1); }
176                 
177
178                 if ($flags && $flags->{reserveforself} eq 1) {
179                         $template->param(CAN_user_reserveforself => 1); }
180                 
181
182                 if ($flags && $flags->{editcatalogue} eq 1) {
183                         $template->param(CAN_user_editcatalogue => 1); }
184                 
185
186                 if ($flags && $flags->{updatecharges} eq 1) {
187                         $template->param(CAN_user_updatecharge => 1); }
188                 
189                 if ($flags && $flags->{acquisition} eq 1) {
190                         $template->param(CAN_user_acquisition => 1); }
191                 
192                 if ($flags && $flags->{management} eq 1) {
193                         $template->param(CAN_user_management => 1);
194                         $template->param(CAN_user_tools => 1); }
195                 
196                 if ($flags && $flags->{tools} eq 1) {
197                         $template->param(CAN_user_tools => 1); }
198                 
199         }
200         $template->param(
201                              LibraryName => C4::Context->preference("LibraryName"),
202                              branchname => C4::Context->userenv->{'branchname'},
203                 );
204         return ($template, $borrowernumber, $cookie);
205 }
206
207
208 =item checkauth
209
210   ($userid, $cookie, $sessionID) = &checkauth($query, $noauth, $flagsrequired, $type);
211
212 Verifies that the user is authorized to run this script.  If
213 the user is authorized, a (userid, cookie, session-id, flags)
214 quadruple is returned.  If the user is not authorized but does
215 not have the required privilege (see $flagsrequired below), it
216 displays an error page and exits.  Otherwise, it displays the
217 login page and exits.
218
219 Note that C<&checkauth> will return if and only if the user
220 is authorized, so it should be called early on, before any
221 unfinished operations (e.g., if you've opened a file, then
222 C<&checkauth> won't close it for you).
223
224 C<$query> is the CGI object for the script calling C<&checkauth>.
225
226 The C<$noauth> argument is optional. If it is set, then no
227 authorization is required for the script.
228
229 C<&checkauth> fetches user and session information from C<$query> and
230 ensures that the user is authorized to run scripts that require
231 authorization.
232
233 The C<$flagsrequired> argument specifies the required privileges
234 the user must have if the username and password are correct.
235 It should be specified as a reference-to-hash; keys in the hash
236 should be the "flags" for the user, as specified in the Members
237 intranet module. Any key specified must correspond to a "flag"
238 in the userflags table. E.g., { circulate => 1 } would specify
239 that the user must have the "circulate" privilege in order to
240 proceed. To make sure that access control is correct, the
241 C<$flagsrequired> parameter must be specified correctly.
242
243 The C<$type> argument specifies whether the template should be
244 retrieved from the opac or intranet directory tree.  "opac" is
245 assumed if it is not specified; however, if C<$type> is specified,
246 "intranet" is assumed if it is not "opac".
247
248 If C<$query> does not have a valid session ID associated with it
249 (i.e., the user has not logged in) or if the session has expired,
250 C<&checkauth> presents the user with a login page (from the point of
251 view of the original script, C<&checkauth> does not return). Once the
252 user has authenticated, C<&checkauth> restarts the original script
253 (this time, C<&checkauth> returns).
254
255 The login page is provided using a HTML::Template, which is set in the
256 systempreferences table or at the top of this file. The variable C<$type>
257 selects which template to use, either the opac or the intranet 
258 authentification template.
259
260 C<&checkauth> returns a user ID, a cookie, and a session ID. The
261 cookie should be sent back to the browser; it verifies that the user
262 has authenticated.
263
264 =cut
265
266
267
268 sub checkauth {
269         my $query=shift;
270         # $authnotrequired will be set for scripts which will run without authentication
271         my $authnotrequired = shift;
272         my $flagsrequired = shift;
273         my $type = shift;
274         $type = 'opac' unless $type;
275
276         my $dbh = C4::Context->dbh;
277         my $timeout = C4::Context->preference('timeout');
278         $timeout = 600 unless $timeout;
279
280         my $template_name;
281         if ($type eq 'opac') {
282                 $template_name = "opac-auth.tmpl";
283         } else {
284                 $template_name = "auth.tmpl";
285         }
286
287         # state variables
288         my $loggedin = 0;
289         my %info;
290         my ($userid, $cookie, $sessionID, $flags,$envcookie);
291         my $logout = $query->param('logout.x');
292         if ($userid = $ENV{'REMOTE_USER'}) {
293                 # Using Basic Authentication, no cookies required
294                 $cookie=$query->cookie(-name => 'sessionID',
295                                 -value => '',
296                                 -expires => '');
297                 $loggedin = 1;
298         } elsif ($sessionID=$query->cookie('sessionID')) {
299                 C4::Context->_new_userenv($sessionID);
300                 if (my %hash=$query->cookie('userenv')){
301                                 C4::Context::set_userenv(
302                                         $hash{number},
303                                         $hash{id},
304                                         $hash{cardnumber},
305                                         $hash{firstname},
306                                         $hash{surname},
307                                         $hash{branch},
308                                         $hash{branchname},
309                                         $hash{flags},
310                                         $hash{emailaddress},
311                                         $hash{branchprinter}
312                                 );
313                 }
314                 my ($ip , $lasttime);
315
316                 ($userid, $ip, $lasttime) = $dbh->selectrow_array(
317                                 "SELECT userid,ip,lasttime FROM sessions WHERE sessionid=?",
318                                                                 undef, $sessionID);
319                 if ($logout) {
320                 # voluntary logout the user
321                 $dbh->do("DELETE FROM sessions WHERE sessionID=?", undef, $sessionID);
322                 C4::Context->_unset_userenv($sessionID);
323                 $sessionID = undef;
324                 $userid = undef;
325                 open L, ">>/tmp/sessionlog";
326                 my $time=localtime(time());
327                 printf L "%20s from %16s logged out at %30s (manually).\n", $userid, $ip, $time;
328                 close L;
329                 }
330                 if ($userid) {
331                         if ($lasttime<time()-$timeout) {
332                                 # timed logout
333                                 $info{'timed_out'} = 1;
334                                 $dbh->do("DELETE FROM sessions WHERE sessionID=?", undef, $sessionID);
335                                 C4::Context->_unset_userenv($sessionID);
336                                 $userid = undef;
337                                 $sessionID = undef;
338                                 open L, ">>/tmp/sessionlog";
339                                 my $time=localtime(time());
340                                 printf L "%20s from %16s logged out at %30s (inactivity).\n", $userid, $ip, $time;
341                                 close L;
342                         } elsif ($ip ne $ENV{'REMOTE_ADDR'}) {
343                                 # Different ip than originally logged in from
344                                 $info{'oldip'} = $ip;
345                                 $info{'newip'} = $ENV{'REMOTE_ADDR'};
346                                 $info{'different_ip'} = 1;
347                                 $dbh->do("DELETE FROM sessions WHERE sessionID=?", undef, $sessionID);
348                                 C4::Context->_unset_userenv($sessionID);
349                                 $sessionID = undef;
350                                 $userid = undef;
351                                 open L, ">>/tmp/sessionlog";
352                                 my $time=localtime(time());
353                                 printf L "%20s from logged out at %30s (ip changed from %16s to %16s).\n", $userid, $time, $ip, $info{'newip'};
354                                 close L;
355                         } else {
356                                 $cookie=$query->cookie(-name => 'sessionID',
357                                                 -value => $sessionID,
358                                                 -expires => '');
359                                 $dbh->do("UPDATE sessions SET lasttime=? WHERE sessionID=?",
360                                         undef, (time(), $sessionID));
361                                 $flags = haspermission($dbh, $userid, $flagsrequired);
362                                 if ($flags) {
363                                 $loggedin = 1;
364                                 } else {
365                                 $info{'nopermission'} = 1;
366                                 }
367                         }
368                 }
369         }
370         unless ($userid) {
371                 $sessionID=int(rand()*100000).'-'.time();
372                 $userid=$query->param('userid');
373                 C4::Context->_new_userenv($sessionID);
374                 my $password=$query->param('password');
375                 C4::Context->_new_userenv($sessionID);
376                 my ($return, $cardnumber) = checkpw($dbh,$userid,$password);
377                 if ($return) {
378                         $dbh->do("DELETE FROM sessions WHERE sessionID=? AND userid=?",
379                                 undef, ($sessionID, $userid));
380                         $dbh->do("INSERT INTO sessions (sessionID, userid, ip,lasttime) VALUES (?, ?, ?, ?)",
381                                 undef, ($sessionID, $userid, $ENV{'REMOTE_ADDR'}, time()));
382                         open L, ">>/tmp/sessionlog";
383                         my $time=localtime(time());
384                         printf L "%20s from %16s logged in  at %30s.\n", $userid, $ENV{'REMOTE_ADDR'}, $time;
385                         close L;
386                         $cookie=$query->cookie(-name => 'sessionID',
387                                                 -value => $sessionID,
388                                                 -expires => '');
389                         if ($flags = haspermission($dbh, $userid, $flagsrequired)) {
390                                 $loggedin = 1;
391                         } else {
392                                 $info{'nopermission'} = 1;
393                                         C4::Context->_unset_userenv($sessionID);
394                         }
395                         if ($return == 1){
396                                 my ($bornum,$firstname,$surname,$userflags,$branchcode,$branchname,$branchprinter,$emailaddress);
397                                 my $sth=$dbh->prepare("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=?");
398                                 $sth->execute($userid);
399                                 ($bornum,$firstname,$surname,$userflags,$branchcode,$branchname,$branchprinter,$emailaddress) = $sth->fetchrow if ($sth->rows);
400 #                               warn "$cardnumber,$bornum,$userid,$firstname,$surname,$userflags,$branchcode,$emailaddress";
401                                 unless ($sth->rows){
402                                         my $sth=$dbh->prepare("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=?");
403                                         $sth->execute($cardnumber);
404                                         ($bornum,$firstname,$surname,$userflags,$branchcode,$branchcode,$branchprinter, $emailaddress) = $sth->fetchrow if ($sth->rows);
405 #                                       warn "$cardnumber,$bornum,$userid,$firstname,$surname,$userflags,$branchcode,$emailaddress";
406                                         unless ($sth->rows){
407                                                 $sth->execute($userid);
408                                                 ($bornum,$firstname,$surname,$userflags,$branchcode,$branchprinter,$emailaddress) = $sth->fetchrow if ($sth->rows);
409                                         }
410 #                                       warn "$cardnumber,$bornum,$userid,$firstname,$surname,$userflags,$branchcode,$emailaddress";
411                                 }
412 # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # 
413 #  new op dev :
414 # 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.
415         my $ip = $ENV{'REMOTE_ADDR'};
416         my $branches = getbranches();
417         my @branchesloop;
418         my $branchprinter;
419         foreach my $br (keys %$branches) {
420 #               now we work with the treatment of ip
421                 my $domain=$branches->{$br}->{branchip};
422                 if ($domain && $ip =~ /^$domain/){
423                         $branchcode = $branches->{$br}->{'branchcode'};
424 #                       new op dev : add the branchprinter and branchname in the cookie
425                         $branchprinter = $branches->{$br}->{'branchprinter'};
426                         $branchname = $branches->{$br}->{'branchname'};
427                 }
428         }
429
430                                 my $hash = C4::Context::set_userenv(
431                                         $bornum,
432                                         $userid,
433                                         $cardnumber,
434                                         $firstname,
435                                         $surname,
436                                         $branchcode,
437                                         $branchname,
438                                         $userflags,
439                                         $emailaddress,
440                                         $branchprinter,
441                                 );
442
443                                 $envcookie=$query->cookie(-name => 'userenv',
444                                                 -value => $hash,
445                                                 -expires => '');
446                         } elsif ($return == 2) {
447                         #We suppose the user is the superlibrarian
448                                 my $hash = C4::Context::set_userenv(
449                                         0,0,
450                                         C4::Context->config('user'),
451                                         C4::Context->config('user'),
452                                         C4::Context->config('user'),
453                                         "",1,C4::Context->preference('KohaAdminEmailAddress')
454                                 );
455                                 $envcookie=$query->cookie(-name => 'userenv',
456                                                 -value => $hash,
457                                                 -expires => '');
458                         }
459                 } else {
460                         if ($userid) {
461                                 $info{'invalid_username_or_password'} = 1;
462                                 C4::Context->_unset_userenv($sessionID);
463                         }
464                 }
465         }
466         my $insecure = C4::Context->boolean_preference('insecure');
467         # finished authentification, now respond
468         if ($loggedin || $authnotrequired || (defined($insecure) && $insecure)) {
469                 # successful login
470                 unless ($cookie) {
471                 $cookie=$query->cookie(-name => 'sessionID',
472                                         -value => '',
473                                         -expires => '');
474                 }
475                 if ($envcookie){
476                         return ($userid, [$cookie,$envcookie], $sessionID, $flags)
477                 } else {
478                         return ($userid, $cookie, $sessionID, $flags);
479                 }
480         }
481         # else we have a problem...
482         # get the inputs from the incoming query
483         my @inputs =();
484         foreach my $name (param $query) {
485                 (next) if ($name eq 'userid' || $name eq 'password');
486                 my $value = $query->param($name);
487                 push @inputs, {name => $name , value => $value};
488         }
489
490         my $template = gettemplate($template_name, $type,$query);
491         $template->param(INPUTS => \@inputs);
492         $template->param(loginprompt => 1) unless $info{'nopermission'};
493
494         my $self_url = $query->url(-absolute => 1);
495         $template->param(url => $self_url, LibraryName=> => C4::Context->preference("LibraryName"),);
496         $template->param(\%info);
497         $cookie=$query->cookie(-name => 'sessionID',
498                                         -value => $sessionID,
499                                         -expires => '');
500         print $query->header(
501                 -type => guesstype($template->output),
502                 -cookie => $cookie
503                 ), $template->output;
504         exit;
505 }
506
507
508
509
510 sub checkpw {
511
512         my ($dbh, $userid, $password) = @_;
513 # INTERNAL AUTH
514         my $sth=$dbh->prepare("select password,cardnumber from borrowers where userid=?");
515         $sth->execute($userid);
516         if ($sth->rows) {
517                 my ($md5password,$cardnumber) = $sth->fetchrow;
518                 if (md5_base64($password) eq $md5password) {
519 #                       C4::Context->set_userenv("$bornum",$userid,$cardnumber,$firstname,$surname,$branchcode,$userflags);
520                         return 1,$cardnumber;
521                 }
522         }
523         $sth=$dbh->prepare("select password from borrowers where cardnumber=?");
524         $sth->execute($userid);
525         if ($sth->rows) {
526                 my ($md5password) = $sth->fetchrow;
527                 if (md5_base64($password) eq $md5password) {
528 #                       C4::Context->set_userenv($bornum,$userid,$cardnumber,$firstname,$surname,$branchcode,$userflags);
529                         return 1,$userid;
530                 }
531         }
532         if ($userid eq C4::Context->config('user') && $password eq C4::Context->config('pass')) {
533                 # Koha superuser account
534 #               C4::Context->set_userenv(0,0,C4::Context->config('user'),C4::Context->config('user'),C4::Context->config('user'),"",1);
535                 return 2;
536         }
537         if ($userid eq 'demo' && $password eq 'demo' && C4::Context->config('demo')) {
538                 # DEMO => the demo user is allowed to do everything (if demo set to 1 in koha.conf
539                 # some features won't be effective : modify systempref, modify MARC structure,
540                 return 2;
541         }
542         return 0;
543 }
544
545 sub getuserflags {
546     my $cardnumber=shift;
547     my $dbh=shift;
548     my $userflags;
549     my $sth=$dbh->prepare("SELECT flags FROM borrowers WHERE cardnumber=?");
550     $sth->execute($cardnumber);
551     my ($flags) = $sth->fetchrow;
552         $flags=0 unless $flags;
553     $sth=$dbh->prepare("SELECT bit, flag, defaulton FROM userflags");
554     $sth->execute;
555     while (my ($bit, $flag, $defaulton) = $sth->fetchrow) {
556                 if (($flags & (2**$bit)) || $defaulton) {
557                         $userflags->{$flag}=1;
558                 } else {
559                         $userflags->{$flag}=0;
560                 }
561     }
562     return $userflags;
563 }
564
565 sub haspermission {
566     my ($dbh, $userid, $flagsrequired) = @_;
567     my $sth=$dbh->prepare("SELECT cardnumber FROM borrowers WHERE userid=?");
568     $sth->execute($userid);
569     my ($cardnumber) = $sth->fetchrow;
570     ($cardnumber) || ($cardnumber=$userid);
571     my $flags=getuserflags($cardnumber,$dbh);
572     my $configfile;
573     if ($userid eq C4::Context->config('user')) {
574         # Super User Account from /etc/koha.conf
575         $flags->{'superlibrarian'}=1;
576      }
577      if ($userid eq 'demo' && C4::Context->config('demo')) {
578         # Demo user that can do "anything" (demo=1 in /etc/koha.conf)
579         $flags->{'superlibrarian'}=1;
580     }
581     return $flags if $flags->{superlibrarian};
582     foreach (keys %$flagsrequired) {
583         return $flags if $flags->{$_};
584     }
585     return 0;
586 }
587
588 sub getborrowernumber {
589     my ($userid) = @_;
590     my $dbh = C4::Context->dbh;
591     for my $field ('userid', 'cardnumber') {
592       my $sth=$dbh->prepare
593           ("select borrowernumber from borrowers where $field=?");
594       $sth->execute($userid);
595       if ($sth->rows) {
596         my ($bnumber) = $sth->fetchrow;
597         return $bnumber;
598       }
599     }
600     return 0;
601 }
602
603 END { }       # module clean-up code here (global destructor)
604 1;
605 __END__
606
607 =back
608
609 =head1 SEE ALSO
610
611 CGI(3)
612
613 C4::Output(3)
614
615 Digest::MD5(3)
616
617 =cut