Merge remote branch 'kc/master' into new/enh/bug_5917
[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} eq '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             using_https                  => $in->{'query'}->https() ? 1 : 0,
341             noItemTypeImages            => C4::Context->preference("noItemTypeImages"),
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             advancedMARCEditor          => C4::Context->preference("advancedMARCEditor"),
360             canreservefromotherbranches => C4::Context->preference('canreservefromotherbranches'),
361             intranetcolorstylesheet     => C4::Context->preference("intranetcolorstylesheet"),
362             IntranetFavicon             => C4::Context->preference("IntranetFavicon"),
363             intranetreadinghistory      => C4::Context->preference("intranetreadinghistory"),
364             intranetstylesheet          => C4::Context->preference("intranetstylesheet"),
365             IntranetUserCSS             => C4::Context->preference("IntranetUserCSS"),
366             intranetuserjs              => C4::Context->preference("intranetuserjs"),
367             intranetbookbag             => C4::Context->preference("intranetbookbag"),
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 $opac_name = '';
384         if (($opac_search_limit =~ /branch:(\w+)/ && $opac_limit_override) || $in->{'query'}->param('limit') =~ /branch:(\w+)/){
385             $opac_name = $1;   # opac_search_limit is a branch, so we use it.
386         } elsif (C4::Context->preference("SearchMyLibraryFirst") && C4::Context->userenv && C4::Context->userenv->{'branch'}) {
387             $opac_name = C4::Context->userenv->{'branch'};
388         }
389         my $checkstyle = C4::Context->preference("opaccolorstylesheet");
390         if ($checkstyle =~ /http/)
391         {
392                 $template->param( opacexternalsheet => $checkstyle);
393         } else
394         {
395                 my $opaccolorstylesheet = C4::Context->preference("opaccolorstylesheet");  
396             $template->param( opaccolorstylesheet => $opaccolorstylesheet);
397         }
398         $template->param(
399             AmazonContent             => "" . C4::Context->preference("AmazonContent"),
400             AnonSuggestions           => "" . C4::Context->preference("AnonSuggestions"),
401             AuthorisedValueImages     => C4::Context->preference("AuthorisedValueImages"),
402             BranchesLoop              => GetBranchesLoop($opac_name),
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_css_override           => $ENV{'OPAC_CSS_OVERRIDE'},
421             opac_search_limit         => $opac_search_limit,
422             opac_limit_override       => $opac_limit_override,
423             OpacBrowser               => C4::Context->preference("OpacBrowser"),
424             OpacCloud                 => C4::Context->preference("OpacCloud"),
425             OpacMainUserBlock         => "" . C4::Context->preference("OpacMainUserBlock"),
426             OpacNav                   => "" . C4::Context->preference("OpacNav"),
427             OpacPasswordChange        => C4::Context->preference("OpacPasswordChange"),
428             OPACPatronDetails        => C4::Context->preference("OPACPatronDetails"),
429             OPACPrivacy               => C4::Context->preference("OPACPrivacy"),
430             OPACFinesTab              => C4::Context->preference("OPACFinesTab"),
431             OpacTopissue              => C4::Context->preference("OpacTopissue"),
432             RequestOnOpac             => C4::Context->preference("RequestOnOpac"),
433             'Version'                 => C4::Context->preference('Version'),
434             hidelostitems             => C4::Context->preference("hidelostitems"),
435             mylibraryfirst            => (C4::Context->preference("SearchMyLibraryFirst") && C4::Context->userenv) ? C4::Context->userenv->{'branch'} : '',
436             opaclayoutstylesheet      => "" . C4::Context->preference("opaclayoutstylesheet"),
437             opacstylesheet            => "" . C4::Context->preference("opacstylesheet"),
438             opacbookbag               => "" . C4::Context->preference("opacbookbag"),
439             opaccredits               => "" . C4::Context->preference("opaccredits"),
440             OpacFavicon               => C4::Context->preference("OpacFavicon"),
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             ShowReviewer              => C4::Context->preference("ShowReviewer"),
449             suggestion                => "" . C4::Context->preference("suggestion"),
450             virtualshelves            => "" . C4::Context->preference("virtualshelves"),
451             OPACSerialIssueDisplayCount => C4::Context->preference("OPACSerialIssueDisplayCount"),
452             OpacAddMastheadLibraryPulldown => C4::Context->preference("OpacAddMastheadLibraryPulldown"),
453             OPACXSLTDetailsDisplay           => C4::Context->preference("OPACXSLTDetailsDisplay"),
454             OPACXSLTResultsDisplay           => C4::Context->preference("OPACXSLTResultsDisplay"),
455             SyndeticsClientCode          => C4::Context->preference("SyndeticsClientCode"),
456             SyndeticsEnabled             => C4::Context->preference("SyndeticsEnabled"),
457             SyndeticsCoverImages         => C4::Context->preference("SyndeticsCoverImages"),
458             SyndeticsTOC                 => C4::Context->preference("SyndeticsTOC"),
459             SyndeticsSummary             => C4::Context->preference("SyndeticsSummary"),
460             SyndeticsEditions            => C4::Context->preference("SyndeticsEditions"),
461             SyndeticsExcerpt             => C4::Context->preference("SyndeticsExcerpt"),
462             SyndeticsReviews             => C4::Context->preference("SyndeticsReviews"),
463             SyndeticsAuthorNotes         => C4::Context->preference("SyndeticsAuthorNotes"),
464             SyndeticsAwards              => C4::Context->preference("SyndeticsAwards"),
465             SyndeticsSeries              => C4::Context->preference("SyndeticsSeries"),
466             SyndeticsCoverImageSize      => C4::Context->preference("SyndeticsCoverImageSize"),
467         );
468
469         $template->param(OpacPublic => '1') if ($user || C4::Context->preference("OpacPublic"));
470     }
471         $template->param(listloop=>[{shelfname=>"Freelist", shelfnumber=>110}]);
472     return ( $template, $borrowernumber, $cookie, $flags);
473 }
474
475 =head2 checkauth
476
477   ($userid, $cookie, $sessionID) = &checkauth($query, $noauth, $flagsrequired, $type);
478
479 Verifies that the user is authorized to run this script.  If
480 the user is authorized, a (userid, cookie, session-id, flags)
481 quadruple is returned.  If the user is not authorized but does
482 not have the required privilege (see $flagsrequired below), it
483 displays an error page and exits.  Otherwise, it displays the
484 login page and exits.
485
486 Note that C<&checkauth> will return if and only if the user
487 is authorized, so it should be called early on, before any
488 unfinished operations (e.g., if you've opened a file, then
489 C<&checkauth> won't close it for you).
490
491 C<$query> is the CGI object for the script calling C<&checkauth>.
492
493 The C<$noauth> argument is optional. If it is set, then no
494 authorization is required for the script.
495
496 C<&checkauth> fetches user and session information from C<$query> and
497 ensures that the user is authorized to run scripts that require
498 authorization.
499
500 The C<$flagsrequired> argument specifies the required privileges
501 the user must have if the username and password are correct.
502 It should be specified as a reference-to-hash; keys in the hash
503 should be the "flags" for the user, as specified in the Members
504 intranet module. Any key specified must correspond to a "flag"
505 in the userflags table. E.g., { circulate => 1 } would specify
506 that the user must have the "circulate" privilege in order to
507 proceed. To make sure that access control is correct, the
508 C<$flagsrequired> parameter must be specified correctly.
509
510 Koha also has a concept of sub-permissions, also known as
511 granular permissions.  This makes the value of each key
512 in the C<flagsrequired> hash take on an additional
513 meaning, i.e.,
514
515  1
516
517 The user must have access to all subfunctions of the module
518 specified by the hash key.
519
520  *
521
522 The user must have access to at least one subfunction of the module
523 specified by the hash key.
524
525  specific permission, e.g., 'export_catalog'
526
527 The user must have access to the specific subfunction list, which
528 must correspond to a row in the permissions table.
529
530 The C<$type> argument specifies whether the template should be
531 retrieved from the opac or intranet directory tree.  "opac" is
532 assumed if it is not specified; however, if C<$type> is specified,
533 "intranet" is assumed if it is not "opac".
534
535 If C<$query> does not have a valid session ID associated with it
536 (i.e., the user has not logged in) or if the session has expired,
537 C<&checkauth> presents the user with a login page (from the point of
538 view of the original script, C<&checkauth> does not return). Once the
539 user has authenticated, C<&checkauth> restarts the original script
540 (this time, C<&checkauth> returns).
541
542 The login page is provided using a HTML::Template, which is set in the
543 systempreferences table or at the top of this file. The variable C<$type>
544 selects which template to use, either the opac or the intranet
545 authentification template.
546
547 C<&checkauth> returns a user ID, a cookie, and a session ID. The
548 cookie should be sent back to the browser; it verifies that the user
549 has authenticated.
550
551 =cut
552
553 sub _version_check ($$) {
554     my $type = shift;
555     my $query = shift;
556     my $version;
557     # If Version syspref is unavailable, it means Koha is beeing installed,
558     # and so we must redirect to OPAC maintenance page or to the WebInstaller
559         # also, if OpacMaintenance is ON, OPAC should redirect to maintenance
560         if (C4::Context->preference('OpacMaintenance') && $type eq 'opac') {
561                 warn "OPAC Install required, redirecting to maintenance";
562                 print $query->redirect("/cgi-bin/koha/maintenance.pl");
563         }
564     unless ($version = C4::Context->preference('Version')) {    # assignment, not comparison
565       if ($type ne 'opac') {
566         warn "Install required, redirecting to Installer";
567         print $query->redirect("/cgi-bin/koha/installer/install.pl");
568       }
569       else {
570         warn "OPAC Install required, redirecting to maintenance";
571         print $query->redirect("/cgi-bin/koha/maintenance.pl");
572       }
573       exit;
574     }
575
576     # check that database and koha version are the same
577     # there is no DB version, it's a fresh install,
578     # go to web installer
579     # there is a DB version, compare it to the code version
580     my $kohaversion=C4::Context::KOHAVERSION;
581     # remove the 3 last . to have a Perl number
582     $kohaversion =~ s/(.*\..*)\.(.*)\.(.*)/$1$2$3/;
583     $debug and print STDERR "kohaversion : $kohaversion\n";
584     if ($version < $kohaversion){
585         my $warning = "Database update needed, redirecting to %s. Database is $version and Koha is $kohaversion";
586         if ($type ne 'opac'){
587             warn sprintf($warning, 'Installer');
588             print $query->redirect("/cgi-bin/koha/installer/install.pl?step=3");
589         } else {
590             warn sprintf("OPAC: " . $warning, 'maintenance');
591             print $query->redirect("/cgi-bin/koha/maintenance.pl");
592         }
593         exit;
594     }
595 }
596
597 sub _session_log {
598     (@_) or return 0;
599     open L, ">>/tmp/sessionlog" or warn "ERROR: Cannot append to /tmp/sessionlog";
600     printf L join("\n",@_);
601     close L;
602 }
603
604 sub checkauth {
605     my $query = shift;
606         $debug and warn "Checking Auth";
607     # $authnotrequired will be set for scripts which will run without authentication
608     my $authnotrequired = shift;
609     my $flagsrequired   = shift;
610     my $type            = shift;
611     $type = 'opac' unless $type;
612
613     my $dbh     = C4::Context->dbh;
614     my $timeout = C4::Context->preference('timeout');
615     # days
616     if ($timeout =~ /(\d+)[dD]/) {
617         $timeout = $1 * 86400;
618     };
619     $timeout = 600 unless $timeout;
620
621     _version_check($type,$query);
622     # state variables
623     my $loggedin = 0;
624     my %info;
625     my ( $userid, $cookie, $sessionID, $flags, $barshelves, $pubshelves );
626     my $logout = $query->param('logout.x');
627
628     if ( $userid = $ENV{'REMOTE_USER'} ) {
629         # Using Basic Authentication, no cookies required
630         $cookie = $query->cookie(
631             -name    => 'CGISESSID',
632             -value   => '',
633             -expires => ''
634         );
635         $loggedin = 1;
636     }
637     elsif ( $sessionID = $query->cookie("CGISESSID")) {     # assignment, not comparison
638         my $session = get_session($sessionID);
639         C4::Context->_new_userenv($sessionID);
640         my ($ip, $lasttime, $sessiontype);
641         if ($session){
642             C4::Context::set_userenv(
643                 $session->param('number'),       $session->param('id'),
644                 $session->param('cardnumber'),   $session->param('firstname'),
645                 $session->param('surname'),      $session->param('branch'),
646                 $session->param('branchname'),   $session->param('flags'),
647                 $session->param('emailaddress'), $session->param('branchprinter')
648             );
649             C4::Context::set_shelves_userenv('bar',$session->param('barshelves'));
650             C4::Context::set_shelves_userenv('pub',$session->param('pubshelves'));
651             C4::Context::set_shelves_userenv('tot',$session->param('totshelves'));
652             $debug and printf STDERR "AUTH_SESSION: (%s)\t%s %s - %s\n", map {$session->param($_)} qw(cardnumber firstname surname branch) ;
653             $ip       = $session->param('ip');
654             $lasttime = $session->param('lasttime');
655             $userid   = $session->param('id');
656                         $sessiontype = $session->param('sessiontype');
657         }
658         if ( ($query->param('koha_login_context')) && ($query->param('userid') ne $session->param('id')) ) {
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();
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 || $userid) {
722                 my $password = $query->param('password');
723                 my ($return, $cardnumber);
724                 if ($cas && $query->param('ticket')) {
725                     my $retuserid;
726                     ( $return, $cardnumber, $retuserid ) = checkpw( $dbh, $userid, $password, $query );
727                     $userid = $retuserid;
728                     $info{'invalidCasLogin'} = 1 unless ($return);
729                 } else {
730                     ( $return, $cardnumber ) = checkpw( $dbh, $userid, $password, $query );
731                 }
732                 if ($return) {
733                _session_log(sprintf "%20s from %16s logged in  at %30s.\n", $userid,$ENV{'REMOTE_ADDR'},(strftime '%c', localtime));
734                 if ( $flags = haspermission(  $userid, $flagsrequired ) ) {
735                                         $loggedin = 1;
736                 }
737                         else {
738                         $info{'nopermission'} = 1;
739                         C4::Context->_unset_userenv($sessionID);
740                 }
741
742                                 my ($borrowernumber, $firstname, $surname, $userflags,
743                                         $branchcode, $branchname, $branchprinter, $emailaddress);
744
745                 if ( $return == 1 ) {
746                         my $select = "
747                         SELECT borrowernumber, firstname, surname, flags, borrowers.branchcode, 
748                             branches.branchname    as branchname, 
749                                 branches.branchprinter as branchprinter, 
750                                 email 
751                         FROM borrowers 
752                         LEFT JOIN branches on borrowers.branchcode=branches.branchcode
753                         ";
754                         my $sth = $dbh->prepare("$select where userid=?");
755                         $sth->execute($userid);
756                                         unless ($sth->rows) {
757                                 $debug and print STDERR "AUTH_1: no rows for userid='$userid'\n";
758                                                 $sth = $dbh->prepare("$select where cardnumber=?");
759                                 $sth->execute($cardnumber);
760                                                 unless ($sth->rows) {
761                                         $debug and print STDERR "AUTH_2a: no rows for cardnumber='$cardnumber'\n";
762                                 $sth->execute($userid);
763                                                         unless ($sth->rows) {
764                                                 $debug and print STDERR "AUTH_2b: no rows for userid='$userid' AS cardnumber\n";
765                                                         }
766                                                 }
767                                         }
768                         if ($sth->rows) {
769                         ($borrowernumber, $firstname, $surname, $userflags,
770                                 $branchcode, $branchname, $branchprinter, $emailaddress) = $sth->fetchrow;
771                                                 $debug and print STDERR "AUTH_3 results: " .
772                                                         "$cardnumber,$borrowernumber,$userid,$firstname,$surname,$userflags,$branchcode,$emailaddress\n";
773                                         } else {
774                                                 print STDERR "AUTH_3: no results for userid='$userid', cardnumber='$cardnumber'.\n";
775                                         }
776
777 # launch a sequence to check if we have a ip for the branch, i
778 # if we have one we replace the branchcode of the userenv by the branch bound in the ip.
779
780                                         my $ip       = $ENV{'REMOTE_ADDR'};
781                                         # if they specify at login, use that
782                                         if ($query->param('branch')) {
783                                                 $branchcode  = $query->param('branch');
784                                                 $branchname = GetBranchName($branchcode);
785                                         }
786                                         my $branches = GetBranches();
787                                         if (C4::Context->boolean_preference('IndependantBranches') && C4::Context->boolean_preference('Autolocation')){
788                                                 # we have to check they are coming from the right ip range
789                                                 my $domain = $branches->{$branchcode}->{'branchip'};
790                                                 if ($ip !~ /^$domain/){
791                                                         $loggedin=0;
792                                                         $info{'wrongip'} = 1;
793                                                 }
794                                         }
795
796                                         my @branchesloop;
797                                         foreach my $br ( keys %$branches ) {
798                                                 #     now we work with the treatment of ip
799                                                 my $domain = $branches->{$br}->{'branchip'};
800                                                 if ( $domain && $ip =~ /^$domain/ ) {
801                                                         $branchcode = $branches->{$br}->{'branchcode'};
802
803                                                         # new op dev : add the branchprinter and branchname in the cookie
804                                                         $branchprinter = $branches->{$br}->{'branchprinter'};
805                                                         $branchname    = $branches->{$br}->{'branchname'};
806                                                 }
807                                         }
808                                         $session->param('number',$borrowernumber);
809                                         $session->param('id',$userid);
810                                         $session->param('cardnumber',$cardnumber);
811                                         $session->param('firstname',$firstname);
812                                         $session->param('surname',$surname);
813                                         $session->param('branch',$branchcode);
814                                         $session->param('branchname',$branchname);
815                                         $session->param('flags',$userflags);
816                                         $session->param('emailaddress',$emailaddress);
817                                         $session->param('ip',$session->remote_addr());
818                                         $session->param('lasttime',time());
819                                         $debug and printf STDERR "AUTH_4: (%s)\t%s %s - %s\n", map {$session->param($_)} qw(cardnumber firstname surname branch) ;
820                                 }
821                                 elsif ( $return == 2 ) {
822                                         #We suppose the user is the superlibrarian
823                                         $borrowernumber = 0;
824                                         $session->param('number',0);
825                                         $session->param('id',C4::Context->config('user'));
826                                         $session->param('cardnumber',C4::Context->config('user'));
827                                         $session->param('firstname',C4::Context->config('user'));
828                                         $session->param('surname',C4::Context->config('user'));
829                                         $session->param('branch','NO_LIBRARY_SET');
830                                         $session->param('branchname','NO_LIBRARY_SET');
831                                         $session->param('flags',1);
832                                         $session->param('emailaddress', C4::Context->preference('KohaAdminEmailAddress'));
833                                         $session->param('ip',$session->remote_addr());
834                                         $session->param('lasttime',time());
835                                 }
836                                 C4::Context::set_userenv(
837                                         $session->param('number'),       $session->param('id'),
838                                         $session->param('cardnumber'),   $session->param('firstname'),
839                                         $session->param('surname'),      $session->param('branch'),
840                                         $session->param('branchname'),   $session->param('flags'),
841                                         $session->param('emailaddress'), $session->param('branchprinter')
842                                 );
843
844                                 # Grab borrower's shelves and public shelves and add them to the session
845                                 # $row_count determines how many records are returned from the db query
846                                 # and the number of lists to be displayed of each type in the 'Lists' button drop down
847                                 my $row_count = 10; # FIXME:This probably should be a syspref
848                                 my ($total, $totshelves, $barshelves, $pubshelves);
849                                 ($barshelves, $totshelves) = C4::VirtualShelves::GetRecentShelves(1, $row_count, $borrowernumber);
850                                 $total->{'bartotal'} = $totshelves;
851                                 ($pubshelves, $totshelves) = C4::VirtualShelves::GetRecentShelves(2, $row_count, undef);
852                                 $total->{'pubtotal'} = $totshelves;
853                                 $session->param('barshelves', $barshelves->[0]);
854                                 $session->param('pubshelves', $pubshelves->[0]);
855                                 $session->param('totshelves', $total);
856
857                                 C4::Context::set_shelves_userenv('bar',$barshelves->[0]);
858                                 C4::Context::set_shelves_userenv('pub',$pubshelves->[0]);
859                                 C4::Context::set_shelves_userenv('tot',$total);
860                         }
861                 else {
862                 if ($userid) {
863                         $info{'invalid_username_or_password'} = 1;
864                         C4::Context->_unset_userenv($sessionID);
865                 }
866                         }
867         }       # END if ( $userid    = $query->param('userid') )
868                 elsif ($type eq "opac") {
869             # if we are here this is an anonymous session; add public lists to it and a few other items...
870             # anonymous sessions are created only for the OPAC
871                         $debug and warn "Initiating an anonymous session...";
872
873                         # Grab the public shelves and add to the session...
874                         my $row_count = 20; # FIXME:This probably should be a syspref
875                         my ($total, $totshelves, $pubshelves);
876                         ($pubshelves, $totshelves) = C4::VirtualShelves::GetRecentShelves(2, $row_count, undef);
877                         $total->{'pubtotal'} = $totshelves;
878                         $session->param('pubshelves', $pubshelves->[0]);
879                         $session->param('totshelves', $total);
880                         C4::Context::set_shelves_userenv('pub',$pubshelves->[0]);
881                         C4::Context::set_shelves_userenv('tot',$total);
882
883                         # setting a couple of other session vars...
884                         $session->param('ip',$session->remote_addr());
885                         $session->param('lasttime',time());
886                         $session->param('sessiontype','anon');
887                 }
888     }   # END unless ($userid)
889     my $insecure = C4::Context->boolean_preference('insecure');
890
891     # finished authentification, now respond
892     if ( $loggedin || $authnotrequired || ( defined($insecure) && $insecure ) )
893     {
894         # successful login
895         unless ($cookie) {
896             $cookie = $query->cookie( CGISESSID => '' );
897         }
898         return ( $userid, $cookie, $sessionID, $flags );
899     }
900
901 #
902 #
903 # AUTH rejected, show the login/password template, after checking the DB.
904 #
905 #
906
907     # get the inputs from the incoming query
908     my @inputs = ();
909     foreach my $name ( param $query) {
910         (next) if ( $name eq 'userid' || $name eq 'password' || $name eq 'ticket' );
911         my $value = $query->param($name);
912         push @inputs, { name => $name, value => $value };
913     }
914     # get the branchloop, which we need for authentication
915     my $branches = GetBranches();
916     my @branch_loop;
917     for my $branch_hash (sort keys %$branches) {
918                 push @branch_loop, {branchcode => "$branch_hash", branchname => $branches->{$branch_hash}->{'branchname'}, };
919     }
920
921     my $template_name = ( $type eq 'opac' ) ? 'opac-auth.tmpl' : 'auth.tmpl';
922     my $template = gettemplate( $template_name, $type, $query );
923     $template->param(branchloop => \@branch_loop,);
924     my $checkstyle = C4::Context->preference("opaccolorstylesheet");
925     if ($checkstyle =~ /\//)
926         {
927                 $template->param( opacexternalsheet => $checkstyle);
928         } else
929         {
930                 my $opaccolorstylesheet = C4::Context->preference("opaccolorstylesheet");  
931             $template->param( opaccolorstylesheet => $opaccolorstylesheet);
932         }
933     $template->param(
934     login        => 1,
935         INPUTS               => \@inputs,
936         casAuthentication    => C4::Context->preference("casAuthentication"),
937         suggestion           => C4::Context->preference("suggestion"),
938         virtualshelves       => C4::Context->preference("virtualshelves"),
939         LibraryName          => C4::Context->preference("LibraryName"),
940         opacuserlogin        => C4::Context->preference("opacuserlogin"),
941         OpacNav              => C4::Context->preference("OpacNav"),
942         opaccredits          => C4::Context->preference("opaccredits"),
943         OpacFavicon          => C4::Context->preference("OpacFavicon"),
944         opacreadinghistory   => C4::Context->preference("opacreadinghistory"),
945         opacsmallimage       => C4::Context->preference("opacsmallimage"),
946         opaclayoutstylesheet => C4::Context->preference("opaclayoutstylesheet"),
947         opaclanguagesdisplay => C4::Context->preference("opaclanguagesdisplay"),
948         opacuserjs           => C4::Context->preference("opacuserjs"),
949         opacbookbag          => "" . C4::Context->preference("opacbookbag"),
950         OpacCloud            => C4::Context->preference("OpacCloud"),
951         OpacTopissue         => C4::Context->preference("OpacTopissue"),
952         OpacAuthorities      => C4::Context->preference("OpacAuthorities"),
953         OpacBrowser          => C4::Context->preference("OpacBrowser"),
954         opacheader           => C4::Context->preference("opacheader"),
955         TagsEnabled                  => C4::Context->preference("TagsEnabled"),
956         OPACUserCSS           => C4::Context->preference("OPACUserCSS"),
957         opacstylesheet       => C4::Context->preference("opacstylesheet"),
958         intranetcolorstylesheet =>
959                                                                 C4::Context->preference("intranetcolorstylesheet"),
960         intranetstylesheet => C4::Context->preference("intranetstylesheet"),
961         intranetbookbag    => C4::Context->preference("intranetbookbag"),
962         IntranetNav        => C4::Context->preference("IntranetNav"),
963         intranetuserjs     => C4::Context->preference("intranetuserjs"),
964         IndependantBranches=> C4::Context->preference("IndependantBranches"),
965         AutoLocation       => C4::Context->preference("AutoLocation"),
966                 wrongip            => $info{'wrongip'},
967     );
968
969     $template->param( OpacPublic => C4::Context->preference("OpacPublic"));
970     $template->param( loginprompt => 1 ) unless $info{'nopermission'};
971
972     if ($cas) { 
973         $template->param(
974             casServerUrl    => login_cas_url(),
975             invalidCasLogin => $info{'invalidCasLogin'}
976         );
977     }
978
979     my $self_url = $query->url( -absolute => 1 );
980     $template->param(
981         url         => $self_url,
982         LibraryName => C4::Context->preference("LibraryName"),
983     );
984     $template->param( \%info );
985 #    $cookie = $query->cookie(CGISESSID => $session->id
986 #   );
987     print $query->header(
988         -type   => 'text/html',
989         -charset => 'utf-8',
990         -cookie => $cookie
991       ),
992       $template->output;
993     exit;
994 }
995
996 =head2 check_api_auth
997
998   ($status, $cookie, $sessionId) = check_api_auth($query, $userflags);
999
1000 Given a CGI query containing the parameters 'userid' and 'password' and/or a session
1001 cookie, determine if the user has the privileges specified by C<$userflags>.
1002
1003 C<check_api_auth> is is meant for authenticating users of web services, and
1004 consequently will always return and will not attempt to redirect the user
1005 agent.
1006
1007 If a valid session cookie is already present, check_api_auth will return a status
1008 of "ok", the cookie, and the Koha session ID.
1009
1010 If no session cookie is present, check_api_auth will check the 'userid' and 'password
1011 parameters and create a session cookie and Koha session if the supplied credentials
1012 are OK.
1013
1014 Possible return values in C<$status> are:
1015
1016 =over
1017
1018 =item "ok" -- user authenticated; C<$cookie> and C<$sessionid> have valid values.
1019
1020 =item "failed" -- credentials are not correct; C<$cookie> and C<$sessionid> are undef
1021
1022 =item "maintenance" -- DB is in maintenance mode; no login possible at the moment
1023
1024 =item "expired -- session cookie has expired; API user should resubmit userid and password
1025
1026 =back
1027
1028 =cut
1029
1030 sub check_api_auth {
1031     my $query = shift;
1032     my $flagsrequired = shift;
1033
1034     my $dbh     = C4::Context->dbh;
1035     my $timeout = C4::Context->preference('timeout');
1036     $timeout = 600 unless $timeout;
1037
1038     unless (C4::Context->preference('Version')) {
1039         # database has not been installed yet
1040         return ("maintenance", undef, undef);
1041     }
1042     my $kohaversion=C4::Context::KOHAVERSION;
1043     $kohaversion =~ s/(.*\..*)\.(.*)\.(.*)/$1$2$3/;
1044     if (C4::Context->preference('Version') < $kohaversion) {
1045         # database in need of version update; assume that
1046         # no API should be called while databsae is in
1047         # this condition.
1048         return ("maintenance", undef, undef);
1049     }
1050
1051     # FIXME -- most of what follows is a copy-and-paste
1052     # of code from checkauth.  There is an obvious need
1053     # for refactoring to separate the various parts of
1054     # the authentication code, but as of 2007-11-19 this
1055     # is deferred so as to not introduce bugs into the
1056     # regular authentication code for Koha 3.0.
1057
1058     # see if we have a valid session cookie already
1059     # however, if a userid parameter is present (i.e., from
1060     # a form submission, assume that any current cookie
1061     # is to be ignored
1062     my $sessionID = undef;
1063     unless ($query->param('userid')) {
1064         $sessionID = $query->cookie("CGISESSID");
1065     }
1066     if ($sessionID) {
1067         my $session = get_session($sessionID);
1068         C4::Context->_new_userenv($sessionID);
1069         if ($session) {
1070             C4::Context::set_userenv(
1071                 $session->param('number'),       $session->param('id'),
1072                 $session->param('cardnumber'),   $session->param('firstname'),
1073                 $session->param('surname'),      $session->param('branch'),
1074                 $session->param('branchname'),   $session->param('flags'),
1075                 $session->param('emailaddress'), $session->param('branchprinter')
1076             );
1077
1078             my $ip = $session->param('ip');
1079             my $lasttime = $session->param('lasttime');
1080             my $userid = $session->param('id');
1081             if ( $lasttime < time() - $timeout ) {
1082                 # time out
1083                 $session->delete();
1084                 C4::Context->_unset_userenv($sessionID);
1085                 $userid    = undef;
1086                 $sessionID = undef;
1087                 return ("expired", undef, undef);
1088             } elsif ( $ip ne $ENV{'REMOTE_ADDR'} ) {
1089                 # IP address changed
1090                 $session->delete();
1091                 C4::Context->_unset_userenv($sessionID);
1092                 $userid    = undef;
1093                 $sessionID = undef;
1094                 return ("expired", undef, undef);
1095             } else {
1096                 my $cookie = $query->cookie( CGISESSID => $session->id );
1097                 $session->param('lasttime',time());
1098                 my $flags = haspermission($userid, $flagsrequired);
1099                 if ($flags) {
1100                     return ("ok", $cookie, $sessionID);
1101                 } else {
1102                     $session->delete();
1103                     C4::Context->_unset_userenv($sessionID);
1104                     $userid    = undef;
1105                     $sessionID = undef;
1106                     return ("failed", undef, undef);
1107                 }
1108             }
1109         } else {
1110             return ("expired", undef, undef);
1111         }
1112     } else {
1113         # new login
1114         my $userid = $query->param('userid');
1115         my $password = $query->param('password');
1116         unless ($userid and $password) {
1117             # caller did something wrong, fail the authenticateion
1118             return ("failed", undef, undef);
1119         }
1120         my ($return, $cardnumber);
1121         if ($cas && $query->param('ticket')) {
1122             my $retuserid;
1123             ( $return, $cardnumber, $retuserid ) = checkpw( $dbh, $userid, $password, $query );
1124             $userid = $retuserid;
1125         } else {
1126             ( $return, $cardnumber ) = checkpw( $dbh, $userid, $password, $query );
1127         }
1128         if ($return and haspermission(  $userid, $flagsrequired)) {
1129             my $session = get_session("");
1130             return ("failed", undef, undef) unless $session;
1131
1132             my $sessionID = $session->id;
1133             C4::Context->_new_userenv($sessionID);
1134             my $cookie = $query->cookie(CGISESSID => $sessionID);
1135             if ( $return == 1 ) {
1136                 my (
1137                     $borrowernumber, $firstname,  $surname,
1138                     $userflags,      $branchcode, $branchname,
1139                     $branchprinter,  $emailaddress
1140                 );
1141                 my $sth =
1142                   $dbh->prepare(
1143 "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=?"
1144                   );
1145                 $sth->execute($userid);
1146                 (
1147                     $borrowernumber, $firstname,  $surname,
1148                     $userflags,      $branchcode, $branchname,
1149                     $branchprinter,  $emailaddress
1150                 ) = $sth->fetchrow if ( $sth->rows );
1151
1152                 unless ($sth->rows ) {
1153                     my $sth = $dbh->prepare(
1154 "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=?"
1155                       );
1156                     $sth->execute($cardnumber);
1157                     (
1158                         $borrowernumber, $firstname,  $surname,
1159                         $userflags,      $branchcode, $branchname,
1160                         $branchprinter,  $emailaddress
1161                     ) = $sth->fetchrow if ( $sth->rows );
1162
1163                     unless ( $sth->rows ) {
1164                         $sth->execute($userid);
1165                         (
1166                             $borrowernumber, $firstname, $surname, $userflags,
1167                             $branchcode, $branchname, $branchprinter, $emailaddress
1168                         ) = $sth->fetchrow if ( $sth->rows );
1169                     }
1170                 }
1171
1172                 my $ip       = $ENV{'REMOTE_ADDR'};
1173                 # if they specify at login, use that
1174                 if ($query->param('branch')) {
1175                     $branchcode  = $query->param('branch');
1176                     $branchname = GetBranchName($branchcode);
1177                 }
1178                 my $branches = GetBranches();
1179                 my @branchesloop;
1180                 foreach my $br ( keys %$branches ) {
1181                     #     now we work with the treatment of ip
1182                     my $domain = $branches->{$br}->{'branchip'};
1183                     if ( $domain && $ip =~ /^$domain/ ) {
1184                         $branchcode = $branches->{$br}->{'branchcode'};
1185
1186                         # new op dev : add the branchprinter and branchname in the cookie
1187                         $branchprinter = $branches->{$br}->{'branchprinter'};
1188                         $branchname    = $branches->{$br}->{'branchname'};
1189                     }
1190                 }
1191                 $session->param('number',$borrowernumber);
1192                 $session->param('id',$userid);
1193                 $session->param('cardnumber',$cardnumber);
1194                 $session->param('firstname',$firstname);
1195                 $session->param('surname',$surname);
1196                 $session->param('branch',$branchcode);
1197                 $session->param('branchname',$branchname);
1198                 $session->param('flags',$userflags);
1199                 $session->param('emailaddress',$emailaddress);
1200                 $session->param('ip',$session->remote_addr());
1201                 $session->param('lasttime',time());
1202             } elsif ( $return == 2 ) {
1203                 #We suppose the user is the superlibrarian
1204                 $session->param('number',0);
1205                 $session->param('id',C4::Context->config('user'));
1206                 $session->param('cardnumber',C4::Context->config('user'));
1207                 $session->param('firstname',C4::Context->config('user'));
1208                 $session->param('surname',C4::Context->config('user'));
1209                 $session->param('branch','NO_LIBRARY_SET');
1210                 $session->param('branchname','NO_LIBRARY_SET');
1211                 $session->param('flags',1);
1212                 $session->param('emailaddress', C4::Context->preference('KohaAdminEmailAddress'));
1213                 $session->param('ip',$session->remote_addr());
1214                 $session->param('lasttime',time());
1215             }
1216             C4::Context::set_userenv(
1217                 $session->param('number'),       $session->param('id'),
1218                 $session->param('cardnumber'),   $session->param('firstname'),
1219                 $session->param('surname'),      $session->param('branch'),
1220                 $session->param('branchname'),   $session->param('flags'),
1221                 $session->param('emailaddress'), $session->param('branchprinter')
1222             );
1223             return ("ok", $cookie, $sessionID);
1224         } else {
1225             return ("failed", undef, undef);
1226         }
1227     }
1228 }
1229
1230 =head2 check_cookie_auth
1231
1232   ($status, $sessionId) = check_api_auth($cookie, $userflags);
1233
1234 Given a CGISESSID cookie set during a previous login to Koha, determine
1235 if the user has the privileges specified by C<$userflags>.
1236
1237 C<check_cookie_auth> is meant for authenticating special services
1238 such as tools/upload-file.pl that are invoked by other pages that
1239 have been authenticated in the usual way.
1240
1241 Possible return values in C<$status> are:
1242
1243 =over
1244
1245 =item "ok" -- user authenticated; C<$sessionID> have valid values.
1246
1247 =item "failed" -- credentials are not correct; C<$sessionid> are undef
1248
1249 =item "maintenance" -- DB is in maintenance mode; no login possible at the moment
1250
1251 =item "expired -- session cookie has expired; API user should resubmit userid and password
1252
1253 =back
1254
1255 =cut
1256
1257 sub check_cookie_auth {
1258     my $cookie = shift;
1259     my $flagsrequired = shift;
1260
1261     my $dbh     = C4::Context->dbh;
1262     my $timeout = C4::Context->preference('timeout');
1263     $timeout = 600 unless $timeout;
1264
1265     unless (C4::Context->preference('Version')) {
1266         # database has not been installed yet
1267         return ("maintenance", undef);
1268     }
1269     my $kohaversion=C4::Context::KOHAVERSION;
1270     $kohaversion =~ s/(.*\..*)\.(.*)\.(.*)/$1$2$3/;
1271     if (C4::Context->preference('Version') < $kohaversion) {
1272         # database in need of version update; assume that
1273         # no API should be called while databsae is in
1274         # this condition.
1275         return ("maintenance", undef);
1276     }
1277
1278     # FIXME -- most of what follows is a copy-and-paste
1279     # of code from checkauth.  There is an obvious need
1280     # for refactoring to separate the various parts of
1281     # the authentication code, but as of 2007-11-23 this
1282     # is deferred so as to not introduce bugs into the
1283     # regular authentication code for Koha 3.0.
1284
1285     # see if we have a valid session cookie already
1286     # however, if a userid parameter is present (i.e., from
1287     # a form submission, assume that any current cookie
1288     # is to be ignored
1289     unless (defined $cookie and $cookie) {
1290         return ("failed", undef);
1291     }
1292     my $sessionID = $cookie;
1293     my $session = get_session($sessionID);
1294     C4::Context->_new_userenv($sessionID);
1295     if ($session) {
1296         C4::Context::set_userenv(
1297             $session->param('number'),       $session->param('id'),
1298             $session->param('cardnumber'),   $session->param('firstname'),
1299             $session->param('surname'),      $session->param('branch'),
1300             $session->param('branchname'),   $session->param('flags'),
1301             $session->param('emailaddress'), $session->param('branchprinter')
1302         );
1303
1304         my $ip = $session->param('ip');
1305         my $lasttime = $session->param('lasttime');
1306         my $userid = $session->param('id');
1307         if ( $lasttime < time() - $timeout ) {
1308             # time out
1309             $session->delete();
1310             C4::Context->_unset_userenv($sessionID);
1311             $userid    = undef;
1312             $sessionID = undef;
1313             return ("expired", undef);
1314         } elsif ( $ip ne $ENV{'REMOTE_ADDR'} ) {
1315             # IP address changed
1316             $session->delete();
1317             C4::Context->_unset_userenv($sessionID);
1318             $userid    = undef;
1319             $sessionID = undef;
1320             return ("expired", undef);
1321         } else {
1322             $session->param('lasttime',time());
1323             my $flags = haspermission($userid, $flagsrequired);
1324             if ($flags) {
1325                 return ("ok", $sessionID);
1326             } else {
1327                 $session->delete();
1328                 C4::Context->_unset_userenv($sessionID);
1329                 $userid    = undef;
1330                 $sessionID = undef;
1331                 return ("failed", undef);
1332             }
1333         }
1334     } else {
1335         return ("expired", undef);
1336     }
1337 }
1338
1339 =head2 get_session
1340
1341   use CGI::Session;
1342   my $session = get_session($sessionID);
1343
1344 Given a session ID, retrieve the CGI::Session object used to store
1345 the session's state.  The session object can be used to store
1346 data that needs to be accessed by different scripts during a
1347 user's session.
1348
1349 If the C<$sessionID> parameter is an empty string, a new session
1350 will be created.
1351
1352 =cut
1353
1354 sub get_session {
1355     my $sessionID = shift;
1356     my $storage_method = C4::Context->preference('SessionStorage');
1357     my $dbh = C4::Context->dbh;
1358     my $session;
1359     if ($storage_method eq 'mysql'){
1360         $session = new CGI::Session("driver:MySQL;serializer:yaml;id:md5", $sessionID, {Handle=>$dbh});
1361     }
1362     elsif ($storage_method eq 'Pg') {
1363         $session = new CGI::Session("driver:PostgreSQL;serializer:yaml;id:md5", $sessionID, {Handle=>$dbh});
1364     }
1365     else {
1366         # catch all defaults to tmp should work on all systems
1367         $session = new CGI::Session("driver:File;serializer:yaml;id:md5", $sessionID, {Directory=>'/tmp'});
1368     }
1369     return $session;
1370 }
1371
1372 sub checkpw {
1373
1374     my ( $dbh, $userid, $password, $query ) = @_;
1375     if ($ldap) {
1376         $debug and print "## checkpw - checking LDAP\n";
1377         my ($retval,$retcard) = checkpw_ldap(@_);    # EXTERNAL AUTH
1378         ($retval) and return ($retval,$retcard);
1379     }
1380
1381     if ($cas && $query->param('ticket')) {
1382         $debug and print STDERR "## checkpw - checking CAS\n";
1383         # In case of a CAS authentication, we use the ticket instead of the password
1384         my $ticket = $query->param('ticket');
1385         my ($retval,$retcard,$retuserid) = checkpw_cas($dbh, $ticket, $query);    # EXTERNAL AUTH
1386         ($retval) and return ($retval,$retcard,$retuserid);
1387         return 0;
1388     }
1389
1390     # INTERNAL AUTH
1391     my $sth =
1392       $dbh->prepare(
1393 "select password,cardnumber,borrowernumber,userid,firstname,surname,branchcode,flags from borrowers where userid=?"
1394       );
1395     $sth->execute($userid);
1396     if ( $sth->rows ) {
1397         my ( $md5password, $cardnumber, $borrowernumber, $userid, $firstname,
1398             $surname, $branchcode, $flags )
1399           = $sth->fetchrow;
1400         if ( md5_base64($password) eq $md5password and $md5password ne "!") {
1401
1402             C4::Context->set_userenv( "$borrowernumber", $userid, $cardnumber,
1403                 $firstname, $surname, $branchcode, $flags );
1404             return 1, $cardnumber;
1405         }
1406     }
1407     $sth =
1408       $dbh->prepare(
1409 "select password,cardnumber,borrowernumber,userid, firstname,surname,branchcode,flags from borrowers where cardnumber=?"
1410       );
1411     $sth->execute($userid);
1412     if ( $sth->rows ) {
1413         my ( $md5password, $cardnumber, $borrowernumber, $userid, $firstname,
1414             $surname, $branchcode, $flags )
1415           = $sth->fetchrow;
1416         if ( md5_base64($password) eq $md5password ) {
1417
1418             C4::Context->set_userenv( $borrowernumber, $userid, $cardnumber,
1419                 $firstname, $surname, $branchcode, $flags );
1420             return 1, $userid;
1421         }
1422     }
1423     if (   $userid && $userid eq C4::Context->config('user')
1424         && "$password" eq C4::Context->config('pass') )
1425     {
1426
1427 # Koha superuser account
1428 #     C4::Context->set_userenv(0,0,C4::Context->config('user'),C4::Context->config('user'),C4::Context->config('user'),"",1);
1429         return 2;
1430     }
1431     if (   $userid && $userid eq 'demo'
1432         && "$password" eq 'demo'
1433         && C4::Context->config('demo') )
1434     {
1435
1436 # DEMO => the demo user is allowed to do everything (if demo set to 1 in koha.conf
1437 # some features won't be effective : modify systempref, modify MARC structure,
1438         return 2;
1439     }
1440     return 0;
1441 }
1442
1443 =head2 getuserflags
1444
1445     my $authflags = getuserflags($flags, $userid, [$dbh]);
1446
1447 Translates integer flags into permissions strings hash.
1448
1449 C<$flags> is the integer userflags value ( borrowers.userflags )
1450 C<$userid> is the members.userid, used for building subpermissions
1451 C<$authflags> is a hashref of permissions
1452
1453 =cut
1454
1455 sub getuserflags {
1456     my $flags   = shift;
1457     my $userid  = shift;
1458     my $dbh     = @_ ? shift : C4::Context->dbh;
1459     my $userflags;
1460     $flags = 0 unless $flags;
1461     my $sth = $dbh->prepare("SELECT bit, flag, defaulton FROM userflags");
1462     $sth->execute;
1463
1464     while ( my ( $bit, $flag, $defaulton ) = $sth->fetchrow ) {
1465         if ( ( $flags & ( 2**$bit ) ) || $defaulton ) {
1466             $userflags->{$flag} = 1;
1467         }
1468         else {
1469             $userflags->{$flag} = 0;
1470         }
1471     }
1472
1473     # get subpermissions and merge with top-level permissions
1474     my $user_subperms = get_user_subpermissions($userid);
1475     foreach my $module (keys %$user_subperms) {
1476         next if $userflags->{$module} == 1; # user already has permission for everything in this module
1477         $userflags->{$module} = $user_subperms->{$module};
1478     }
1479
1480     return $userflags;
1481 }
1482
1483 =head2 get_user_subpermissions
1484
1485   $user_perm_hashref = get_user_subpermissions($userid);
1486
1487 Given the userid (note, not the borrowernumber) of a staff user,
1488 return a hashref of hashrefs of the specific subpermissions
1489 accorded to the user.  An example return is
1490
1491  {
1492     tools => {
1493         export_catalog => 1,
1494         import_patrons => 1,
1495     }
1496  }
1497
1498 The top-level hash-key is a module or function code from
1499 userflags.flag, while the second-level key is a code
1500 from permissions.
1501
1502 The results of this function do not give a complete picture
1503 of the functions that a staff user can access; it is also
1504 necessary to check borrowers.flags.
1505
1506 =cut
1507
1508 sub get_user_subpermissions {
1509     my $userid = shift;
1510
1511     my $dbh = C4::Context->dbh;
1512     my $sth = $dbh->prepare("SELECT flag, user_permissions.code
1513                              FROM user_permissions
1514                              JOIN permissions USING (module_bit, code)
1515                              JOIN userflags ON (module_bit = bit)
1516                              JOIN borrowers USING (borrowernumber)
1517                              WHERE userid = ?");
1518     $sth->execute($userid);
1519
1520     my $user_perms = {};
1521     while (my $perm = $sth->fetchrow_hashref) {
1522         $user_perms->{$perm->{'flag'}}->{$perm->{'code'}} = 1;
1523     }
1524     return $user_perms;
1525 }
1526
1527 =head2 get_all_subpermissions
1528
1529   my $perm_hashref = get_all_subpermissions();
1530
1531 Returns a hashref of hashrefs defining all specific
1532 permissions currently defined.  The return value
1533 has the same structure as that of C<get_user_subpermissions>,
1534 except that the innermost hash value is the description
1535 of the subpermission.
1536
1537 =cut
1538
1539 sub get_all_subpermissions {
1540     my $dbh = C4::Context->dbh;
1541     my $sth = $dbh->prepare("SELECT flag, code, description
1542                              FROM permissions
1543                              JOIN userflags ON (module_bit = bit)");
1544     $sth->execute();
1545
1546     my $all_perms = {};
1547     while (my $perm = $sth->fetchrow_hashref) {
1548         $all_perms->{$perm->{'flag'}}->{$perm->{'code'}} = $perm->{'description'};
1549     }
1550     return $all_perms;
1551 }
1552
1553 =head2 haspermission
1554
1555   $flags = ($userid, $flagsrequired);
1556
1557 C<$userid> the userid of the member
1558 C<$flags> is a hashref of required flags like C<$borrower-&lt;{authflags}> 
1559
1560 Returns member's flags or 0 if a permission is not met.
1561
1562 =cut
1563
1564 sub haspermission {
1565     my ($userid, $flagsrequired) = @_;
1566     my $sth = C4::Context->dbh->prepare("SELECT flags FROM borrowers WHERE userid=?");
1567     $sth->execute($userid);
1568     my $flags = getuserflags( $sth->fetchrow(), $userid );
1569     if ( $userid eq C4::Context->config('user') ) {
1570         # Super User Account from /etc/koha.conf
1571         $flags->{'superlibrarian'} = 1;
1572     }
1573     elsif ( $userid eq 'demo' && C4::Context->config('demo') ) {
1574         # Demo user that can do "anything" (demo=1 in /etc/koha.conf)
1575         $flags->{'superlibrarian'} = 1;
1576     }
1577     return $flags if $flags->{superlibrarian};
1578     foreach my $module ( keys %$flagsrequired ) {
1579         my $subperm = $flagsrequired->{$module};
1580         if ($subperm eq '*') {
1581             return 0 unless ( $flags->{$module} == 1 or ref($flags->{$module}) );
1582         } else {
1583             return 0 unless ( $flags->{$module} == 1 or
1584                                 ( ref($flags->{$module}) and
1585                                   exists $flags->{$module}->{$subperm} and
1586                                   $flags->{$module}->{$subperm} == 1
1587                                 )
1588                             );
1589         }
1590     }
1591     return $flags;
1592     #FIXME - This fcn should return the failed permission so a suitable error msg can be delivered.
1593 }
1594
1595
1596 sub getborrowernumber {
1597     my ($userid) = @_;
1598     my $userenv = C4::Context->userenv;
1599     if ( defined( $userenv ) && ref( $userenv ) eq 'HASH' && $userenv->{number} ) {
1600         return $userenv->{number};
1601     }
1602     my $dbh = C4::Context->dbh;
1603     for my $field ( 'userid', 'cardnumber' ) {
1604         my $sth =
1605           $dbh->prepare("select borrowernumber from borrowers where $field=?");
1606         $sth->execute($userid);
1607         if ( $sth->rows ) {
1608             my ($bnumber) = $sth->fetchrow;
1609             return $bnumber;
1610         }
1611     }
1612     return 0;
1613 }
1614
1615 END { }    # module clean-up code here (global destructor)
1616 1;
1617 __END__
1618
1619 =head1 SEE ALSO
1620
1621 CGI(3)
1622
1623 C4::Output(3)
1624
1625 Digest::MD5(3)
1626
1627 =cut