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