session management: use YAML::Syck for serialization
[koha.git] / C4 / Auth.pm
1
2 # -*- tab-width: 8 -*-
3 # NOTE: This file uses 8-character tabs; do not change the tab size!
4
5 package C4::Auth;
6
7 # Copyright 2000-2002 Katipo Communications
8 #
9 # This file is part of Koha.
10 #
11 # Koha is free software; you can redistribute it and/or modify it under the
12 # terms of the GNU General Public License as published by the Free Software
13 # Foundation; either version 2 of the License, or (at your option) any later
14 # version.
15 #
16 # Koha is distributed in the hope that it will be useful, but WITHOUT ANY
17 # WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
18 # A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
19 #
20 # You should have received a copy of the GNU General Public License along with
21 # Koha; if not, write to the Free Software Foundation, Inc., 59 Temple Place,
22 # Suite 330, Boston, MA  02111-1307 USA
23
24 use strict;
25 use Digest::MD5 qw(md5_base64);
26 use CGI::Session;
27
28 require Exporter;
29 use C4::Context;
30 use C4::Output;    # to get the template
31 use C4::Members;
32 use C4::Koha;
33 use C4::Branch; # GetBranches
34
35 # use utf8;
36 use vars qw($VERSION @ISA @EXPORT @EXPORT_OK %EXPORT_TAGS $debug $ldap);
37
38 BEGIN {
39     $VERSION = 3.01;        # set version for version checking
40     $debug = $ENV{DEBUG} || 0 ;
41     @ISA   = qw(Exporter);
42     @EXPORT    = qw(&checkauth &get_template_and_user);
43     @EXPORT_OK = qw(&check_api_auth &get_session &check_cookie_auth &checkpw);
44     $ldap = C4::Context->config('useldapserver') || 0;
45     if ($ldap) {
46         require C4::Auth_with_ldap;             # no import
47         import  C4::Auth_with_ldap qw(checkpw_ldap);
48     }
49 }
50
51 =head1 NAME
52
53 C4::Auth - Authenticates Koha users
54
55 =head1 SYNOPSIS
56
57   use CGI;
58   use C4::Auth;
59
60   my $query = new CGI;
61
62   my ($template, $borrowernumber, $cookie) 
63     = get_template_and_user(
64         {
65             template_name   => "opac-main.tmpl",
66             query           => $query,
67       type            => "opac",
68       authnotrequired => 1,
69       flagsrequired   => {borrow => 1},
70   }
71     );
72
73   print $query->header(
74     -type => 'utf-8',
75     -cookie => $cookie
76   ), $template->output;
77
78
79 =head1 DESCRIPTION
80
81     The main function of this module is to provide
82     authentification. However the get_template_and_user function has
83     been provided so that a users login information is passed along
84     automatically. This gets loaded into the template.
85
86 =head1 FUNCTIONS
87
88 =over 2
89
90 =item get_template_and_user
91
92     my ($template, $borrowernumber, $cookie)
93         = get_template_and_user(
94           {
95             template_name   => "opac-main.tmpl",
96             query           => $query,
97             type            => "opac",
98             authnotrequired => 1,
99             flagsrequired   => {borrow => 1},
100           }
101         );
102
103     This call passes the C<query>, C<flagsrequired> and C<authnotrequired>
104     to C<&checkauth> (in this module) to perform authentification.
105     See C<&checkauth> for an explanation of these parameters.
106
107     The C<template_name> is then used to find the correct template for
108     the page. The authenticated users details are loaded onto the
109     template in the HTML::Template LOOP variable C<USER_INFO>. Also the
110     C<sessionID> is passed to the template. This can be used in templates
111     if cookies are disabled. It needs to be put as and input to every
112     authenticated page.
113
114     More information on the C<gettemplate> sub can be found in the
115     Output.pm module.
116
117 =cut
118
119 sub get_template_and_user {
120     my $in       = shift;
121     my $template =
122       gettemplate( $in->{'template_name'}, $in->{'type'}, $in->{'query'} );
123     my ( $user, $cookie, $sessionID, $flags ) = checkauth(
124         $in->{'query'},
125         $in->{'authnotrequired'},
126         $in->{'flagsrequired'},
127         $in->{'type'}
128     ) unless ($in->{'template_name'}=~/maintenance/);
129
130     my $borrowernumber;
131     my $insecure = C4::Context->preference('insecure');
132     if ($user or $insecure) {
133
134         # load the template variables for stylesheets and JavaScript
135         $template->param( css_libs => $in->{'css_libs'} );
136         $template->param( css_module => $in->{'css_module'} );
137         $template->param( css_page => $in->{'css_page'} );
138         $template->param( css_widgets => $in->{'css_widgets'} );
139
140         $template->param( js_libs => $in->{'js_libs'} );
141         $template->param( js_module => $in->{'js_module'} );
142         $template->param( js_page => $in->{'js_page'} );
143         $template->param( js_widgets => $in->{'js_widgets'} );
144
145         # user info
146         $template->param( loggedinusername => $user );
147         $template->param( sessionID        => $sessionID );
148
149         $borrowernumber = getborrowernumber($user);
150         my ( $borr, $alternativeflags ) =
151           GetMemberDetails( $borrowernumber );
152         my @bordat;
153         $bordat[0] = $borr;
154         $template->param( "USER_INFO" => \@bordat );
155
156         my @flagroots = qw(circulate catalogue parameters borrowers permissions reserveforothers borrow
157                             editcatalogue updatecharge management tools editauthorities serials reports);
158         # We are going to use the $flags returned by checkauth
159         # to create the template's parameters that will indicate
160         # which menus the user can access.
161         if (( $flags && $flags->{superlibrarian}==1) or $insecure==1) {
162             $template->param( CAN_user_circulate        => 1 );
163             $template->param( CAN_user_catalogue        => 1 );
164             $template->param( CAN_user_parameters       => 1 );
165             $template->param( CAN_user_borrowers        => 1 );
166             $template->param( CAN_user_permission       => 1 );
167             $template->param( CAN_user_reserveforothers => 1 );
168             $template->param( CAN_user_borrow           => 1 );
169             $template->param( CAN_user_editcatalogue    => 1 );
170             $template->param( CAN_user_updatecharge     => 1 );
171             $template->param( CAN_user_acquisition      => 1 );
172             $template->param( CAN_user_management       => 1 );
173             $template->param( CAN_user_tools            => 1 ); 
174             $template->param( CAN_user_editauthorities  => 1 );
175             $template->param( CAN_user_serials          => 1 );
176             $template->param( CAN_user_reports          => 1 );
177             $template->param( CAN_user_staffaccess      => 1 );
178         }
179
180         if ( $flags && $flags->{circulate} == 1 ) {
181             $template->param( CAN_user_circulate => 1 );
182         }
183
184         if ( $flags && $flags->{catalogue} == 1 ) {
185             $template->param( CAN_user_catalogue => 1 );
186         }
187
188         if ( $flags && $flags->{parameters} == 1 ) {
189             $template->param( CAN_user_parameters => 1 );
190             $template->param( CAN_user_management => 1 );
191         }
192
193         if ( $flags && $flags->{borrowers} == 1 ) {
194             $template->param( CAN_user_borrowers => 1 );
195         }
196
197         if ( $flags && $flags->{permissions} == 1 ) {
198             $template->param( CAN_user_permission => 1 );
199         }
200
201         if ( $flags && $flags->{reserveforothers} == 1 ) {
202             $template->param( CAN_user_reserveforothers => 1 );
203         }
204
205         if ( $flags && $flags->{borrow} == 1 ) {
206             $template->param( CAN_user_borrow => 1 );
207         }
208
209         if ( $flags && $flags->{editcatalogue} == 1 ) {
210             $template->param( CAN_user_editcatalogue => 1 );
211         }
212
213         if ( $flags && $flags->{updatecharges} == 1 ) {
214             $template->param( CAN_user_updatecharge => 1 );
215         }
216
217         if ( $flags && $flags->{acquisition} == 1 ) {
218             $template->param( CAN_user_acquisition => 1 );
219         }
220
221         if ( $flags && $flags->{tools} == 1 ) {
222             $template->param( CAN_user_tools => 1 );
223         }
224   
225         if ( $flags && $flags->{editauthorities} == 1 ) {
226             $template->param( CAN_user_editauthorities => 1 );
227         }
228     
229         if ( $flags && $flags->{serials} == 1 ) {
230             $template->param( CAN_user_serials => 1 );
231         }
232
233         if ( $flags && $flags->{reports} == 1 ) {
234             $template->param( CAN_user_reports => 1 );
235         }
236         if ( $flags && $flags->{staffaccess} == 1 ) {
237             $template->param( CAN_user_staffaccess => 1 );
238         }
239     }
240     if ( $in->{'type'} eq "intranet" ) {
241         $template->param(
242             intranetcolorstylesheet => C4::Context->preference("intranetcolorstylesheet"),
243             intranetstylesheet => C4::Context->preference("intranetstylesheet"),
244             IntranetNav        => C4::Context->preference("IntranetNav"),
245             intranetuserjs     => C4::Context->preference("intranetuserjs"),
246             TemplateEncoding   => C4::Context->preference("TemplateEncoding"),
247             AmazonContent      => C4::Context->preference("AmazonContent"),
248             LibraryName        => C4::Context->preference("LibraryName"),
249             LoginBranchcode    => (C4::Context->userenv?C4::Context->userenv->{"branch"}:"insecure"),
250             LoginBranchname    => (C4::Context->userenv?C4::Context->userenv->{"branchname"}:"insecure"),
251             LoginFirstname     => (C4::Context->userenv?C4::Context->userenv->{"firstname"}:"Bel"),
252             LoginSurname       => C4::Context->userenv?C4::Context->userenv->{"surname"}:"Inconnu", 
253             AutoLocation       => C4::Context->preference("AutoLocation"),
254             hide_marc          => C4::Context->preference("hide_marc"),
255             patronimages       => C4::Context->preference("patronimages"),
256             "BiblioDefaultView".C4::Context->preference("IntranetBiblioDefaultView") => 1,
257             advancedMARCEditor      => C4::Context->preference("advancedMARCEditor"),
258             suggestion              => C4::Context->preference("suggestion"),
259             virtualshelves          => C4::Context->preference("virtualshelves"),
260             LibraryName             => C4::Context->preference("LibraryName"),
261             KohaAdminEmailAddress   => "" . C4::Context->preference("KohaAdminEmailAddress"),
262             IntranetmainUserblock   => C4::Context->preference("IntranetmainUserblock"),
263             IndependantBranches     => C4::Context->preference("IndependantBranches"),
264             CircAutocompl => C4::Context->preference("CircAutocompl"),
265             yuipath => C4::Context->preference("yuipath"),
266             FRBRizeEditions => C4::Context->preference("FRBRizeEditions"),
267             AmazonSimilarItems => C4::Context->preference("AmazonSimilarItems"),
268             'item-level_itypes' => C4::Context->preference('item-level_itypes'),
269             intranetreadinghistory => C4::Context->preference('intranetreadinghistory'),
270             canreservefromotherbranches => C4::Context->preference('canreservefromotherbranches'),
271         );
272     }
273     else {
274         warn "template type should be OPAC, here it is=[" . $in->{'type'} . "]" unless ( $in->{'type'} eq 'opac' );
275         my $LibraryNameTitle = C4::Context->preference("LibraryName");
276         $LibraryNameTitle =~ s/<(?:\/?)(?:br|p)\s*(?:\/?)>/ /sgi;
277         $LibraryNameTitle =~ s/<(?:[^<>'"]|'(?:[^']*)'|"(?:[^"]*)")*>//sg;
278   $template->param(
279             KohaAdminEmailAddress  => "" . C4::Context->preference("KohaAdminEmailAddress"),
280             AnonSuggestions =>  "" . C4::Context->preference("AnonSuggestions"),
281             suggestion             => "" . C4::Context->preference("suggestion"),
282             virtualshelves         => "" . C4::Context->preference("virtualshelves"),
283             OpacNav                => "" . C4::Context->preference("OpacNav"),
284             opacheader             => "" . C4::Context->preference("opacheader"),
285             opaccredits            => "" . C4::Context->preference("opaccredits"),
286             opacsmallimage         => "" . C4::Context->preference("opacsmallimage"),
287             opaclargeimage         => "" . C4::Context->preference("opaclargeimage"),
288             opaclayoutstylesheet   => "". C4::Context->preference("opaclayoutstylesheet"),
289             opaccolorstylesheet    => "". C4::Context->preference("opaccolorstylesheet"),
290             opaclanguagesdisplay   => "". C4::Context->preference("opaclanguagesdisplay"),
291             opacuserlogin          => "" . C4::Context->preference("opacuserlogin"),
292             opacbookbag            => "" . C4::Context->preference("opacbookbag"),
293             TemplateEncoding       => "". C4::Context->preference("TemplateEncoding"),
294             AmazonContent          => "" . C4::Context->preference("AmazonContent"),
295             LibraryName            => "" . C4::Context->preference("LibraryName"),
296             LibraryNameTitle       => "" . $LibraryNameTitle,
297             LoginBranchcode        => (C4::Context->userenv?C4::Context->userenv->{"branch"}:"insecure"),
298             LoginBranchname        => C4::Context->userenv?C4::Context->userenv->{"branchname"}:"", 
299             LoginFirstname        => (C4::Context->userenv?C4::Context->userenv->{"firstname"}:"Bel"),
300             LoginSurname        => C4::Context->userenv?C4::Context->userenv->{"surname"}:"Inconnu", 
301             OpacPasswordChange     => C4::Context->preference("OpacPasswordChange"),
302             opacreadinghistory     => C4::Context->preference("opacreadinghistory"),
303             opacuserjs             => C4::Context->preference("opacuserjs"),
304             OpacCloud              => C4::Context->preference("OpacCloud"),
305             OpacTopissue           => C4::Context->preference("OpacTopissue"),
306             OpacAuthorities        => C4::Context->preference("OpacAuthorities"),
307             OpacBrowser            => C4::Context->preference("OpacBrowser"),
308             RequestOnOpac          => C4::Context->preference("RequestOnOpac"),
309             reviewson              => C4::Context->preference("reviewson"),
310             hide_marc              => C4::Context->preference("hide_marc"),
311             patronimages           => C4::Context->preference("patronimages"),
312             mylibraryfirst   => C4::Context->preference("SearchMyLibraryFirst"),
313             "BiblioDefaultView".C4::Context->preference("BiblioDefaultView") => 1,
314             OPACFRBRizeEditions => C4::Context->preference("OPACFRBRizeEditions"),
315             'item-level_itypes' => C4::Context->preference('item-level_itypes'),
316         );
317     }
318     return ( $template, $borrowernumber, $cookie, $flags);
319 }
320
321 =item checkauth
322
323   ($userid, $cookie, $sessionID) = &checkauth($query, $noauth, $flagsrequired, $type);
324
325 Verifies that the user is authorized to run this script.  If
326 the user is authorized, a (userid, cookie, session-id, flags)
327 quadruple is returned.  If the user is not authorized but does
328 not have the required privilege (see $flagsrequired below), it
329 displays an error page and exits.  Otherwise, it displays the
330 login page and exits.
331
332 Note that C<&checkauth> will return if and only if the user
333 is authorized, so it should be called early on, before any
334 unfinished operations (e.g., if you've opened a file, then
335 C<&checkauth> won't close it for you).
336
337 C<$query> is the CGI object for the script calling C<&checkauth>.
338
339 The C<$noauth> argument is optional. If it is set, then no
340 authorization is required for the script.
341
342 C<&checkauth> fetches user and session information from C<$query> and
343 ensures that the user is authorized to run scripts that require
344 authorization.
345
346 The C<$flagsrequired> argument specifies the required privileges
347 the user must have if the username and password are correct.
348 It should be specified as a reference-to-hash; keys in the hash
349 should be the "flags" for the user, as specified in the Members
350 intranet module. Any key specified must correspond to a "flag"
351 in the userflags table. E.g., { circulate => 1 } would specify
352 that the user must have the "circulate" privilege in order to
353 proceed. To make sure that access control is correct, the
354 C<$flagsrequired> parameter must be specified correctly.
355
356 The C<$type> argument specifies whether the template should be
357 retrieved from the opac or intranet directory tree.  "opac" is
358 assumed if it is not specified; however, if C<$type> is specified,
359 "intranet" is assumed if it is not "opac".
360
361 If C<$query> does not have a valid session ID associated with it
362 (i.e., the user has not logged in) or if the session has expired,
363 C<&checkauth> presents the user with a login page (from the point of
364 view of the original script, C<&checkauth> does not return). Once the
365 user has authenticated, C<&checkauth> restarts the original script
366 (this time, C<&checkauth> returns).
367
368 The login page is provided using a HTML::Template, which is set in the
369 systempreferences table or at the top of this file. The variable C<$type>
370 selects which template to use, either the opac or the intranet 
371 authentification template.
372
373 C<&checkauth> returns a user ID, a cookie, and a session ID. The
374 cookie should be sent back to the browser; it verifies that the user
375 has authenticated.
376
377 =cut
378
379 sub _version_check ($$) {
380     my $type = shift;
381     my $query = shift;
382     my $version;
383     # If Version syspref is unavailable, it means Koha is beeing installed,
384     # and so we must redirect to OPAC maintenance page or to the WebInstaller
385     #warn "about to check version";
386     unless ($version = C4::Context->preference('Version')) {    # assignment, not comparison
387       if ($type ne 'opac') {
388         warn "Install required, redirecting to Installer";
389         print $query->redirect("/cgi-bin/koha/installer/install.pl");
390       } 
391       else {
392         warn "OPAC Install required, redirecting to maintenance";
393         print $query->redirect("/cgi-bin/koha/maintenance.pl");
394       }
395       exit;
396     }
397
398     # check that database and koha version are the same
399     # there is no DB version, it's a fresh install,
400     # go to web installer
401     # there is a DB version, compare it to the code version
402     my $kohaversion=C4::Context::KOHAVERSION;
403     # remove the 3 last . to have a Perl number
404     $kohaversion =~ s/(.*\..*)\.(.*)\.(.*)/$1$2$3/;
405     $debug and print STDERR "kohaversion : $kohaversion\n";
406     if ($version < $kohaversion){
407         my $warning = "Database update needed, redirecting to %s. Database is $version and Koha is "
408             . C4::Context->config("kohaversion");
409         if ($type ne 'opac'){
410             warn sprintf($warning, 'Installer');
411             print $query->redirect("/cgi-bin/koha/installer/install.pl?step=3");
412         } else {
413             warn sprintf("OPAC: " . $warning, 'maintenance');
414             print $query->redirect("/cgi-bin/koha/maintenance.pl");
415         }       
416         exit;
417     }
418 }
419
420 sub _session_log {
421     (@_) or return 0;
422     open L, ">>/tmp/sessionlog";
423     printf L join("\n",@_);
424     close L;
425 }
426
427 sub checkauth {
428     my $query = shift;
429   # warn "Checking Auth";
430     # $authnotrequired will be set for scripts which will run without authentication
431     my $authnotrequired = shift;
432     my $flagsrequired   = shift;
433     my $type            = shift;
434     $type = 'opac' unless $type;
435
436     my $dbh     = C4::Context->dbh;
437     my $timeout = C4::Context->preference('timeout');
438     # days
439     if ($timeout =~ /(\d+)[dD]/) {
440         $timeout = $1 * 86400;
441     };
442     $timeout = 600 unless $timeout;
443
444     _version_check($type,$query);
445     # state variables
446     my $loggedin = 0;
447     my %info;
448     my ( $userid, $cookie, $sessionID, $flags );
449     my $logout = $query->param('logout.x');
450     if ( $userid = $ENV{'REMOTE_USER'} ) {
451         # Using Basic Authentication, no cookies required
452         $cookie = $query->cookie(
453             -name    => 'CGISESSID',
454             -value   => '',
455             -expires => ''
456         );
457         $loggedin = 1;
458     }
459     elsif ( $sessionID = $query->cookie("CGISESSID")) {     # assignment, not comparison (?)
460         my $session = get_session($sessionID);
461         C4::Context->_new_userenv($sessionID);
462         if ($session){
463             C4::Context::set_userenv(
464                 $session->param('number'),       $session->param('id'),
465                 $session->param('cardnumber'),   $session->param('firstname'),
466                 $session->param('surname'),      $session->param('branch'),
467                 $session->param('branchname'),   $session->param('flags'),
468                 $session->param('emailaddress'), $session->param('branchprinter')
469             );
470             $debug and printf STDERR "AUTH_SESSION: (%s)\t%s %s - %s\n", map {$session->param($_)} qw(cardnumber firstname surname branch) ;
471         }
472         my $ip;
473         my $lasttime;
474         if ($session) {
475             $ip = $session->param('ip');
476             $lasttime = $session->param('lasttime');
477             $userid = $session->param('id');