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