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