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