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