adding LibraryName variable in the template
[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'});
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, $flags) = getpatroninformation(undef, $borrowernumber);
125                 my @bordat;
126                 $bordat[0] = $borr;
127                 $template->param(USER_INFO => \@bordat,
128                 );
129         }
130         $template->param(
131                              LibraryName => C4::Context->preference("LibraryName"),
132                 );
133         return ($template, $borrowernumber, $cookie);
134 }
135
136
137 =item checkauth
138
139   ($userid, $cookie, $sessionID) = &checkauth($query, $noauth, $flagsrequired, $type);
140
141 Verifies that the user is authorized to run this script.  If
142 the user is authorized, a (userid, cookie, session-id, flags)
143 quadruple is returned.  If the user is not authorized but does
144 not have the required privilege (see $flagsrequired below), it
145 displays an error page and exits.  Otherwise, it displays the
146 login page and exits.
147
148 Note that C<&checkauth> will return if and only if the user
149 is authorized, so it should be called early on, before any
150 unfinished operations (e.g., if you've opened a file, then
151 C<&checkauth> won't close it for you).
152
153 C<$query> is the CGI object for the script calling C<&checkauth>.
154
155 The C<$noauth> argument is optional. If it is set, then no
156 authorization is required for the script.
157
158 C<&checkauth> fetches user and session information from C<$query> and
159 ensures that the user is authorized to run scripts that require
160 authorization.
161
162 The C<$flagsrequired> argument specifies the required privileges
163 the user must have if the username and password are correct.
164 It should be specified as a reference-to-hash; keys in the hash
165 should be the "flags" for the user, as specified in the Members
166 intranet module. Any key specified must correspond to a "flag"
167 in the userflags table. E.g., { circulate => 1 } would specify
168 that the user must have the "circulate" privilege in order to
169 proceed. To make sure that access control is correct, the
170 C<$flagsrequired> parameter must be specified correctly.
171
172 The C<$type> argument specifies whether the template should be
173 retrieved from the opac or intranet directory tree.  "opac" is
174 assumed if it is not specified; however, if C<$type> is specified,
175 "intranet" is assumed if it is not "opac".
176
177 If C<$query> does not have a valid session ID associated with it
178 (i.e., the user has not logged in) or if the session has expired,
179 C<&checkauth> presents the user with a login page (from the point of
180 view of the original script, C<&checkauth> does not return). Once the
181 user has authenticated, C<&checkauth> restarts the original script
182 (this time, C<&checkauth> returns).
183
184 The login page is provided using a HTML::Template, which is set in the
185 systempreferences table or at the top of this file. The variable C<$type>
186 selects which template to use, either the opac or the intranet 
187 authentification template.
188
189 C<&checkauth> returns a user ID, a cookie, and a session ID. The
190 cookie should be sent back to the browser; it verifies that the user
191 has authenticated.
192
193 =cut
194
195
196
197 sub checkauth {
198         my $query=shift;
199         # $authnotrequired will be set for scripts which will run without authentication
200         my $authnotrequired = shift;
201         my $flagsrequired = shift;
202         my $type = shift;
203         $type = 'opac' unless $type;
204
205         my $dbh = C4::Context->dbh;
206         my $timeout = C4::Context->preference('timeout');
207         $timeout = 600 unless $timeout;
208
209         my $template_name;
210         if ($type eq 'opac') {
211                 $template_name = "opac-auth.tmpl";
212         } else {
213                 $template_name = "auth.tmpl";
214         }
215
216         # state variables
217         my $loggedin = 0;
218         my %info;
219         my ($userid, $cookie, $sessionID, $flags);
220         my $logout = $query->param('logout.x');
221         if ($userid = $ENV{'REMOTE_USER'}) {
222                 # Using Basic Authentication, no cookies required
223                 $cookie=$query->cookie(-name => 'sessionID',
224                                 -value => '',
225                                 -expires => '');
226                 $loggedin = 1;
227         } elsif ($sessionID=$query->cookie('sessionID')) {
228                 my ($ip , $lasttime);
229                 ($userid, $ip, $lasttime) = $dbh->selectrow_array(
230                                 "SELECT userid,ip,lasttime FROM sessions WHERE sessionid=?",
231                                                                 undef, $sessionID);
232                 if ($logout) {
233                 # voluntary logout the user
234                 $dbh->do("DELETE FROM sessions WHERE sessionID=?", undef, $sessionID);
235                 $sessionID = undef;
236                 $userid = undef;
237                 open L, ">>/tmp/sessionlog";
238                 my $time=localtime(time());
239                 printf L "%20s from %16s logged out at %30s (manually).\n", $userid, $ip, $time;
240                 close L;
241                 }
242                 if ($userid) {
243                 if ($lasttime<time()-$timeout) {
244                         # timed logout
245                         $info{'timed_out'} = 1;
246                         $dbh->do("DELETE FROM sessions WHERE sessionID=?", undef, $sessionID);
247                         $userid = undef;
248                         $sessionID = undef;
249                         open L, ">>/tmp/sessionlog";
250                         my $time=localtime(time());
251                         printf L "%20s from %16s logged out at %30s (inactivity).\n", $userid, $ip, $time;
252                         close L;
253                 } elsif ($ip ne $ENV{'REMOTE_ADDR'}) {
254                         # Different ip than originally logged in from
255                         $info{'oldip'} = $ip;
256                         $info{'newip'} = $ENV{'REMOTE_ADDR'};
257                         $info{'different_ip'} = 1;
258                         $dbh->do("DELETE FROM sessions WHERE sessionID=?", undef, $sessionID);
259                         $sessionID = undef;
260                         $userid = undef;
261                         open L, ">>/tmp/sessionlog";
262                         my $time=localtime(time());
263                         printf L "%20s from logged out at %30s (ip changed from %16s to %16s).\n", $userid, $time, $ip, $info{'newip'};
264                         close L;
265                 } else {
266                         $cookie=$query->cookie(-name => 'sessionID',
267                                         -value => $sessionID,
268                                         -expires => '');
269                         $dbh->do("UPDATE sessions SET lasttime=? WHERE sessionID=?",
270                                 undef, (time(), $sessionID));
271                         $flags = haspermission($dbh, $userid, $flagsrequired);
272                         if ($flags) {
273                         $loggedin = 1;
274                         } else {
275                         $info{'nopermission'} = 1;
276                         }
277                 }
278                 }
279         }
280         unless ($userid) {
281                 $sessionID=int(rand()*100000).'-'.time();
282                 $userid=$query->param('userid');
283                 my $password=$query->param('password');
284                 my ($return, $cardnumber) = checkpw($dbh,$userid,$password);
285                 if ($return) {
286                 $dbh->do("DELETE FROM sessions WHERE sessionID=? AND userid=?",
287                         undef, ($sessionID, $userid));
288                 $dbh->do("INSERT INTO sessions (sessionID, userid, ip,lasttime) VALUES (?, ?, ?, ?)",
289                         undef, ($sessionID, $userid, $ENV{'REMOTE_ADDR'}, time()));
290                 open L, ">>/tmp/sessionlog";
291                 my $time=localtime(time());
292                 printf L "%20s from %16s logged in  at %30s.\n", $userid, $ENV{'REMOTE_ADDR'}, $time;
293                 close L;
294                 $cookie=$query->cookie(-name => 'sessionID',
295                                         -value => $sessionID,
296                                         -expires => '');
297                 if ($flags = haspermission($dbh, $userid, $flagsrequired)) {
298                         $loggedin = 1;
299                 } else {
300                         $info{'nopermission'} = 1;
301                 }
302                 } else {
303                 if ($userid) {
304                         $info{'invalid_username_or_password'} = 1;
305                 }
306                 }
307         }
308         my $insecure = C4::Context->boolean_preference('insecure');
309         # finished authentification, now respond
310         if ($loggedin || $authnotrequired || (defined($insecure) && $insecure)) {
311                 # successful login
312                 unless ($cookie) {
313                 $cookie=$query->cookie(-name => 'sessionID',
314                                         -value => '',
315                                         -expires => '');
316                 }
317                 return ($userid, $cookie, $sessionID, $flags);
318         }
319         # else we have a problem...
320         # get the inputs from the incoming query
321         my @inputs =();
322         foreach my $name (param $query) {
323                 (next) if ($name eq 'userid' || $name eq 'password');
324                 my $value = $query->param($name);
325                 push @inputs, {name => $name , value => $value};
326         }
327
328         my $template = gettemplate($template_name, $type);
329         $template->param(INPUTS => \@inputs);
330         $template->param(loginprompt => 1) unless $info{'nopermission'};
331
332         my $self_url = $query->url(-absolute => 1);
333         $template->param(url => $self_url);
334         $template->param(\%info);
335         $cookie=$query->cookie(-name => 'sessionID',
336                                         -value => $sessionID,
337                                         -expires => '');
338         print $query->header(
339                 -type => guesstype($template->output),
340                 -cookie => $cookie
341                 ), $template->output;
342         exit;
343 }
344
345
346
347
348 sub checkpw {
349
350 # This should be modified to allow a selection of authentication schemes
351 # (e.g. LDAP), as well as local authentication through the borrowers
352 # tables passwd field
353 #
354         my ($dbh, $userid, $password) = @_;
355 # LDAP
356 #       my $sth=$dbh->prepare("select value from systempreferences where variable=?");
357 #       $sth->execute("ldapserver");
358         my $ldapserver = C4::Context->preferences('ldapserver');
359         if ($ldapserver) {
360                 my $ldapinfos = C4::Context->preferences('ldapinfos');
361                 my %bindargs;
362                 my $nom  = "uid=$userid, $ldapinfos";
363                 my $db = Net::LDAP->new( $ldapserver );
364                 $bindargs{dn}=$nom;
365                 $bindargs{password}=$password;
366                 my $res =$db->bind(%bindargs);
367                 if($res->code) {
368                 # auth refused
369                         return 0;
370                 } else {
371                         #get the cardnumber
372                         my $sth=$dbh->prepare("select cardnumber from borrowers where userid=?");
373                         $sth->execute($userid);
374                         if ($sth->rows) {
375                                 my $cardnumber = $sth->fetchrow;
376                                 #we have the cardnumber
377                                 return 1,$cardnumber;
378                         }
379                         if ($userid eq C4::Context->config('user') && $password eq C4::Context->config('pass')) {
380                                 # Koha superuser account
381                                 return 2;
382                         }
383                 }
384         }
385 # INTERNAL AUTH
386         my $sth=$dbh->prepare("select password,cardnumber from borrowers where userid=?");
387         $sth->execute($userid);
388         if ($sth->rows) {
389                 my ($md5password,$cardnumber) = $sth->fetchrow;
390                 if (md5_base64($password) eq $md5password) {
391                         return 1,$cardnumber;
392                 }
393         }
394         my $sth=$dbh->prepare("select password from borrowers where cardnumber=?");
395         $sth->execute($userid);
396         if ($sth->rows) {
397                 my ($md5password) = $sth->fetchrow;
398                 if (md5_base64($password) eq $md5password) {
399                         return 1,$userid;
400                 }
401         }
402         if ($userid eq C4::Context->config('user') && $password eq C4::Context->config('pass')) {
403                 # Koha superuser account
404                 return 2;
405         }
406         if ($userid eq 'demo' && $password eq 'demo' && C4::Context->config('demo')) {
407                 # DEMO => the demo user is allowed to do everything (if demo set to 1 in koha.conf
408                 # some features won't be effective : modify systempref, modify MARC structure,
409                 return 2;
410         }
411         return 0;
412 }
413
414 sub getuserflags {
415     my $cardnumber=shift;
416     my $dbh=shift;
417     my $userflags;
418     my $sth=$dbh->prepare("SELECT flags FROM borrowers WHERE cardnumber=?");
419     $sth->execute($cardnumber);
420     my ($flags) = $sth->fetchrow;
421     $sth=$dbh->prepare("SELECT bit, flag, defaulton FROM userflags");
422     $sth->execute;
423     while (my ($bit, $flag, $defaulton) = $sth->fetchrow) {
424         if (($flags & (2**$bit)) || $defaulton) {
425             $userflags->{$flag}=1;
426         }
427     }
428     return $userflags;
429 }
430
431 sub haspermission {
432     my ($dbh, $userid, $flagsrequired) = @_;
433     my $sth=$dbh->prepare("SELECT cardnumber FROM borrowers WHERE userid=?");
434     $sth->execute($userid);
435     my ($cardnumber) = $sth->fetchrow;
436     ($cardnumber) || ($cardnumber=$userid);
437     my $flags=getuserflags($cardnumber,$dbh);
438     my $configfile;
439     if ($userid eq C4::Context->config('user')) {
440         # Super User Account from /etc/koha.conf
441         $flags->{'superlibrarian'}=1;
442      }
443      if ($userid eq 'demo' && C4::Context->config('demo')) {
444         # Demo user that can do "anything" (demo=1 in /etc/koha.conf)
445         $flags->{'superlibrarian'}=1;
446     }
447     return $flags if $flags->{superlibrarian};
448     foreach (keys %$flagsrequired) {
449         return $flags if $flags->{$_};
450     }
451     return 0;
452 }
453
454 sub getborrowernumber {
455     my ($userid) = @_;
456     my $dbh = C4::Context->dbh;
457     for my $field ('userid', 'cardnumber') {
458       my $sth=$dbh->prepare
459           ("select borrowernumber from borrowers where $field=?");
460       $sth->execute($userid);
461       if ($sth->rows) {
462         my ($bnumber) = $sth->fetchrow;
463         return $bnumber;
464       }
465     }
466     return 0;
467 }
468
469 END { }       # module clean-up code here (global destructor)
470 1;
471 __END__
472
473 =back
474
475 =head1 SEE ALSO
476
477 CGI(3)
478
479 C4::Output(3)
480
481 Digest::MD5(3)
482
483 =cut