Another batch of POD cleanups
[koha.git] / C4 / Auth.pm
1 package C4::Auth;
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 under the
8 # terms of the GNU General Public License as published by the Free Software
9 # Foundation; either version 2 of the License, or (at your option) any later
10 # version.
11 #
12 # Koha is distributed in the hope that it will be useful, but WITHOUT ANY
13 # WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
14 # A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
15 #
16 # You should have received a copy of the GNU General Public License along
17 # with Koha; if not, write to the Free Software Foundation, Inc.,
18 # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
19
20 use strict;
21 #use warnings; FIXME - Bug 2505
22 use Digest::MD5 qw(md5_base64);
23 use Storable qw(thaw freeze);
24 use URI::Escape;
25 use CGI::Session;
26
27 require Exporter;
28 use C4::Context;
29 use C4::Output;    # to get the template
30 use C4::Members;
31 use C4::Koha;
32 use C4::Branch; # GetBranches
33 use C4::VirtualShelves;
34 use POSIX qw/strftime/;
35
36 # use utf8;
37 use vars qw($VERSION @ISA @EXPORT @EXPORT_OK %EXPORT_TAGS $debug $ldap $cas $caslogout);
38
39 BEGIN {
40     $VERSION = 3.02;        # set version for version checking
41     $debug = $ENV{DEBUG};
42     @ISA   = qw(Exporter);
43     @EXPORT    = qw(&checkauth &get_template_and_user &haspermission &get_user_subpermissions);
44     @EXPORT_OK = qw(&check_api_auth &get_session &check_cookie_auth &checkpw &get_all_subpermissions &get_user_subpermissions);
45     %EXPORT_TAGS = (EditPermissions => [qw(get_all_subpermissions get_user_subpermissions)]);
46     $ldap = C4::Context->config('useldapserver') || 0;
47     $cas = C4::Context->preference('casAuthentication');
48     $caslogout = C4::Context->preference('casLogout');
49     if ($ldap) {
50         require C4::Auth_with_ldap;             # no import
51         import  C4::Auth_with_ldap qw(checkpw_ldap);
52     }
53     if ($cas) {
54         require C4::Auth_with_cas;             # no import
55         import  C4::Auth_with_cas qw(checkpw_cas login_cas logout_cas login_cas_url);
56     }
57
58 }
59
60 =head1 NAME
61
62 C4::Auth - Authenticates Koha users
63
64 =head1 SYNOPSIS
65
66   use CGI;
67   use C4::Auth;
68   use C4::Output;
69
70   my $query = new CGI;
71
72   my ($template, $borrowernumber, $cookie)
73     = get_template_and_user(
74         {
75             template_name   => "opac-main.tmpl",
76             query           => $query,
77       type            => "opac",
78       authnotrequired => 1,
79       flagsrequired   => {borrow => 1, catalogue => '*', tools => 'import_patrons' },
80   }
81     );
82
83   output_html_with_http_headers $query, $cookie, $template->output;
84
85 =head1 DESCRIPTION
86
87 The main function of this module is to provide
88 authentification. However the get_template_and_user function has
89 been provided so that a users login information is passed along
90 automatically. This gets loaded into the template.
91
92 =head1 FUNCTIONS
93
94 =head2 get_template_and_user
95
96  my ($template, $borrowernumber, $cookie)
97      = get_template_and_user(
98        {
99          template_name   => "opac-main.tmpl",
100          query           => $query,
101          type            => "opac",
102          authnotrequired => 1,
103          flagsrequired   => {borrow => 1, catalogue => '*', tools => 'import_patrons' },
104        }
105      );
106
107 This call passes the C<query>, C<flagsrequired> and C<authnotrequired>
108 to C<&checkauth> (in this module) to perform authentification.
109 See C<&checkauth> for an explanation of these parameters.
110
111 The C<template_name> is then used to find the correct template for
112 the page. The authenticated users details are loaded onto the
113 template in the HTML::Template LOOP variable C<USER_INFO>. Also the
114 C<sessionID> is passed to the template. This can be used in templates
115 if cookies are disabled. It needs to be put as and input to every
116 authenticated page.
117
118 More information on the C<gettemplate> sub can be found in the
119 Output.pm module.
120
121 =cut
122
123 my $SEARCH_HISTORY_INSERT_SQL =<<EOQ;
124 INSERT INTO search_history(userid, sessionid, query_desc, query_cgi, total, time            )
125 VALUES                    (     ?,         ?,          ?,         ?,     ?, FROM_UNIXTIME(?))
126 EOQ
127 sub get_template_and_user {
128     my $in       = shift;
129     my $template =
130       gettemplate( $in->{'template_name'}, $in->{'type'}, $in->{'query'} );
131     my ( $user, $cookie, $sessionID, $flags ) = checkauth(
132         $in->{'query'},
133         $in->{'authnotrequired'},
134         $in->{'flagsrequired'},
135         $in->{'type'}
136     ) unless ($in->{'template_name'}=~/maintenance/);
137
138     my $borrowernumber;
139     my $insecure = C4::Context->preference('insecure');
140     if ($user or $insecure) {
141
142         # load the template variables for stylesheets and JavaScript
143         $template->param( css_libs => $in->{'css_libs'} );
144         $template->param( css_module => $in->{'css_module'} );
145         $template->param( css_page => $in->{'css_page'} );
146         $template->param( css_widgets => $in->{'css_widgets'} );
147
148         $template->param( js_libs => $in->{'js_libs'} );
149         $template->param( js_module => $in->{'js_module'} );
150         $template->param( js_page => $in->{'js_page'} );
151         $template->param( js_widgets => $in->{'js_widgets'} );
152
153         # user info
154         $template->param( loggedinusername => $user );
155         $template->param( sessionID        => $sessionID );
156
157                 my ($total, $pubshelves, $barshelves) = C4::Context->get_shelves_userenv();
158                 if (defined($pubshelves)) {
159                 $template->param(       pubshelves      => scalar (@$pubshelves),
160                                                         pubshelvesloop  => $pubshelves,
161                                                         );
162                         $template->param(       pubtotal                => $total->{'pubtotal'}, ) if ($total->{'pubtotal'} > scalar (@$pubshelves));
163                 }
164                 if (defined($barshelves)) {
165                 $template->param(       barshelves      => scalar (@$barshelves),
166                                                         barshelvesloop  => $barshelves,
167                                                         );
168                         $template->param(       bartotal                => $total->{'bartotal'}, ) if ($total->{'bartotal'} > scalar (@$barshelves));
169                 }
170
171         $borrowernumber = getborrowernumber($user) if defined($user);
172
173         my ( $borr ) = GetMemberDetails( $borrowernumber );
174         my @bordat;
175         $bordat[0] = $borr;
176         $template->param( "USER_INFO" => \@bordat );
177
178         my $all_perms = get_all_subpermissions();
179
180         my @flagroots = qw(circulate catalogue parameters borrowers permissions reserveforothers borrow
181                             editcatalogue updatecharges management tools editauthorities serials reports acquisition);
182         # We are going to use the $flags returned by checkauth
183         # to create the template's parameters that will indicate
184         # which menus the user can access.
185         if (( $flags && $flags->{superlibrarian}==1) or $insecure==1) {
186             $template->param( CAN_user_circulate        => 1 );
187             $template->param( CAN_user_catalogue        => 1 );
188             $template->param( CAN_user_parameters       => 1 );
189             $template->param( CAN_user_borrowers        => 1 );
190             $template->param( CAN_user_permissions      => 1 );
191             $template->param( CAN_user_reserveforothers => 1 );
192             $template->param( CAN_user_borrow           => 1 );
193             $template->param( CAN_user_editcatalogue    => 1 );
194             $template->param( CAN_user_updatecharges     => 1 );
195             $template->param( CAN_user_acquisition      => 1 );
196             $template->param( CAN_user_management       => 1 );
197             $template->param( CAN_user_tools            => 1 );
198             $template->param( CAN_user_editauthorities  => 1 );
199             $template->param( CAN_user_serials          => 1 );
200             $template->param( CAN_user_reports          => 1 );
201             $template->param( CAN_user_staffaccess      => 1 );
202             foreach my $module (keys %$all_perms) {
203                 foreach my $subperm (keys %{ $all_perms->{$module} }) {
204                     $template->param( "CAN_user_${module}_${subperm}" => 1 );
205                 }
206             }
207         }
208
209         if (C4::Context->preference('GranularPermissions')) {
210             if ( $flags ) {
211                 foreach my $module (keys %$all_perms) {
212                     if ( $flags->{$module} == 1) {
213                         foreach my $subperm (keys %{ $all_perms->{$module} }) {
214                             $template->param( "CAN_user_${module}_${subperm}" => 1 );
215                         }
216                     } elsif ( ref($flags->{$module}) ) {
217                         foreach my $subperm (keys %{ $flags->{$module} } ) {
218                             $template->param( "CAN_user_${module}_${subperm}" => 1 );
219                         }
220                     }
221                 }
222             }
223         } else {
224             foreach my $module (keys %$all_perms) {
225                 foreach my $subperm (keys %{ $all_perms->{$module} }) {
226                     $template->param( "CAN_user_${module}_${subperm}" => 1 );
227                 }
228             }
229         }
230
231         if ($flags) {
232             foreach my $module (keys %$flags) {
233                 if ( $flags->{$module} == 1 or ref($flags->{$module}) ) {
234                     $template->param( "CAN_user_$module" => 1 );
235                     if ($module eq "parameters") {
236                         $template->param( CAN_user_management => 1 );
237                     }
238                 }
239             }
240         }
241                 # Logged-in opac search history
242                 # If the requested template is an opac one and opac search history is enabled
243                 if ($in->{'type'} == "opac" && C4::Context->preference('EnableOpacSearchHistory')) {
244                         my $dbh = C4::Context->dbh;
245                         my $query = "SELECT COUNT(*) FROM search_history WHERE userid=?";
246                         my $sth = $dbh->prepare($query);
247                         $sth->execute($borrowernumber);
248                         
249                         # If at least one search has already been performed
250                         if ($sth->fetchrow_array > 0) { 
251                         # We show the link in opac
252                         $template->param(ShowOpacRecentSearchLink => 1);
253                         }
254
255                         # And if there's a cookie with searches performed when the user was not logged in, 
256                         # we add them to the logged-in search history
257                         my $searchcookie = $in->{'query'}->cookie('KohaOpacRecentSearches');
258                         if ($searchcookie){
259                                 $searchcookie = uri_unescape($searchcookie);
260                                 my @recentSearches = @{thaw($searchcookie) || []};
261                                 if (@recentSearches) {
262                                         my $sth = $dbh->prepare($SEARCH_HISTORY_INSERT_SQL);
263                                         $sth->execute( $borrowernumber,
264                                                        $in->{'query'}->cookie("CGISESSID"),
265                                                        $_->{'query_desc'},
266                                                        $_->{'query_cgi'},
267                                                        $_->{'total'},
268                                                        $_->{'time'},
269                                         ) foreach @recentSearches;
270
271                                         # And then, delete the cookie's content
272                                         my $newsearchcookie = $in->{'query'}->cookie(
273                                                                                                 -name => 'KohaOpacRecentSearches',
274                                                                                                 -value => freeze([]),
275                                                                                                 -expires => ''
276                                                                                          );
277                                         $cookie = [$cookie, $newsearchcookie];
278                                 }
279                         }
280                 }
281     }
282         else {  # if this is an anonymous session, setup to display public lists...
283
284         # load the template variables for stylesheets and JavaScript
285         $template->param( css_libs => $in->{'css_libs'} );
286         $template->param( css_module => $in->{'css_module'} );
287         $template->param( css_page => $in->{'css_page'} );
288         $template->param( css_widgets => $in->{'css_widgets'} );
289
290         $template->param( js_libs => $in->{'js_libs'} );
291         $template->param( js_module => $in->{'js_module'} );
292         $template->param( js_page => $in->{'js_page'} );
293         $template->param( js_widgets => $in->{'js_widgets'} );
294
295         $template->param( sessionID        => $sessionID );
296         
297         my ($total, $pubshelves) = C4::Context->get_shelves_userenv();  # an anonymous user has no 'barshelves'...
298         if (defined(($pubshelves))) {
299             $template->param(   pubshelves      => scalar (@$pubshelves),
300                                 pubshelvesloop  => $pubshelves,
301                             );
302             $template->param(   pubtotal        => $total->{'pubtotal'}, ) if ($total->{'pubtotal'} > scalar (@$pubshelves));
303         }
304
305     }
306         # Anonymous opac search history
307         # If opac search history is enabled and at least one search has already been performed
308         if (C4::Context->preference('EnableOpacSearchHistory')) {
309                 my $searchcookie = $in->{'query'}->cookie('KohaOpacRecentSearches');
310                 if ($searchcookie){
311                         $searchcookie = uri_unescape($searchcookie);
312                         my @recentSearches = @{thaw($searchcookie) || []};
313             # We show the link in opac
314                         if (@recentSearches) {
315                                 $template->param(ShowOpacRecentSearchLink => 1);
316                         }
317             }
318         }
319
320     if(C4::Context->preference('dateformat')){
321         if(C4::Context->preference('dateformat') eq "metric"){
322             $template->param(dateformat_metric => 1);
323         } elsif(C4::Context->preference('dateformat') eq "us"){
324             $template->param(dateformat_us => 1);
325         } else {
326             $template->param(dateformat_iso => 1);
327         }
328     } else {
329         $template->param(dateformat_iso => 1);
330     }
331
332     # these template parameters are set the same regardless of $in->{'type'}
333     $template->param(
334             "BiblioDefaultView".C4::Context->preference("BiblioDefaultView")         => 1,
335             EnhancedMessagingPreferences => C4::Context->preference('EnhancedMessagingPreferences'),
336             GoogleJackets                => C4::Context->preference("GoogleJackets"),
337             KohaAdminEmailAddress        => "" . C4::Context->preference("KohaAdminEmailAddress"),
338             LoginBranchcode              => (C4::Context->userenv?C4::Context->userenv->{"branch"}:"insecure"),
339             LoginFirstname               => (C4::Context->userenv?C4::Context->userenv->{"firstname"}:"Bel"),
340             LoginSurname                 => C4::Context->userenv?C4::Context->userenv->{"surname"}:"Inconnu",
341             TagsEnabled                  => C4::Context->preference("TagsEnabled"),
342             hide_marc                    => C4::Context->preference("hide_marc"),
343             'item-level_itypes'          => C4::Context->preference('item-level_itypes'),
344             patronimages                 => C4::Context->preference("patronimages"),
345             singleBranchMode             => C4::Context->preference("singleBranchMode"),
346             XSLTDetailsDisplay           => C4::Context->preference("XSLTDetailsDisplay"),
347             XSLTResultsDisplay           => C4::Context->preference("XSLTResultsDisplay"),
348             BranchesLoop                 => GetBranchesLoop(),
349             using_https                  => $in->{'query'}->https() ? 1 : 0,
350     );
351
352     if ( $in->{'type'} eq "intranet" ) {
353         $template->param(
354             AmazonContent               => C4::Context->preference("AmazonContent"),
355             AmazonCoverImages           => C4::Context->preference("AmazonCoverImages"),
356             AmazonEnabled               => C4::Context->preference("AmazonEnabled"),
357             AmazonSimilarItems          => C4::Context->preference("AmazonSimilarItems"),
358             AutoLocation                => C4::Context->preference("AutoLocation"),
359             "BiblioDefaultView".C4::Context->preference("IntranetBiblioDefaultView") => 1,
360             CircAutocompl               => C4::Context->preference("CircAutocompl"),
361             FRBRizeEditions             => C4::Context->preference("FRBRizeEditions"),
362             IndependantBranches         => C4::Context->preference("IndependantBranches"),
363             IntranetNav                 => C4::Context->preference("IntranetNav"),
364             IntranetmainUserblock       => C4::Context->preference("IntranetmainUserblock"),
365             LibraryName                 => C4::Context->preference("LibraryName"),
366             LoginBranchname             => (C4::Context->userenv?C4::Context->userenv->{"branchname"}:"insecure"),
367             TemplateEncoding            => C4::Context->preference("TemplateEncoding"),
368             advancedMARCEditor          => C4::Context->preference("advancedMARCEditor"),
369             canreservefromotherbranches => C4::Context->preference('canreservefromotherbranches'),
370             intranetcolorstylesheet     => C4::Context->preference("intranetcolorstylesheet"),
371             intranetreadinghistory      => C4::Context->preference("intranetreadinghistory"),
372             intranetstylesheet          => C4::Context->preference("intranetstylesheet"),
373             intranetuserjs              => C4::Context->preference("intranetuserjs"),
374             intranetbookbag             => C4::Context->preference("intranetbookbag"),
375             noItemTypeImages            => C4::Context->preference("noItemTypeImages"),
376             suggestion                  => C4::Context->preference("suggestion"),
377             virtualshelves              => C4::Context->preference("virtualshelves"),
378             StaffSerialIssueDisplayCount => C4::Context->preference("StaffSerialIssueDisplayCount"),
379             NoZebra                     => C4::Context->preference('NoZebra'),
380         );
381     }
382     else {
383         warn "template type should be OPAC, here it is=[" . $in->{'type'} . "]" unless ( $in->{'type'} eq 'opac' );
384         #TODO : replace LibraryName syspref with 'system name', and remove this html processing
385         my $LibraryNameTitle = C4::Context->preference("LibraryName");
386         $LibraryNameTitle =~ s/<(?:\/?)(?:br|p)\s*(?:\/?)>/ /sgi;
387         $LibraryNameTitle =~ s/<(?:[^<>'"]|'(?:[^']*)'|"(?:[^"]*)")*>//sg;
388         # variables passed from CGI: opac_css_override and opac_search_limits.
389         my $opac_search_limit = $ENV{'OPAC_SEARCH_LIMIT'};
390         my $opac_limit_override = $ENV{'OPAC_LIMIT_OVERRIDE'};
391         my $mylibraryfirst = C4::Context->preference("SearchMyLibraryFirst");
392         my $opac_name;
393         if($opac_limit_override && ($opac_search_limit =~ /branch:(\w+)/) ){
394              $opac_name = C4::Branch::GetBranchName($1)   # opac_search_limit is a branch, so we use it.
395         } elsif($mylibraryfirst){
396             $opac_name = C4::Branch::GetBranchName($mylibraryfirst);
397         }
398         $template->param(
399             AmazonContent             => "" . C4::Context->preference("AmazonContent"),
400             AnonSuggestions           => "" . C4::Context->preference("AnonSuggestions"),
401             AuthorisedValueImages     => C4::Context->preference("AuthorisedValueImages"),
402             LibraryName               => "" . C4::Context->preference("LibraryName"),
403             LibraryNameTitle          => "" . $LibraryNameTitle,
404             LoginBranchname           => C4::Context->userenv?C4::Context->userenv->{"branchname"}:"",
405             OPACAmazonEnabled         => C4::Context->preference("OPACAmazonEnabled"),
406             OPACAmazonSimilarItems    => C4::Context->preference("OPACAmazonSimilarItems"),
407             OPACAmazonCoverImages     => C4::Context->preference("OPACAmazonCoverImages"),
408             OPACAmazonReviews         => C4::Context->preference("OPACAmazonReviews"),
409             OPACFRBRizeEditions       => C4::Context->preference("OPACFRBRizeEditions"),
410             OpacHighlightedWords       => C4::Context->preference("OpacHighlightedWords"),
411             OPACItemHolds             => C4::Context->preference("OPACItemHolds"),
412             OPACShelfBrowser          => "". C4::Context->preference("OPACShelfBrowser"),
413             OPACURLOpenInNewWindow    => "" . C4::Context->preference("OPACURLOpenInNewWindow"),
414             OPACUserCSS               => "". C4::Context->preference("OPACUserCSS"),
415             OPACViewOthersSuggestions => "" . C4::Context->preference("OPACViewOthersSuggestions"),
416             OpacAuthorities           => C4::Context->preference("OpacAuthorities"),
417             OPACBaseURL               => ($in->{'query'}->https() ? "https://" : "http://") . $ENV{'SERVER_NAME'} .
418                    ($ENV{'SERVER_PORT'} eq ($in->{'query'}->https() ? "443" : "80") ? '' : ":$ENV{'SERVER_PORT'}"),
419             opac_name             => $opac_name,
420             opac_css_override           => $ENV{'OPAC_CSS_OVERRIDE'},
421             opac_search_limit         => $opac_search_limit,
422             opac_limit_override       => $opac_limit_override,
423             OpacBrowser               => C4::Context->preference("OpacBrowser"),
424             OpacCloud                 => C4::Context->preference("OpacCloud"),
425             OpacMainUserBlock         => "" . C4::Context->preference("OpacMainUserBlock"),
426             OpacNav                   => "" . C4::Context->preference("OpacNav"),
427             OpacPasswordChange        => C4::Context->preference("OpacPasswordChange"),
428             OPACPatronDetails        => C4::Context->preference("OPACPatronDetails"),
429             OPACFinesTab              => C4::Context->preference("OPACFinesTab"),
430             OpacTopissue              => C4::Context->preference("OpacTopissue"),
431             RequestOnOpac             => C4::Context->preference("RequestOnOpac"),
432             TemplateEncoding          => "". C4::Context->preference("TemplateEncoding"),
433             'Version'                 => C4::Context->preference('Version'),
434             hidelostitems             => C4::Context->preference("hidelostitems"),
435             mylibraryfirst            => (C4::Context->preference("SearchMyLibraryFirst") && C4::Context->userenv) ? C4::Context->userenv->{'branch'} : '',
436             opaclayoutstylesheet      => "" . C4::Context->preference("opaclayoutstylesheet"),
437             opaccolorstylesheet       => "" . C4::Context->preference("opaccolorstylesheet"),
438             opacstylesheet            => "" . C4::Context->preference("opacstylesheet"),
439             opacbookbag               => "" . C4::Context->preference("opacbookbag"),
440             opaccredits               => "" . C4::Context->preference("opaccredits"),
441             opacheader                => "" . C4::Context->preference("opacheader"),
442             opaclanguagesdisplay      => "" . C4::Context->preference("opaclanguagesdisplay"),
443             opacreadinghistory        => C4::Context->preference("opacreadinghistory"),
444             opacsmallimage            => "" . C4::Context->preference("opacsmallimage"),
445             opacuserjs                => C4::Context->preference("opacuserjs"),
446             opacuserlogin             => "" . C4::Context->preference("opacuserlogin"),
447             reviewson                 => C4::Context->preference("reviewson"),
448             suggestion                => "" . C4::Context->preference("suggestion"),
449             virtualshelves            => "" . C4::Context->preference("virtualshelves"),
450             OPACSerialIssueDisplayCount => C4::Context->preference("OPACSerialIssueDisplayCount"),
451             OpacAddMastheadLibraryPulldown => C4::Context->preference("OpacAddMastheadLibraryPulldown"),
452             OPACXSLTDetailsDisplay           => C4::Context->preference("OPACXSLTDetailsDisplay"),
453             OPACXSLTResultsDisplay           => C4::Context->preference("OPACXSLTResultsDisplay"),
454             SyndeticsClientCode          => C4::Context->preference("SyndeticsClientCode"),
455             SyndeticsEnabled             => C4::Context->preference("SyndeticsEnabled"),
456             SyndeticsCoverImages         => C4::Context->preference("SyndeticsCoverImages"),
457             SyndeticsTOC                 => C4::Context->preference("SyndeticsTOC"),
458             SyndeticsSummary             => C4::Context->preference("SyndeticsSummary"),
459             SyndeticsEditions            => C4::Context->preference("SyndeticsEditions"),
460             SyndeticsExcerpt             => C4::Context->preference("SyndeticsExcerpt"),
461             SyndeticsReviews             => C4::Context->preference("SyndeticsReviews"),
462             SyndeticsAuthorNotes         => C4::Context->preference("SyndeticsAuthorNotes"),
463             SyndeticsAwards              => C4::Context->preference("SyndeticsAwards"),
464             SyndeticsSeries              => C4::Context->preference("SyndeticsSeries"),
465             SyndeticsCoverImageSize      => C4::Context->preference("SyndeticsCoverImageSize"),
466         );
467     }
468         $template->param(listloop=>[{shelfname=>"Freelist", shelfnumber=>110}]);
469     return ( $template, $borrowernumber, $cookie, $flags);
470 }
471
472 =head2 checkauth
473
474   ($userid, $cookie, $sessionID) = &checkauth($query, $noauth, $flagsrequired, $type);
475
476 Verifies that the user is authorized to run this script.  If
477 the user is authorized, a (userid, cookie, session-id, flags)
478 quadruple is returned.  If the user is not authorized but does
479 not have the required privilege (see $flagsrequired below), it
480 displays an error page and exits.  Otherwise, it displays the
481 login page and exits.
482
483 Note that C<&checkauth> will return if and only if the user
484 is authorized, so it should be called early on, before any
485 unfinished operations (e.g., if you've opened a file, then
486 C<&checkauth> won't close it for you).
487
488 C<$query> is the CGI object for the script calling C<&checkauth>.
489
490 The C<$noauth> argument is optional. If it is set, then no
491 authorization is required for the script.
492
493 C<&checkauth> fetches user and session information from C<$query> and
494 ensures that the user is authorized to run scripts that require
495 authorization.
496
497 The C<$flagsrequired> argument specifies the required privileges
498 the user must have if the username and password are correct.
499 It should be specified as a reference-to-hash; keys in the hash
500 should be the "flags" for the user, as specified in the Members
501 intranet module. Any key specified must correspond to a "flag"
502 in the userflags table. E.g., { circulate => 1 } would specify
503 that the user must have the "circulate" privilege in order to
504 proceed. To make sure that access control is correct, the
505 C<$flagsrequired> parameter must be specified correctly.
506
507 If the GranularPermissions system preference is ON, the
508 value of each key in the C<flagsrequired> hash takes on an additional
509 meaning, e.g.,
510
511  1
512
513 The user must have access to all subfunctions of the module
514 specified by the hash key.
515
516  *
517
518 The user must have access to at least one subfunction of the module
519 specified by the hash key.
520
521  specific permission, e.g., 'export_catalog'
522
523 The user must have access to the specific subfunction list, which
524 must correspond to a row in the permissions table.
525
526 The C<$type> argument specifies whether the template should be
527 retrieved from the opac or intranet directory tree.  "opac" is
528 assumed if it is not specified; however, if C<$type> is specified,
529 "intranet" is assumed if it is not "opac".
530
531 If C<$query> does not have a valid session ID associated with it
532 (i.e., the user has not logged in) or if the session has expired,
533 C<&checkauth> presents the user with a login page (from the point of
534 view of the original script, C<&checkauth> does not return). Once the
535 user has authenticated, C<&checkauth> restarts the original script
536 (this time, C<&checkauth> returns).
537
538 The login page is provided using a HTML::Template, which is set in the
539 systempreferences table or at the top of this file. The variable C<$type>
540 selects which template to use, either the opac or the intranet
541 authentification template.
542
543 C<&checkauth> returns a user ID, a cookie, and a session ID. The
544 cookie should be sent back to the browser; it verifies that the user
545 has authenticated.
546
547 =cut
548
549 sub _version_check ($$) {
550     my $type = shift;
551     my $query = shift;
552     my $version;
553     # If Version syspref is unavailable, it means Koha is beeing installed,
554     # and so we must redirect to OPAC maintenance page or to the WebInstaller
555         # also, if OpacMaintenance is ON, OPAC should redirect to maintenance
556         if (C4::Context->preference('OpacMaintenance') && $type eq 'opac') {
557                 warn "OPAC Install required, redirecting to maintenance";
558                 print $query->redirect("/cgi-bin/koha/maintenance.pl");
559         }
560     unless ($version = C4::Context->preference('Version')) {    # assignment, not comparison
561       if ($type ne 'opac') {
562         warn "Install required, redirecting to Installer";
563         print $query->redirect("/cgi-bin/koha/installer/install.pl");
564       }
565       else {
566         warn "OPAC Install required, redirecting to maintenance";
567         print $query->redirect("/cgi-bin/koha/maintenance.pl");
568       }
569       exit;
570     }
571
572     # check that database and koha version are the same
573     # there is no DB version, it's a fresh install,
574     # go to web installer
575     # there is a DB version, compare it to the code version
576     my $kohaversion=C4::Context::KOHAVERSION;
577     # remove the 3 last . to have a Perl number
578     $kohaversion =~ s/(.*\..*)\.(.*)\.(.*)/$1$2$3/;
579     $debug and print STDERR "kohaversion : $kohaversion\n";
580     if ($version < $kohaversion){
581         my $warning = "Database update needed, redirecting to %s. Database is $version and Koha is $kohaversion";
582         if ($type ne 'opac'){
583             warn sprintf($warning, 'Installer');
584             print $query->redirect("/cgi-bin/koha/installer/install.pl?step=3");
585         } else {
586             warn sprintf("OPAC: " . $warning, 'maintenance');
587             print $query->redirect("/cgi-bin/koha/maintenance.pl");
588         }
589         exit;
590     }
591 }
592
593 sub _session_log {
594     (@_) or return 0;
595     open L, ">>/tmp/sessionlog" or warn "ERROR: Cannot append to /tmp/sessionlog";
596     printf L join("\n",@_);
597     close L;
598 }
599
600 sub checkauth {
601     my $query = shift;
602         $debug and warn "Checking Auth";
603     # $authnotrequired will be set for scripts which will run without authentication
604     my $authnotrequired = shift;
605     my $flagsrequired   = shift;
606     my $type            = shift;
607     $type = 'opac' unless $type;
608
609     my $dbh     = C4::Context->dbh;
610     my $timeout = C4::Context->preference('timeout');
611     # days
612     if ($timeout =~ /(\d+)[dD]/) {
613         $timeout = $1 * 86400;
614     };
615     $timeout = 600 unless $timeout;
616
617     _version_check($type,$query);
618     # state variables
619     my $loggedin = 0;
620     my %info;
621     my ( $userid, $cookie, $sessionID, $flags, $barshelves, $pubshelves );
622     my $logout = $query->param('logout.x');
623
624     if ( $userid = $ENV{'REMOTE_USER'} ) {
625         # Using Basic Authentication, no cookies required
626         $cookie = $query->cookie(
627             -name    => 'CGISESSID',
628             -value   => '',
629             -expires => ''
630         );
631         $loggedin = 1;
632     }
633     elsif ( $sessionID = $query->cookie("CGISESSID")) {     # assignment, not comparison
634         my $session = get_session($sessionID);
635         C4::Context->_new_userenv($sessionID);
636         my ($ip, $lasttime, $sessiontype);
637         if ($session){
638             C4::Context::set_userenv(
639                 $session->param('number'),       $session->param('id'),
640                 $session->param('cardnumber'),   $session->param('firstname'),
641                 $session->param('surname'),      $session->param('branch'),
642                 $session->param('branchname'),   $session->param('flags'),
643                 $session->param('emailaddress'), $session->param('branchprinter')
644             );
645             C4::Context::set_shelves_userenv('bar',$session->param('barshelves'));
646             C4::Context::set_shelves_userenv('pub',$session->param('pubshelves'));
647             C4::Context::set_shelves_userenv('tot',$session->param('totshelves'));
648             $debug and printf STDERR "AUTH_SESSION: (%s)\t%s %s - %s\n", map {$session->param($_)} qw(cardnumber firstname surname branch) ;
649             $ip       = $session->param('ip');
650             $lasttime = $session->param('lasttime');
651             $userid   = $session->param('id');
652                         $sessiontype = $session->param('sessiontype');
653         }
654         if ( ($query->param('koha_login_context')) && ($query->param('userid') ne $session->param('id')) ) {
655             #if a user enters an id ne to the id in the current session, we need to log them in...
656             #first we need to clear the anonymous session...
657             $debug and warn "query id = " . $query->param('userid') . " but session id = " . $session->param('id');
658             $session->flush;      
659             $session->delete();
660             C4::Context->_unset_userenv($sessionID);
661                         $sessionID = undef;
662                         $userid = undef;
663                 }
664         elsif ($logout) {
665             # voluntary logout the user
666             $session->flush;
667             $session->delete();
668             C4::Context->_unset_userenv($sessionID);
669             _session_log(sprintf "%20s from %16s logged out at %30s (manually).\n", $userid,$ip,(strftime "%c",localtime));
670             $sessionID = undef;
671             $userid    = undef;
672
673             if ($cas and $caslogout) {
674                 logout_cas($query);
675             }
676         }
677         elsif ( $lasttime < time() - $timeout ) {
678             # timed logout
679             $info{'timed_out'} = 1;
680             $session->delete();
681             C4::Context->_unset_userenv($sessionID);
682             _session_log(sprintf "%20s from %16s logged out at %30s (inactivity).\n", $userid,$ip,(strftime "%c",localtime));
683             $userid    = undef;
684             $sessionID = undef;
685         }
686         elsif ( $ip ne $ENV{'REMOTE_ADDR'} ) {
687             # Different ip than originally logged in from
688             $info{'oldip'}        = $ip;
689             $info{'newip'}        = $ENV{'REMOTE_ADDR'};
690             $info{'different_ip'} = 1;
691             $session->delete();
692             C4::Context->_unset_userenv($sessionID);
693             _session_log(sprintf "%20s from %16s logged out at %30s (ip changed to %16s).\n", $userid,$ip,(strftime "%c",localtime), $info{'newip'});
694             $sessionID = undef;
695             $userid    = undef;
696         }
697         else {
698             $cookie = $query->cookie( CGISESSID => $session->id );
699             $session->param('lasttime',time());
700             unless ( $sessiontype eq 'anon' ) { #if this is an anonymous session, we want to update the session, but not behave as if they are logged in...
701                 $flags = haspermission($userid, $flagsrequired);
702                 if ($flags) {
703                     $loggedin = 1;
704                 } else {
705                     $info{'nopermission'} = 1;
706                 }
707             }
708         }
709     }
710     unless ($userid || $sessionID) {
711         #we initiate a session prior to checking for a username to allow for anonymous sessions...
712                 my $session = get_session("") or die "Auth ERROR: Cannot get_session()";
713         my $sessionID = $session->id;
714         C4::Context->_new_userenv($sessionID);
715         $cookie = $query->cookie(CGISESSID => $sessionID);
716             $userid    = $query->param('userid');
717             if ($cas || $userid) {
718                 my $password = $query->param('password');
719                 my ($return, $cardnumber);
720                 if ($cas && $query->param('ticket')) {
721                     my $retuserid;
722                     ( $return, $cardnumber, $retuserid ) = checkpw( $dbh, $userid, $password, $query );
723                     $userid = $retuserid;
724                     $info{'invalidCasLogin'} = 1 unless ($return);
725                 } else {
726                     ( $return, $cardnumber ) = checkpw( $dbh, $userid, $password, $query );
727                 }
728                 if ($return) {
729                 _session_log(sprintf "%20s from %16s logged in  at %30s.\n", $userid,$ENV{'REMOTE_ADDR'},localtime);
730                 if ( $flags = haspermission(  $userid, $flagsrequired ) ) {
731                                         $loggedin = 1;
732                 }
733                         else {
734                         $info{'nopermission'} = 1;
735                         C4::Context->_unset_userenv($sessionID);
736                 }
737
738                                 my ($borrowernumber, $firstname, $surname, $userflags,
739                                         $branchcode, $branchname, $branchprinter, $emailaddress);
740
741                 if ( $return == 1 ) {
742                         my $select = "
743                         SELECT borrowernumber, firstname, surname, flags, borrowers.branchcode, 
744                             branches.branchname    as branchname, 
745                                 branches.branchprinter as branchprinter, 
746                                 email 
747                         FROM borrowers 
748                         LEFT JOIN branches on borrowers.branchcode=branches.branchcode
749                         ";
750                         my $sth = $dbh->prepare("$select where userid=?");
751                         $sth->execute($userid);
752                                         unless ($sth->rows) {
753                                 $debug and print STDERR "AUTH_1: no rows for userid='$userid'\n";
754                                                 $sth = $dbh->prepare("$select where cardnumber=?");
755                                 $sth->execute($cardnumber);
756                                                 unless ($sth->rows) {
757                                         $debug and print STDERR "AUTH_2a: no rows for cardnumber='$cardnumber'\n";
758                                 $sth->execute($userid);
759                                                         unless ($sth->rows) {
760                                                 $debug and print STDERR "AUTH_2b: no rows for userid='$userid' AS cardnumber\n";
761                                                         }
762                                                 }
763                                         }
764                         if ($sth->rows) {
765                         ($borrowernumber, $firstname, $surname, $userflags,
766                                 $branchcode, $branchname, $branchprinter, $emailaddress) = $sth->fetchrow;
767                                                 $debug and print STDERR "AUTH_3 results: " .
768                                                         "$cardnumber,$borrowernumber,$userid,$firstname,$surname,$userflags,$branchcode,$emailaddress\n";
769                                         } else {
770                                                 print STDERR "AUTH_3: no results for userid='$userid', cardnumber='$cardnumber'.\n";
771                                         }
772
773 # launch a sequence to check if we have a ip for the branch, i
774 # if we have one we replace the branchcode of the userenv by the branch bound in the ip.
775
776                                         my $ip       = $ENV{'REMOTE_ADDR'};
777                                         # if they specify at login, use that
778                                         if ($query->param('branch')) {
779                                                 $branchcode  = $query->param('branch');
780                                                 $branchname = GetBranchName($branchcode);
781                                         }
782                                         my $branches = GetBranches();
783                                         if (C4::Context->boolean_preference('IndependantBranches') && C4::Context->boolean_preference('Autolocation')){
784                                                 # we have to check they are coming from the right ip range
785                                                 my $domain = $branches->{$branchcode}->{'branchip'};
786                                                 if ($ip !~ /^$domain/){
787                                                         $loggedin=0;
788                                                         $info{'wrongip'} = 1;
789                                                 }
790                                         }
791
792                                         my @branchesloop;
793                                         foreach my $br ( keys %$branches ) {
794                                                 #     now we work with the treatment of ip
795                                                 my $domain = $branches->{$br}->{'branchip'};
796                                                 if ( $domain && $ip =~ /^$domain/ ) {
797                                                         $branchcode = $branches->{$br}->{'branchcode'};
798
799                                                         # new op dev : add the branchprinter and branchname in the cookie
800                                                         $branchprinter = $branches->{$br}->{'branchprinter'};
801                                                         $branchname    = $branches->{$br}->{'branchname'};
802                                                 }
803                                         }
804                                         $session->param('number',$borrowernumber);
805                                         $session->param('id',$userid);
806                                         $session->param('cardnumber',$cardnumber);
807                                         $session->param('firstname',$firstname);
808                                         $session->param('surname',$surname);
809                                         $session->param('branch',$branchcode);
810                                         $session->param('branchname',$branchname);
811                                         $session->param('flags',$userflags);
812                                         $session->param('emailaddress',$emailaddress);
813                                         $session->param('ip',$session->remote_addr());
814                                         $session->param('lasttime',time());
815                                         $debug and printf STDERR "AUTH_4: (%s)\t%s %s - %s\n", map {$session->param($_)} qw(cardnumber firstname surname branch) ;
816                                 }
817                                 elsif ( $return == 2 ) {
818                                         #We suppose the user is the superlibrarian
819                                         $borrowernumber = 0;
820                                         $session->param('number',0);
821                                         $session->param('id',C4::Context->config('user'));
822                                         $session->param('cardnumber',C4::Context->config('user'));
823                                         $session->param('firstname',C4::Context->config('user'));
824                                         $session->param('surname',C4::Context->config('user'));
825                                         $session->param('branch','NO_LIBRARY_SET');
826                                         $session->param('branchname','NO_LIBRARY_SET');
827                                         $session->param('flags',1);
828                                         $session->param('emailaddress', C4::Context->preference('KohaAdminEmailAddress'));
829                                         $session->param('ip',$session->remote_addr());
830                                         $session->param('lasttime',time());
831                                 }
832                                 C4::Context::set_userenv(
833                                         $session->param('number'),       $session->param('id'),
834                                         $session->param('cardnumber'),   $session->param('firstname'),
835                                         $session->param('surname'),      $session->param('branch'),
836                                         $session->param('branchname'),   $session->param('flags'),
837                                         $session->param('emailaddress'), $session->param('branchprinter')
838                                 );
839
840                                 # Grab borrower's shelves and public shelves and add them to the session
841                                 # $row_count determines how many records are returned from the db query
842                                 # and the number of lists to be displayed of each type in the 'Lists' button drop down
843                                 my $row_count = 10; # FIXME:This probably should be a syspref
844                                 my ($total, $totshelves, $barshelves, $pubshelves);
845                                 ($barshelves, $totshelves) = C4::VirtualShelves::GetRecentShelves(1, $row_count, $borrowernumber);
846                                 $total->{'bartotal'} = $totshelves;
847                                 ($pubshelves, $totshelves) = C4::VirtualShelves::GetRecentShelves(2, $row_count, undef);
848                                 $total->{'pubtotal'} = $totshelves;
849                                 $session->param('barshelves', $barshelves->[0]);
850                                 $session->param('pubshelves', $pubshelves->[0]);
851                                 $session->param('totshelves', $total);
852
853                                 C4::Context::set_shelves_userenv('bar',$barshelves->[0]);
854                                 C4::Context::set_shelves_userenv('pub',$pubshelves->[0]);
855                                 C4::Context::set_shelves_userenv('tot',$total);
856                         }
857                 else {
858                 if ($userid) {
859                         $info{'invalid_username_or_password'} = 1;
860                         C4::Context->_unset_userenv($sessionID);
861                 }
862                         }
863         }       # END if ( $userid    = $query->param('userid') )
864                 elsif ($type eq "opac") {
865             # if we are here this is an anonymous session; add public lists to it and a few other items...
866             # anonymous sessions are created only for the OPAC
867                         $debug and warn "Initiating an anonymous session...";
868
869                         # Grab the public shelves and add to the session...
870                         my $row_count = 20; # FIXME:This probably should be a syspref
871                         my ($total, $totshelves, $pubshelves);
872                         ($pubshelves, $totshelves) = C4::VirtualShelves::GetRecentShelves(2, $row_count, undef);
873                         $total->{'pubtotal'} = $totshelves;
874                         $session->param('pubshelves', $pubshelves->[0]);
875                         $session->param('totshelves', $total);
876                         C4::Context::set_shelves_userenv('pub',$pubshelves->[0]);
877                         C4::Context::set_shelves_userenv('tot',$total);
878
879                         # setting a couple of other session vars...
880                         $session->param('ip',$session->remote_addr());
881                         $session->param('lasttime',time());
882                         $session->param('sessiontype','anon');
883                 }
884     }   # END unless ($userid)
885     my $insecure = C4::Context->boolean_preference('insecure');
886
887     # finished authentification, now respond
888     if ( $loggedin || $authnotrequired || ( defined($insecure) && $insecure ) )
889     {
890         # successful login
891         unless ($cookie) {
892             $cookie = $query->cookie( CGISESSID => '' );
893         }
894         return ( $userid, $cookie, $sessionID, $flags );
895     }
896
897 #
898 #
899 # AUTH rejected, show the login/password template, after checking the DB.
900 #
901 #
902
903     # get the inputs from the incoming query
904     my @inputs = ();
905     foreach my $name ( param $query) {
906         (next) if ( $name eq 'userid' || $name eq 'password' || $name eq 'ticket' );
907         my $value = $query->param($name);
908         push @inputs, { name => $name, value => $value };
909     }
910     # get the branchloop, which we need for authentication
911     my $branches = GetBranches();
912     my @branch_loop;
913     for my $branch_hash (sort keys %$branches) {
914                 push @branch_loop, {branchcode => "$branch_hash", branchname => $branches->{$branch_hash}->{'branchname'}, };
915     }
916
917     my $template_name = ( $type eq 'opac' ) ? 'opac-auth.tmpl' : 'auth.tmpl';
918     my $template = gettemplate( $template_name, $type, $query );
919     $template->param(branchloop => \@branch_loop,);
920     $template->param(
921     login        => 1,
922         INPUTS               => \@inputs,
923         casAuthentication    => C4::Context->preference("casAuthentication"),
924         suggestion           => C4::Context->preference("suggestion"),
925         virtualshelves       => C4::Context->preference("virtualshelves"),
926         LibraryName          => C4::Context->preference("LibraryName"),
927         opacuserlogin        => C4::Context->preference("opacuserlogin"),
928         OpacNav              => C4::Context->preference("OpacNav"),
929         opaccredits          => C4::Context->preference("opaccredits"),
930         opacreadinghistory   => C4::Context->preference("opacreadinghistory"),
931         opacsmallimage       => C4::Context->preference("opacsmallimage"),
932         opaclayoutstylesheet => C4::Context->preference("opaclayoutstylesheet"),
933         opaccolorstylesheet  => C4::Context->preference("opaccolorstylesheet"),
934         opaclanguagesdisplay => C4::Context->preference("opaclanguagesdisplay"),
935         opacuserjs           => C4::Context->preference("opacuserjs"),
936         opacbookbag          => "" . C4::Context->preference("opacbookbag"),
937         OpacCloud            => C4::Context->preference("OpacCloud"),
938         OpacTopissue         => C4::Context->preference("OpacTopissue"),
939         OpacAuthorities      => C4::Context->preference("OpacAuthorities"),
940         OpacBrowser          => C4::Context->preference("OpacBrowser"),
941         opacheader           => C4::Context->preference("opacheader"),
942         TagsEnabled                  => C4::Context->preference("TagsEnabled"),
943         OPACUserCSS           => C4::Context->preference("OPACUserCSS"),
944         intranetcolorstylesheet =>
945                                                                 C4::Context->preference("intranetcolorstylesheet"),
946         intranetstylesheet => C4::Context->preference("intranetstylesheet"),
947         intranetbookbag    => C4::Context->preference("intranetbookbag"),
948         IntranetNav        => C4::Context->preference("IntranetNav"),
949         intranetuserjs     => C4::Context->preference("intranetuserjs"),
950         TemplateEncoding   => C4::Context->preference("TemplateEncoding"),
951         IndependantBranches=> C4::Context->preference("IndependantBranches"),
952         AutoLocation       => C4::Context->preference("AutoLocation"),
953                 wrongip            => $info{'wrongip'}
954     );
955     $template->param( loginprompt => 1 ) unless $info{'nopermission'};
956
957     if ($cas) { 
958         $template->param(
959             casServerUrl    => login_cas_url(),
960             invalidCasLogin => $info{'invalidCasLogin'}
961         );
962     }
963
964     my $self_url = $query->url( -absolute => 1 );
965     $template->param(
966         url         => $self_url,
967         LibraryName => C4::Context->preference("LibraryName"),
968     );
969     $template->param( \%info );
970 #    $cookie = $query->cookie(CGISESSID => $session->id
971 #   );
972     print $query->header(
973         -type   => 'text/html',
974         -charset => 'utf-8',
975         -cookie => $cookie
976       ),
977       $template->output;
978     exit;
979 }
980
981 =head2 check_api_auth
982
983   ($status, $cookie, $sessionId) = check_api_auth($query, $userflags);
984
985 Given a CGI query containing the parameters 'userid' and 'password' and/or a session
986 cookie, determine if the user has the privileges specified by C<$userflags>.
987
988 C<check_api_auth> is is meant for authenticating users of web services, and
989 consequently will always return and will not attempt to redirect the user
990 agent.
991
992 If a valid session cookie is already present, check_api_auth will return a status
993 of "ok", the cookie, and the Koha session ID.
994
995 If no session cookie is present, check_api_auth will check the 'userid' and 'password
996 parameters and create a session cookie and Koha session if the supplied credentials
997 are OK.
998
999 Possible return values in C<$status> are:
1000
1001 =over
1002
1003 =item "ok" -- user authenticated; C<$cookie> and C<$sessionid> have valid values.
1004
1005 =item "failed" -- credentials are not correct; C<$cookie> and C<$sessionid> are undef
1006
1007 =item "maintenance" -- DB is in maintenance mode; no login possible at the moment
1008
1009 =item "expired -- session cookie has expired; API user should resubmit userid and password
1010
1011 =back
1012
1013 =cut
1014
1015 sub check_api_auth {
1016     my $query = shift;
1017     my $flagsrequired = shift;
1018
1019     my $dbh     = C4::Context->dbh;
1020     my $timeout = C4::Context->preference('timeout');
1021     $timeout = 600 unless $timeout;
1022
1023     unless (C4::Context->preference('Version')) {
1024         # database has not been installed yet
1025         return ("maintenance", undef, undef);
1026     }
1027     my $kohaversion=C4::Context::KOHAVERSION;
1028     $kohaversion =~ s/(.*\..*)\.(.*)\.(.*)/$1$2$3/;
1029     if (C4::Context->preference('Version') < $kohaversion) {
1030         # database in need of version update; assume that
1031         # no API should be called while databsae is in
1032         # this condition.
1033         return ("maintenance", undef, undef);
1034     }
1035
1036     # FIXME -- most of what follows is a copy-and-paste
1037     # of code from checkauth.  There is an obvious need
1038     # for refactoring to separate the various parts of
1039     # the authentication code, but as of 2007-11-19 this
1040     # is deferred so as to not introduce bugs into the
1041     # regular authentication code for Koha 3.0.
1042
1043     # see if we have a valid session cookie already
1044     # however, if a userid parameter is present (i.e., from
1045     # a form submission, assume that any current cookie
1046     # is to be ignored
1047     my $sessionID = undef;
1048     unless ($query->param('userid')) {
1049         $sessionID = $query->cookie("CGISESSID");
1050     }
1051     if ($sessionID) {
1052         my $session = get_session($sessionID);
1053         C4::Context->_new_userenv($sessionID);
1054         if ($session) {
1055             C4::Context::set_userenv(
1056                 $session->param('number'),       $session->param('id'),
1057                 $session->param('cardnumber'),   $session->param('firstname'),
1058                 $session->param('surname'),      $session->param('branch'),
1059                 $session->param('branchname'),   $session->param('flags'),
1060                 $session->param('emailaddress'), $session->param('branchprinter')
1061             );
1062
1063             my $ip = $session->param('ip');
1064             my $lasttime = $session->param('lasttime');
1065             my $userid = $session->param('id');
1066             if ( $lasttime < time() - $timeout ) {
1067                 # time out
1068                 $session->delete();
1069                 C4::Context->_unset_userenv($sessionID);
1070                 $userid    = undef;
1071                 $sessionID = undef;
1072                 return ("expired", undef, undef);
1073             } elsif ( $ip ne $ENV{'REMOTE_ADDR'} ) {
1074                 # IP address changed
1075                 $session->delete();
1076                 C4::Context->_unset_userenv($sessionID);
1077                 $userid    = undef;
1078                 $sessionID = undef;
1079                 return ("expired", undef, undef);
1080             } else {
1081                 my $cookie = $query->cookie( CGISESSID => $session->id );
1082                 $session->param('lasttime',time());
1083                 my $flags = haspermission($userid, $flagsrequired);
1084                 if ($flags) {
1085                     return ("ok", $cookie, $sessionID);
1086                 } else {
1087                     $session->delete();
1088                     C4::Context->_unset_userenv($sessionID);
1089                     $userid    = undef;
1090                     $sessionID = undef;
1091                     return ("failed", undef, undef);
1092                 }
1093             }
1094         } else {
1095             return ("expired", undef, undef);
1096         }
1097     } else {
1098         # new login
1099         my $userid = $query->param('userid');
1100         my $password = $query->param('password');
1101         unless ($userid and $password) {
1102             # caller did something wrong, fail the authenticateion
1103             return ("failed", undef, undef);
1104         }
1105         my ($return, $cardnumber);
1106         if ($cas && $query->param('ticket')) {
1107             my $retuserid;
1108             ( $return, $cardnumber, $retuserid ) = checkpw( $dbh, $userid, $password, $query );
1109             $userid = $retuserid;
1110         } else {
1111             ( $return, $cardnumber ) = checkpw( $dbh, $userid, $password, $query );
1112         }
1113         if ($return and haspermission(  $userid, $flagsrequired)) {
1114             my $session = get_session("");
1115             return ("failed", undef, undef) unless $session;
1116
1117             my $sessionID = $session->id;
1118             C4::Context->_new_userenv($sessionID);
1119             my $cookie = $query->cookie(CGISESSID => $sessionID);
1120             if ( $return == 1 ) {
1121                 my (
1122                     $borrowernumber, $firstname,  $surname,
1123                     $userflags,      $branchcode, $branchname,
1124                     $branchprinter,  $emailaddress
1125                 );
1126                 my $sth =
1127                   $dbh->prepare(
1128 "select borrowernumber, firstname, surname, flags, borrowers.branchcode, branches.branchname as branchname,branches.branchprinter as branchprinter, email from borrowers left join branches on borrowers.branchcode=branches.branchcode where userid=?"
1129                   );
1130                 $sth->execute($userid);
1131                 (
1132                     $borrowernumber, $firstname,  $surname,
1133                     $userflags,      $branchcode, $branchname,
1134                     $branchprinter,  $emailaddress
1135                 ) = $sth->fetchrow if ( $sth->rows );
1136
1137                 unless ($sth->rows ) {
1138                     my $sth = $dbh->prepare(
1139 "select borrowernumber, firstname, surname, flags, borrowers.branchcode, branches.branchname as branchname, branches.branchprinter as branchprinter, email from borrowers left join branches on borrowers.branchcode=branches.branchcode where cardnumber=?"
1140                       );
1141                     $sth->execute($cardnumber);
1142                     (
1143                         $borrowernumber, $firstname,  $surname,
1144                         $userflags,      $branchcode, $branchname,
1145                         $branchprinter,  $emailaddress
1146                     ) = $sth->fetchrow if ( $sth->rows );
1147
1148                     unless ( $sth->rows ) {
1149                         $sth->execute($userid);
1150                         (
1151                             $borrowernumber, $firstname, $surname, $userflags,
1152                             $branchcode, $branchname, $branchprinter, $emailaddress
1153                         ) = $sth->fetchrow if ( $sth->rows );
1154                     }
1155                 }
1156
1157                 my $ip       = $ENV{'REMOTE_ADDR'};
1158                 # if they specify at login, use that
1159                 if ($query->param('branch')) {
1160                     $branchcode  = $query->param('branch');
1161                     $branchname = GetBranchName($branchcode);
1162                 }
1163                 my $branches = GetBranches();
1164                 my @branchesloop;
1165                 foreach my $br ( keys %$branches ) {
1166                     #     now we work with the treatment of ip
1167                     my $domain = $branches->{$br}->{'branchip'};
1168                     if ( $domain && $ip =~ /^$domain/ ) {
1169                         $branchcode = $branches->{$br}->{'branchcode'};
1170
1171                         # new op dev : add the branchprinter and branchname in the cookie
1172                         $branchprinter = $branches->{$br}->{'branchprinter'};
1173                         $branchname    = $branches->{$br}->{'branchname'};
1174                     }
1175                 }
1176                 $session->param('number',$borrowernumber);
1177                 $session->param('id',$userid);
1178                 $session->param('cardnumber',$cardnumber);
1179                 $session->param('firstname',$firstname);
1180                 $session->param('surname',$surname);
1181                 $session->param('branch',$branchcode);
1182                 $session->param('branchname',$branchname);
1183                 $session->param('flags',$userflags);
1184                 $session->param('emailaddress',$emailaddress);
1185                 $session->param('ip',$session->remote_addr());
1186                 $session->param('lasttime',time());
1187             } elsif ( $return == 2 ) {
1188                 #We suppose the user is the superlibrarian
1189                 $session->param('number',0);
1190                 $session->param('id',C4::Context->config('user'));
1191                 $session->param('cardnumber',C4::Context->config('user'));
1192                 $session->param('firstname',C4::Context->config('user'));
1193                 $session->param('surname',C4::Context->config('user'));
1194                 $session->param('branch','NO_LIBRARY_SET');
1195                 $session->param('branchname','NO_LIBRARY_SET');
1196                 $session->param('flags',1);
1197                 $session->param('emailaddress', C4::Context->preference('KohaAdminEmailAddress'));
1198                 $session->param('ip',$session->remote_addr());
1199                 $session->param('lasttime',time());
1200             }
1201             C4::Context::set_userenv(
1202                 $session->param('number'),       $session->param('id'),
1203                 $session->param('cardnumber'),   $session->param('firstname'),
1204                 $session->param('surname'),      $session->param('branch'),
1205                 $session->param('branchname'),   $session->param('flags'),
1206                 $session->param('emailaddress'), $session->param('branchprinter')
1207             );
1208             return ("ok", $cookie, $sessionID);
1209         } else {
1210             return ("failed", undef, undef);
1211         }
1212     }
1213 }
1214
1215 =head2 check_cookie_auth
1216
1217   ($status, $sessionId) = check_api_auth($cookie, $userflags);
1218
1219 Given a CGISESSID cookie set during a previous login to Koha, determine
1220 if the user has the privileges specified by C<$userflags>.
1221
1222 C<check_cookie_auth> is meant for authenticating special services
1223 such as tools/upload-file.pl that are invoked by other pages that
1224 have been authenticated in the usual way.
1225
1226 Possible return values in C<$status> are:
1227
1228 =over
1229
1230 =item "ok" -- user authenticated; C<$sessionID> have valid values.
1231
1232 =item "failed" -- credentials are not correct; C<$sessionid> are undef
1233
1234 =item "maintenance" -- DB is in maintenance mode; no login possible at the moment
1235
1236 =item "expired -- session cookie has expired; API user should resubmit userid and password
1237
1238 =back
1239
1240 =cut
1241
1242 sub check_cookie_auth {
1243     my $cookie = shift;
1244     my $flagsrequired = shift;
1245
1246     my $dbh     = C4::Context->dbh;
1247     my $timeout = C4::Context->preference('timeout');
1248     $timeout = 600 unless $timeout;
1249
1250     unless (C4::Context->preference('Version')) {
1251         # database has not been installed yet
1252         return ("maintenance", undef);
1253     }
1254     my $kohaversion=C4::Context::KOHAVERSION;
1255     $kohaversion =~ s/(.*\..*)\.(.*)\.(.*)/$1$2$3/;
1256     if (C4::Context->preference('Version') < $kohaversion) {
1257         # database in need of version update; assume that
1258         # no API should be called while databsae is in
1259         # this condition.
1260         return ("maintenance", undef);
1261     }
1262
1263     # FIXME -- most of what follows is a copy-and-paste
1264     # of code from checkauth.  There is an obvious need
1265     # for refactoring to separate the various parts of
1266     # the authentication code, but as of 2007-11-23 this
1267     # is deferred so as to not introduce bugs into the
1268     # regular authentication code for Koha 3.0.
1269
1270     # see if we have a valid session cookie already
1271     # however, if a userid parameter is present (i.e., from
1272     # a form submission, assume that any current cookie
1273     # is to be ignored
1274     unless (defined $cookie and $cookie) {
1275         return ("failed", undef);
1276     }
1277     my $sessionID = $cookie;
1278     my $session = get_session($sessionID);
1279     C4::Context->_new_userenv($sessionID);
1280     if ($session) {
1281         C4::Context::set_userenv(
1282             $session->param('number'),       $session->param('id'),
1283             $session->param('cardnumber'),   $session->param('firstname'),
1284             $session->param('surname'),      $session->param('branch'),
1285             $session->param('branchname'),   $session->param('flags'),
1286             $session->param('emailaddress'), $session->param('branchprinter')
1287         );
1288
1289         my $ip = $session->param('ip');
1290         my $lasttime = $session->param('lasttime');
1291         my $userid = $session->param('id');
1292         if ( $lasttime < time() - $timeout ) {
1293             # time out
1294             $session->delete();
1295             C4::Context->_unset_userenv($sessionID);
1296             $userid    = undef;
1297             $sessionID = undef;
1298             return ("expired", undef);
1299         } elsif ( $ip ne $ENV{'REMOTE_ADDR'} ) {
1300             # IP address changed
1301             $session->delete();
1302             C4::Context->_unset_userenv($sessionID);
1303             $userid    = undef;
1304             $sessionID = undef;
1305             return ("expired", undef);
1306         } else {
1307             $session->param('lasttime',time());
1308             my $flags = haspermission($userid, $flagsrequired);
1309             if ($flags) {
1310                 return ("ok", $sessionID);
1311             } else {
1312                 $session->delete();
1313                 C4::Context->_unset_userenv($sessionID);
1314                 $userid    = undef;
1315                 $sessionID = undef;
1316                 return ("failed", undef);
1317             }
1318         }
1319     } else {
1320         return ("expired", undef);
1321     }
1322 }
1323
1324 =head2 get_session
1325
1326   use CGI::Session;
1327   my $session = get_session($sessionID);
1328
1329 Given a session ID, retrieve the CGI::Session object used to store
1330 the session's state.  The session object can be used to store
1331 data that needs to be accessed by different scripts during a
1332 user's session.
1333
1334 If the C<$sessionID> parameter is an empty string, a new session
1335 will be created.
1336
1337 =cut
1338
1339 sub get_session {
1340     my $sessionID = shift;
1341     my $storage_method = C4::Context->preference('SessionStorage');
1342     my $dbh = C4::Context->dbh;
1343     my $session;
1344     if ($storage_method eq 'mysql'){
1345         $session = new CGI::Session("driver:MySQL;serializer:yaml;id:md5", $sessionID, {Handle=>$dbh});
1346     }
1347     elsif ($storage_method eq 'Pg') {
1348         $session = new CGI::Session("driver:PostgreSQL;serializer:yaml;id:md5", $sessionID, {Handle=>$dbh});
1349     }
1350     else {
1351         # catch all defaults to tmp should work on all systems
1352         $session = new CGI::Session("driver:File;serializer:yaml;id:md5", $sessionID, {Directory=>'/tmp'});
1353     }
1354     return $session;
1355 }
1356
1357 sub checkpw {
1358
1359     my ( $dbh, $userid, $password, $query ) = @_;
1360     if ($ldap) {
1361         $debug and print "## checkpw - checking LDAP\n";
1362         my ($retval,$retcard) = checkpw_ldap(@_);    # EXTERNAL AUTH
1363         ($retval) and return ($retval,$retcard);
1364     }
1365
1366     if ($cas && $query->param('ticket')) {
1367         $debug and print STDERR "## checkpw - checking CAS\n";
1368         # In case of a CAS authentication, we use the ticket instead of the password
1369         my $ticket = $query->param('ticket');
1370         my ($retval,$retcard,$retuserid) = checkpw_cas($dbh, $ticket, $query);    # EXTERNAL AUTH
1371         ($retval) and return ($retval,$retcard,$retuserid);
1372         return 0;
1373     }
1374
1375     # INTERNAL AUTH
1376     my $sth =
1377       $dbh->prepare(
1378 "select password,cardnumber,borrowernumber,userid,firstname,surname,branchcode,flags from borrowers where userid=?"
1379       );
1380     $sth->execute($userid);
1381     if ( $sth->rows ) {
1382         my ( $md5password, $cardnumber, $borrowernumber, $userid, $firstname,
1383             $surname, $branchcode, $flags )
1384           = $sth->fetchrow;
1385         if ( md5_base64($password) eq $md5password and $md5password ne "!") {
1386
1387             C4::Context->set_userenv( "$borrowernumber", $userid, $cardnumber,
1388                 $firstname, $surname, $branchcode, $flags );
1389             return 1, $cardnumber;
1390         }
1391     }
1392     $sth =
1393       $dbh->prepare(
1394 "select password,cardnumber,borrowernumber,userid, firstname,surname,branchcode,flags from borrowers where cardnumber=?"
1395       );
1396     $sth->execute($userid);
1397     if ( $sth->rows ) {
1398         my ( $md5password, $cardnumber, $borrowernumber, $userid, $firstname,
1399             $surname, $branchcode, $flags )
1400           = $sth->fetchrow;
1401         if ( md5_base64($password) eq $md5password ) {
1402
1403             C4::Context->set_userenv( $borrowernumber, $userid, $cardnumber,
1404                 $firstname, $surname, $branchcode, $flags );
1405             return 1, $userid;
1406         }
1407     }
1408     if (   $userid && $userid eq C4::Context->config('user')
1409         && "$password" eq C4::Context->config('pass') )
1410     {
1411
1412 # Koha superuser account
1413 #     C4::Context->set_userenv(0,0,C4::Context->config('user'),C4::Context->config('user'),C4::Context->config('user'),"",1);
1414         return 2;
1415     }
1416     if (   $userid && $userid eq 'demo'
1417         && "$password" eq 'demo'
1418         && C4::Context->config('demo') )
1419     {
1420
1421 # DEMO => the demo user is allowed to do everything (if demo set to 1 in koha.conf
1422 # some features won't be effective : modify systempref, modify MARC structure,
1423         return 2;
1424     }
1425     return 0;
1426 }
1427
1428 =head2 getuserflags
1429
1430     my $authflags = getuserflags($flags, $userid, [$dbh]);
1431
1432 Translates integer flags into permissions strings hash.
1433
1434 C<$flags> is the integer userflags value ( borrowers.userflags )
1435 C<$userid> is the members.userid, used for building subpermissions
1436 C<$authflags> is a hashref of permissions
1437
1438 =cut
1439
1440 sub getuserflags {
1441     my $flags   = shift;
1442     my $userid  = shift;
1443     my $dbh     = @_ ? shift : C4::Context->dbh;
1444     my $userflags;
1445     $flags = 0 unless $flags;
1446     my $sth = $dbh->prepare("SELECT bit, flag, defaulton FROM userflags");
1447     $sth->execute;
1448
1449     while ( my ( $bit, $flag, $defaulton ) = $sth->fetchrow ) {
1450         if ( ( $flags & ( 2**$bit ) ) || $defaulton ) {
1451             $userflags->{$flag} = 1;
1452         }
1453         else {
1454             $userflags->{$flag} = 0;
1455         }
1456     }
1457
1458     # get subpermissions and merge with top-level permissions
1459     my $user_subperms = get_user_subpermissions($userid);
1460     foreach my $module (keys %$user_subperms) {
1461         next if $userflags->{$module} == 1; # user already has permission for everything in this module
1462         $userflags->{$module} = $user_subperms->{$module};
1463     }
1464
1465     return $userflags;
1466 }
1467
1468 =head2 get_user_subpermissions
1469
1470   $user_perm_hashref = get_user_subpermissions($userid);
1471
1472 Given the userid (note, not the borrowernumber) of a staff user,
1473 return a hashref of hashrefs of the specific subpermissions
1474 accorded to the user.  An example return is
1475
1476  {
1477     tools => {
1478         export_catalog => 1,
1479         import_patrons => 1,
1480     }
1481  }
1482
1483 The top-level hash-key is a module or function code from
1484 userflags.flag, while the second-level key is a code
1485 from permissions.
1486
1487 The results of this function do not give a complete picture
1488 of the functions that a staff user can access; it is also
1489 necessary to check borrowers.flags.
1490
1491 =cut
1492
1493 sub get_user_subpermissions {
1494     my $userid = shift;
1495
1496     my $dbh = C4::Context->dbh;
1497     my $sth = $dbh->prepare("SELECT flag, user_permissions.code
1498                              FROM user_permissions
1499                              JOIN permissions USING (module_bit, code)
1500                              JOIN userflags ON (module_bit = bit)
1501                              JOIN borrowers USING (borrowernumber)
1502                              WHERE userid = ?");
1503     $sth->execute($userid);
1504
1505     my $user_perms = {};
1506     while (my $perm = $sth->fetchrow_hashref) {
1507         $user_perms->{$perm->{'flag'}}->{$perm->{'code'}} = 1;
1508     }
1509     return $user_perms;
1510 }
1511
1512 =head2 get_all_subpermissions
1513
1514   my $perm_hashref = get_all_subpermissions();
1515
1516 Returns a hashref of hashrefs defining all specific
1517 permissions currently defined.  The return value
1518 has the same structure as that of C<get_user_subpermissions>,
1519 except that the innermost hash value is the description
1520 of the subpermission.
1521
1522 =cut
1523
1524 sub get_all_subpermissions {
1525     my $dbh = C4::Context->dbh;
1526     my $sth = $dbh->prepare("SELECT flag, code, description
1527                              FROM permissions
1528                              JOIN userflags ON (module_bit = bit)");
1529     $sth->execute();
1530
1531     my $all_perms = {};
1532     while (my $perm = $sth->fetchrow_hashref) {
1533         $all_perms->{$perm->{'flag'}}->{$perm->{'code'}} = $perm->{'description'};
1534     }
1535     return $all_perms;
1536 }
1537
1538 =head2 haspermission
1539
1540   $flags = ($userid, $flagsrequired);
1541
1542 C<$userid> the userid of the member
1543 C<$flags> is a hashref of required flags like C<$borrower-&lt;{authflags}> 
1544
1545 Returns member's flags or 0 if a permission is not met.
1546
1547 =cut
1548
1549 sub haspermission {
1550     my ($userid, $flagsrequired) = @_;
1551     my $sth = C4::Context->dbh->prepare("SELECT flags FROM borrowers WHERE userid=?");
1552     $sth->execute($userid);
1553     my $flags = getuserflags( $sth->fetchrow(), $userid );
1554     if ( $userid eq C4::Context->config('user') ) {
1555         # Super User Account from /etc/koha.conf
1556         $flags->{'superlibrarian'} = 1;
1557     }
1558     elsif ( $userid eq 'demo' && C4::Context->config('demo') ) {
1559         # Demo user that can do "anything" (demo=1 in /etc/koha.conf)
1560         $flags->{'superlibrarian'} = 1;
1561     }
1562     return $flags if $flags->{superlibrarian};
1563     foreach my $module ( keys %$flagsrequired ) {
1564         if (C4::Context->preference('GranularPermissions')) {
1565             my $subperm = $flagsrequired->{$module};
1566             if ($subperm eq '*') {
1567                 return 0 unless ( $flags->{$module} == 1 or ref($flags->{$module}) );
1568             } else {
1569                 return 0 unless ( $flags->{$module} == 1 or
1570                                     ( ref($flags->{$module}) and
1571                                       exists $flags->{$module}->{$subperm} and
1572                                       $flags->{$module}->{$subperm} == 1
1573                                     )
1574                                 );
1575             }
1576         } else {
1577             return 0 unless ( $flags->{$module} );
1578         }
1579     }
1580     return $flags;
1581     #FIXME - This fcn should return the failed permission so a suitable error msg can be delivered.
1582 }
1583
1584
1585 sub getborrowernumber {
1586     my ($userid) = @_;
1587     my $userenv = C4::Context->userenv;
1588     if ( defined( $userenv ) && ref( $userenv ) eq 'HASH' && $userenv->{number} ) {
1589         return $userenv->{number};
1590     }
1591     my $dbh = C4::Context->dbh;
1592     for my $field ( 'userid', 'cardnumber' ) {
1593         my $sth =
1594           $dbh->prepare("select borrowernumber from borrowers where $field=?");
1595         $sth->execute($userid);
1596         if ( $sth->rows ) {
1597             my ($bnumber) = $sth->fetchrow;
1598             return $bnumber;
1599         }
1600     }
1601     return 0;
1602 }
1603
1604 END { }    # module clean-up code here (global destructor)
1605 1;
1606 __END__
1607
1608 =head1 SEE ALSO
1609
1610 CGI(3)
1611
1612 C4::Output(3)
1613
1614 Digest::MD5(3)
1615
1616 =cut