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