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