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