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