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