Bug 36207: (RM follow-up) CSRF correction
[koha.git] / C4 / InstallAuth.pm
1 package C4::InstallAuth;
2
3 # Copyright 2000-2002 Katipo Communications
4 #
5 # This file is part of Koha.
6 #
7 # Koha is free software; you can redistribute it and/or modify it
8 # under the terms of the GNU General Public License as published by
9 # the Free Software Foundation; either version 3 of the License, or
10 # (at your option) any later version.
11 #
12 # Koha is distributed in the hope that it will be useful, but
13 # WITHOUT ANY WARRANTY; without even the implied warranty of
14 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 # GNU General Public License for more details.
16 #
17 # You should have received a copy of the GNU General Public License
18 # along with Koha; if not, see <http://www.gnu.org/licenses>.
19
20 use Modern::Perl;
21 use CGI::Session;
22 use File::Spec;
23
24 require Exporter;
25
26 use C4::Context;
27 use C4::Output qw( output_html_with_http_headers );
28 use C4::Templates;
29
30 use Koha::Session;
31
32 our (@ISA, @EXPORT_OK);
33 BEGIN {
34     @ISA    = qw(Exporter);
35     @EXPORT_OK = qw(
36       checkauth
37       get_template_and_user
38     );
39 }
40
41 =head1 NAME
42
43 InstallAuth - Authenticates Koha users for Install process
44
45 =head1 SYNOPSIS
46
47   use CGI qw ( -utf8 );
48   use InstallAuth;
49   use C4::Output;
50
51   my $query = new CGI;
52
53     my ( $template, $borrowernumber, $cookie ) = get_template_and_user(
54         {   template_name   => "opac-main.tt",
55             query           => $query,
56             type            => "opac",
57             authnotrequired => 1,
58             flagsrequired   => { acquisition => '*' },
59         }
60     );
61
62   output_html_with_http_headers $query, $cookie, $template->output;
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 This package is different from C4::Auth in so far as
71 C4::Auth uses many preferences which are supposed NOT to be obtainable when installing the database.
72     
73 As in C4::Auth, Authentication is based on cookies.
74
75 =head1 FUNCTIONS
76
77 =head2 get_template_and_user
78
79     my ( $template, $borrowernumber, $cookie ) = get_template_and_user(
80         {   template_name   => "opac-main.tt",
81             query           => $query,
82             type            => "opac",
83             authnotrequired => 1,
84             flagsrequired   => { acquisition => '*' },
85         }
86     );
87
88 This call passes the C<query>, C<flagsrequired> and C<authnotrequired>
89 to C<&checkauth> (in this module) to perform authentification.
90 See C<&checkauth> for an explanation of these parameters.
91
92 The C<template_name> is then used to find the correct template for
93 the page. The authenticated users details are loaded onto the
94 template in the logged_in_user variable (which is a Koha::Patron object). Also the
95 C<sessionID> is passed to the template. This can be used in templates
96 if cookies are disabled. It needs to be put as and input to every
97 authenticated page.
98
99 More information on the C<gettemplate> sub can be found in the
100 Templates.pm module.
101
102 =cut
103
104 sub get_template_and_user {
105     my $in       = shift;
106     my $query    = $in->{'query'};
107     my $language =_get_template_language($query->cookie('KohaOpacLanguage'));
108     my $path     = C4::Context->config('intrahtdocs'). "/prog/". $language;
109
110     my $tmplbase = $in->{template_name};
111     my $filename = "$path/modules/" . $tmplbase;
112     my $interface = 'intranet';
113     my $template = C4::Templates->new( $interface, $filename, $tmplbase, $query);
114
115     my $request_method = $in->{query}->request_method // q{};
116     unless ( $request_method eq 'POST' && $in->{query}->param('op') eq 'cud-login' ) {
117         $in->{query}->param('login_userid', '');
118         $in->{query}->param('login_password', '')
119     }
120
121     my ( $user, $cookie, $sessionID, $flags ) = checkauth(
122         $in->{'query'},
123         $in->{'authnotrequired'},
124         $in->{'flagsrequired'},
125         $in->{'type'}
126     );
127
128     my $session = Koha::Session->get_session( { sessionID => $sessionID, storage_method => 'file' } );
129
130     my $borrowernumber;
131     if ($user) {
132         $template->param( loggedinusername => $user );
133         $template->param( sessionID        => $sessionID );
134
135         # We are going to use the $flags returned by checkauth
136         # to create the template's parameters that will indicate
137         # which menus the user can access.
138         if ( ( $flags && $flags->{superlibrarian} == 1 ) ) {
139             $template->param( CAN_user_circulate        => 1 );
140             $template->param( CAN_user_catalogue        => 1 );
141             $template->param( CAN_user_parameters       => 1 );
142             $template->param( CAN_user_borrowers        => 1 );
143             $template->param( CAN_user_permission       => 1 );
144             $template->param( CAN_user_reserveforothers => 1 );
145             $template->param( CAN_user_editcatalogue    => 1 );
146             $template->param( CAN_user_updatecharges    => 1 );
147             $template->param( CAN_user_acquisition      => 1 );
148             $template->param( CAN_user_tools            => 1 );
149             $template->param( CAN_user_editauthorities  => 1 );
150             $template->param( CAN_user_serials          => 1 );
151             $template->param( CAN_user_reports          => 1 );
152             $template->param( CAN_user_problem_reports   => 1 );
153             $template->param( CAN_user_recalls          => 1 );
154         }
155
156         my $minPasswordLength = C4::Context->preference('minPasswordLength');
157         $minPasswordLength = 3 if not $minPasswordLength or $minPasswordLength < 3;
158         $template->param(minPasswordLength => $minPasswordLength,);
159     }
160     return ( $template, $borrowernumber, $cookie );
161 }
162
163 sub _get_template_language {
164
165     #verify if opac language exists in staff (bug 5660)
166     #conditions are 1) dir exists and 2) enabled in prefs
167     my ($opaclang) = @_;
168     return 'en' unless $opaclang;
169     $opaclang =~ s/[^a-zA-Z_-]*//g;
170     my $path = C4::Context->config('intrahtdocs') . "/prog/$opaclang";
171     -d $path ? $opaclang : 'en';
172 }
173
174 =head2 checkauth
175
176   ($userid, $cookie, $sessionID) = &checkauth($query, $noauth, $flagsrequired, $type);
177
178 Verifies that the user is authorized to run this script.  If
179 the user is authorized, a (userid, cookie, session-id, flags)
180 quadruple is returned.  If the user is not authorized but does
181 not have the required privilege (see $flagsrequired below), it
182 displays an error page and exits.  Otherwise, it displays the
183 login page and exits.
184
185 Note that C<&checkauth> will return if and only if the user
186 is authorized, so it should be called early on, before any
187 unfinished operations (e.g., if you've opened a file, then
188 C<&checkauth> won't close it for you).
189
190 C<$query> is the CGI object for the script calling C<&checkauth>.
191
192 The C<$noauth> argument is optional. If it is set, then no
193 authorization is required for the script.
194
195 C<&checkauth> fetches user and session information from C<$query> and
196 ensures that the user is authorized to run scripts that require
197 authorization.
198
199 The C<$flagsrequired> argument specifies the required privileges
200 the user must have if the username and password are correct.
201 It should be specified as a reference-to-hash; keys in the hash
202 should be the "flags" for the user, as specified in the Members
203 intranet module. Any key specified must correspond to a "flag"
204 in the userflags table. E.g., { circulate => 1 } would specify
205 that the user must have the "circulate" privilege in order to
206 proceed. To make sure that access control is correct, the
207 C<$flagsrequired> parameter must be specified correctly.
208
209 The C<$type> argument specifies whether the template should be
210 retrieved from the opac or intranet directory tree.  "opac" is
211 assumed if it is not specified; however, if C<$type> is specified,
212 "intranet" is assumed if it is not "opac".
213
214 If C<$query> does not have a valid session ID associated with it
215 (i.e., the user has not logged in) or if the session has expired,
216 C<&checkauth> presents the user with a login page (from the point of
217 view of the original script, C<&checkauth> does not return). Once the
218 user has authenticated, C<&checkauth> restarts the original script
219 (this time, C<&checkauth> returns).
220
221 The login page is provided using a HTML::Template, which is set in the
222 systempreferences table or at the top of this file. The variable C<$type>
223 selects which template to use, either the opac or the intranet 
224 authentification template.
225
226 C<&checkauth> returns a user ID, a cookie, and a session ID. The
227 cookie should be sent back to the browser; it verifies that the user
228 has authenticated.
229
230 =cut
231
232 sub checkauth {
233     my $query = shift;
234
235 # $authnotrequired will be set for scripts which will run without authentication
236     my $authnotrequired = shift;
237     my $flagsrequired   = shift;
238     my $type            = shift;
239     $type = 'intranet' unless $type;
240
241     my $dbh = C4::Context->dbh();
242     my $template_name;
243     $template_name = "installer/auth.tt";
244
245     # state variables
246     my $loggedin = 0;
247     my %info;
248     my ( $userid, $cookie, $flags, $envcookie );
249     my $logout = $query->param('logout.x');
250
251     my $sessionID = $query->cookie("CGISESSID");
252     my $session = Koha::Session->get_session( { sessionID => $sessionID, storage_method => 'file' } );
253
254     if ( $session ) {
255         C4::Context->_new_userenv($sessionID);
256         if ( $session->param('cardnumber') ) {
257             C4::Context->set_userenv(
258                 $session->param('number'),
259                 $session->param('id'),
260                 $session->param('cardnumber'),
261                 $session->param('firstname'),
262                 $session->param('surname'),
263                 $session->param('branch'),
264                 $session->param('branchname'),
265                 $session->param('flags'),
266                 $session->param('emailaddress')
267             );
268             $cookie = $query->cookie(
269                 -name     => 'CGISESSID',
270                 -value    => $session->id,
271                 -HttpOnly => 1,
272                 -secure => ( C4::Context->https_enabled() ? 1 : 0 ),
273                 -sameSite => 'Lax'
274             );
275             $loggedin = 1;
276             $userid   = $session->param('cardnumber');
277         }
278     }
279
280     if ($logout || !$session) {
281         # voluntary logout the user
282         C4::Context->_unset_userenv($sessionID);
283         $session = Koha::Session->get_session( { storage_method => 'file' } );
284     }
285
286     $sessionID = $session->id;
287
288     unless ($userid) {
289         $userid = $query->param('login_userid');
290         my $password = $query->param('login_password');
291         C4::Context->_new_userenv($sessionID);
292         my ( $return, $cardnumber ) = checkpw( $userid, $password );
293         if ($return) {
294             $loggedin = 1;
295             # open L, ">>/tmp/sessionlog";
296             # my $time = localtime( time() );
297             # printf L "%20s from %16s logged in  at %30s.\n", $userid,
298             #  $ENV{'REMOTE_ADDR'}, $time;
299             # close L;
300             $cookie = $query->cookie(
301                 -name     => 'CGISESSID',
302                 -value    => $sessionID,
303                 -HttpOnly => 1,
304                 -secure => ( C4::Context->https_enabled() ? 1 : 0 ),
305                 -sameSite => 'Lax'
306             );
307             if ( $return == 2 ) {
308
309            #Only superlibrarian should have access to this page.
310            #Since if it is a user, it is supposed that there is a borrower table
311            #And thus that data structure is loaded.
312                 my $hash = C4::Context->set_userenv(
313                     0,                           0,
314                     C4::Context->config('user'), C4::Context->config('user'),
315                     C4::Context->config('user'), "",
316                     "NO_LIBRARY_SET",            1,
317                     ""
318                 );
319                 $session->param( 'number',     0 );
320                 $session->param( 'id',         C4::Context->config('user') );
321                 $session->param( 'cardnumber', C4::Context->config('user') );
322                 $session->param( 'firstname',  C4::Context->config('user') );
323                 $session->param( 'surname',    C4::Context->config('user'), );
324                 $session->param( 'branch',     'NO_LIBRARY_SET' );
325                 $session->param( 'branchname', 'NO_LIBRARY_SET' );
326                 $session->param( 'flags',      1 );
327                 $session->param( 'emailaddress',
328                     C4::Context->preference('KohaAdminEmailAddress') );
329                 $session->param( 'ip',       $session->remote_addr() );
330                 $session->param( 'lasttime', time() );
331                 $userid = C4::Context->config('user');
332             }
333         }
334         else {
335             if ($userid) {
336                 $info{'invalid_username_or_password'} = 1;
337                 C4::Context->_unset_userenv($sessionID);
338             }
339         }
340     }
341
342     # finished authentification, now respond
343     if ($loggedin) {
344
345         # successful login
346         unless ($cookie) {
347             $cookie = $query->cookie(
348                 -name    => 'CGISESSID',
349                 -value   => '',
350                 -HttpOnly => 1,
351                 -expires => '',
352                 -secure => ( C4::Context->https_enabled() ? 1 : 0 ),
353                 -sameSite => 'Lax'
354             );
355         }
356         if ($envcookie) {
357             return ( $userid, [ $cookie, $envcookie ], $sessionID, $flags );
358         }
359         else {
360             return ( $userid, $cookie, $sessionID, $flags );
361         }
362     }
363
364     # else we have a problem...
365     # get the inputs from the incoming query
366     my @inputs = ();
367     foreach my $name ( param $query) {
368         (next) if ( $name eq 'userid' || $name eq 'password' );
369         my $value = $query->param($name);
370         push @inputs, { name => $name, value => $value };
371     }
372
373     my $path =
374       C4::Context->config('intrahtdocs') . "/prog/"
375       . ( $query->param('language') ? $query->param('language') : "en" );
376     my $filename = "$path/modules/$template_name";
377     my $interface = 'intranet';
378     my $template = C4::Templates->new( $interface, $filename, '', $query);
379     $template->param(
380         INPUTS => \@inputs,
381
382     );
383     $template->param( login => 1 );
384     $template->param( loginprompt => 1 ) unless $info{'nopermission'};
385
386     if ($info{'invalid_username_or_password'} && $info{'invalid_username_or_password'} == 1) {
387                 $template->param( 'invalid_username_or_password' => $info{'invalid_username_or_password'});
388     }
389
390     unless ( $sessionID ) {
391         $session = Koha::Session->get_session( { storage_method => 'file' } );
392         $sessionID = $session->id;
393     }
394     $template->param(
395         %info,
396         sessionID => $sessionID,
397     );
398     $cookie = $query->cookie(
399         -name    => 'CGISESSID',
400         -value   => $sessionID,
401         -HttpOnly => 1,
402         -expires => '',
403         -secure => ( C4::Context->https_enabled() ? 1 : 0 ),
404         -sameSite => 'Lax'
405     );
406     print $query->header(
407         -type    => 'text/html; charset=utf-8',
408         -cookie  => $cookie
409       ),
410       $template->output;
411     exit;
412 }
413
414 sub checkpw {
415
416     my ( $userid, $password ) = @_;
417
418     if (   $userid
419         && $userid     eq C4::Context->config('user')
420         && "$password" eq C4::Context->config('pass') )
421     {
422
423         # Koha superuser account
424         C4::Context->set_userenv(
425             0, 0,
426             C4::Context->config('user'),
427             C4::Context->config('user'),
428             C4::Context->config('user'),
429             "", "NO_LIBRARY_SET", 1
430         );
431         return 2;
432     }
433     return 0;
434 }
435
436 END { }    # module clean-up code here (global destructor)
437 1;
438 __END__
439
440 =head1 SEE ALSO
441
442 CGI(3)
443
444 C4::Output(3)
445
446 Digest::MD5(3)
447
448 =cut