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