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