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