some bugfixes, but still don't work correctly
[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                 warn "NEWUSERENV : ".$sessionID;
298                 C4::Context->_new_userenv($sessionID);
299                 my ($ip , $lasttime);
300                 ($userid, $ip, $lasttime) = $dbh->selectrow_array(
301                                 "SELECT userid,ip,lasttime FROM sessions WHERE sessionid=?",
302                                                                 undef, $sessionID);
303                 if ($logout) {
304                         # voluntary logout the user
305                         $dbh->do("DELETE FROM sessions WHERE sessionID=?", undef, $sessionID);
306                         C4::Context->_unset_userenv($sessionID);
307                         warn "DEL USERENV0";
308                         $sessionID = undef;
309                         $userid = undef;
310                         open L, ">>/tmp/sessionlog";
311                         my $time=localtime(time());
312                         printf L "%20s from %16s logged out at %30s (manually).\n", $userid, $ip, $time;
313                         close L;
314                 }
315                 if ($userid) {
316                         if ($lasttime<time()-$timeout) {
317                                 # timed logout
318                                 $info{'timed_out'} = 1;
319                                 $dbh->do("DELETE FROM sessions WHERE sessionID=?", undef, $sessionID);
320                                 C4::Context->_unset_userenv($sessionID);
321                                 warn "DEL USERENV1";
322                                 $userid = undef;
323                                 $sessionID = undef;
324                                 open L, ">>/tmp/sessionlog";
325                                 my $time=localtime(time());
326                                 printf L "%20s from %16s logged out at %30s (inactivity).\n", $userid, $ip, $time;
327                                 close L;
328                         } elsif ($ip ne $ENV{'REMOTE_ADDR'}) {
329                                 # Different ip than originally logged in from
330                                 $info{'oldip'} = $ip;
331                                 $info{'newip'} = $ENV{'REMOTE_ADDR'};
332                                 $info{'different_ip'} = 1;
333                                 $dbh->do("DELETE FROM sessions WHERE sessionID=?", undef, $sessionID);
334                                 C4::Context->_unset_userenv($sessionID);
335                                 warn "DEL USERENV2";
336                                 $sessionID = undef;
337                                 $userid = undef;
338                                 open L, ">>/tmp/sessionlog";
339                                 my $time=localtime(time());
340                                 printf L "%20s from logged out at %30s (ip changed from %16s to %16s).\n", $userid, $time, $ip, $info{'newip'};
341                                 close L;
342                         } else {
343                                 $cookie=$query->cookie(-name => 'sessionID',
344                                                 -value => $sessionID,
345                                                 -expires => '');
346                                 $dbh->do("UPDATE sessions SET lasttime=? WHERE sessionID=?",
347                                         undef, (time(), $sessionID));
348                                 $flags = haspermission($dbh, $userid, $flagsrequired);
349                                 if ($flags) {
350                                 $loggedin = 1;
351                                 } else {
352                                 $info{'nopermission'} = 1;
353                                 }
354                         }
355                 }
356         }
357         unless ($userid) {
358                 $sessionID=int(rand()*100000).'-'.time();
359                 $userid=$query->param('userid');
360                 warn "NEWUSERENV : ".$sessionID;
361                 C4::Context->_new_userenv($sessionID);
362                 my $password=$query->param('password');
363                 my ($return, $cardnumber) = checkpw($dbh,$userid,$password);
364                 if ($return) {
365                 $dbh->do("DELETE FROM sessions WHERE sessionID=? AND userid=?",
366                         undef, ($sessionID, $userid));
367                 $dbh->do("INSERT INTO sessions (sessionID, userid, ip,lasttime) VALUES (?, ?, ?, ?)",
368                         undef, ($sessionID, $userid, $ENV{'REMOTE_ADDR'}, time()));
369                 open L, ">>/tmp/sessionlog";
370                 my $time=localtime(time());
371                 printf L "%20s from %16s logged in  at %30s.\n", $userid, $ENV{'REMOTE_ADDR'}, $time;
372                 close L;
373                 $cookie=$query->cookie(-name => 'sessionID',
374                                         -value => $sessionID,
375                                         -expires => '');
376                 
377                 if ($flags = haspermission($dbh, $userid, $flagsrequired)) {
378                         $loggedin = 1;
379                 } else {
380                         $info{'nopermission'} = 1;
381                         C4::Context->_unset_userenv($sessionID);
382                 }
383                 } else {
384                 if ($userid) {
385                         $info{'invalid_username_or_password'} = 1;
386                         C4::Context->_unset_userenv($sessionID);
387                 }
388                 }
389         }
390         my $insecure = C4::Context->boolean_preference('insecure');
391         # finished authentification, now respond
392         if ($loggedin || $authnotrequired || (defined($insecure) && $insecure)) {
393                 # successful login
394                 unless ($cookie) {
395                 $cookie=$query->cookie(-name => 'sessionID',
396                                         -value => '',
397                                         -expires => '');
398                 }
399                 return ($userid, $cookie, $sessionID, $flags);
400         }
401         # else we have a problem...
402         # get the inputs from the incoming query
403         my @inputs =();
404         foreach my $name (param $query) {
405                 (next) if ($name eq 'userid' || $name eq 'password');
406                 my $value = $query->param($name);
407                 push @inputs, {name => $name , value => $value};
408         }
409
410         my $template = gettemplate($template_name, $type,$query);
411         $template->param(INPUTS => \@inputs);
412         $template->param(loginprompt => 1) unless $info{'nopermission'};
413
414         my $self_url = $query->url(-absolute => 1);
415         $template->param(url => $self_url, LibraryName=> => C4::Context->preference("LibraryName"),);
416         $template->param(\%info);
417         $cookie=$query->cookie(-name => 'sessionID',
418                                         -value => $sessionID,
419                                         -expires => '');
420         print $query->header(
421                 -type => guesstype($template->output),
422                 -cookie => $cookie
423                 ), $template->output;
424         exit;
425 }
426
427
428
429
430 sub checkpw {
431
432         my ($dbh, $userid, $password) = @_;
433 # INTERNAL AUTH
434         my $sth=$dbh->prepare("select password,cardnumber,borrowernumber,userid,firstname,surname,flags,branchcode  from borrowers where userid=?");
435         $sth->execute($userid);
436         if ($sth->rows) {
437                 my ($md5password,$cardnumber,$bornum,$userid,$firstname,$surname,$userflags,$branchcode) = $sth->fetchrow;
438                 if (md5_base64($password) eq $md5password) {
439                         warn "setuserenv1 $bornum,$userid,$cardnumber,$firstname,$surname,$branchcode,$userflags";
440                         C4::Context->set_userenv($bornum,$userid,$cardnumber,$firstname,$surname,$branchcode,$userflags);
441                         return 1,$cardnumber;
442                 }
443         }
444         my $sth=$dbh->prepare("select password,cardnumber,borrowernumber,userid,firstname,surname,flags,branchcode from borrowers where cardnumber=?");
445         $sth->execute($userid);
446         if ($sth->rows) {
447                 my ($md5password,$cardnumber,$bornum,$userid,$firstname,$surname,$userflags,$branchcode) = $sth->fetchrow;
448                 if (md5_base64($password) eq $md5password) {
449                         warn "setuserenv2 $bornum,$userid,$cardnumber,$firstname,$surname,$branchcode,$userflags";
450                         C4::Context->set_userenv($bornum,$userid,$cardnumber,$firstname,$surname,$branchcode,$userflags);
451                         return 1,$userid;
452                 }
453         }
454         if ($userid eq C4::Context->config('user') && $password eq C4::Context->config('pass')) {
455                 # Koha superuser account
456                         warn "setuserenv3";
457                 C4::Context->set_userenv(0,0,C4::Context->config('user'),C4::Context->config('user'),C4::Context->config('user'),"",1);
458                 return 2;
459         }
460         if ($userid eq 'demo' && $password eq 'demo' && C4::Context->config('demo')) {
461                 # DEMO => the demo user is allowed to do everything (if demo set to 1 in koha.conf
462                 # some features won't be effective : modify systempref, modify MARC structure,
463                 return 2;
464         }
465         return 0;
466 }
467
468 sub getuserflags {
469     my $cardnumber=shift;
470     my $dbh=shift;
471     my $userflags;
472     my $sth=$dbh->prepare("SELECT flags FROM borrowers WHERE cardnumber=?");
473     $sth->execute($cardnumber);
474     my ($flags) = $sth->fetchrow;
475     $sth=$dbh->prepare("SELECT bit, flag, defaulton FROM userflags");
476     $sth->execute;
477     while (my ($bit, $flag, $defaulton) = $sth->fetchrow) {
478         if (($flags & (2**$bit)) || $defaulton) {
479             $userflags->{$flag}=1;
480         }
481     }
482     return $userflags;
483 }
484
485 sub haspermission {
486     my ($dbh, $userid, $flagsrequired) = @_;
487     my $sth=$dbh->prepare("SELECT cardnumber FROM borrowers WHERE userid=?");
488     $sth->execute($userid);
489     my ($cardnumber) = $sth->fetchrow;
490     ($cardnumber) || ($cardnumber=$userid);
491     my $flags=getuserflags($cardnumber,$dbh);
492     my $configfile;
493     if ($userid eq C4::Context->config('user')) {
494         # Super User Account from /etc/koha.conf
495         $flags->{'superlibrarian'}=1;
496      }
497      if ($userid eq 'demo' && C4::Context->config('demo')) {
498         # Demo user that can do "anything" (demo=1 in /etc/koha.conf)
499         $flags->{'superlibrarian'}=1;
500     }
501     return $flags if $flags->{superlibrarian};
502     foreach (keys %$flagsrequired) {
503         return $flags if $flags->{$_};
504     }
505     return 0;
506 }
507
508 sub getborrowernumber {
509     my ($userid) = @_;
510     my $dbh = C4::Context->dbh;
511     for my $field ('userid', 'cardnumber') {
512       my $sth=$dbh->prepare
513           ("select borrowernumber from borrowers where $field=?");
514       $sth->execute($userid);
515       if ($sth->rows) {
516         my ($bnumber) = $sth->fetchrow;
517         return $bnumber;
518       }
519     }
520     return 0;
521 }
522
523 END { }       # module clean-up code here (global destructor)
524 1;
525 __END__
526
527 =back
528
529 =head1 SEE ALSO
530
531 CGI(3)
532
533 C4::Output(3)
534
535 Digest::MD5(3)
536
537 =cut