Auth.pm 89.2 KB
Newer Older
1 2
package C4::Auth;

3 4 5 6
# Copyright 2000-2002 Katipo Communications
#
# This file is part of Koha.
#
7 8 9 10
# Koha is free software; you can redistribute it and/or modify it
# under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 3 of the License, or
# (at your option) any later version.
11
#
12 13 14 15
# Koha is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
16
#
17 18
# You should have received a copy of the GNU General Public License
# along with Koha; if not, see <http://www.gnu.org/licenses>.
19

20
use strict;
21
use warnings;
22
use Digest::MD5 qw(md5_base64);
23
use File::Spec;
24
use JSON qw/encode_json/;
25
use URI::Escape;
26 27
use CGI::Session;

28
require Exporter;
29
use C4::Context;
30
use C4::Templates;    # to get the template
31
use C4::Languages;
32
use C4::Search::History;
33
use Koha;
34
use Koha::Caches;
35
use Koha::AuthUtils qw(get_script_name hash_password);
36
use Koha::Library::Groups;
37
use Koha::Libraries;
38
use Koha::Patrons;
39
use POSIX qw/strftime/;
40
use List::MoreUtils qw/ any /;
joubu's avatar
joubu committed
41
use Encode qw( encode is_utf8);
tipaul's avatar
tipaul committed
42

Paul POULAIN's avatar
Paul POULAIN committed
43
# use utf8;
44
use vars qw(@ISA @EXPORT @EXPORT_OK %EXPORT_TAGS $debug $ldap $cas $caslogout $shib $shib_login);
45 46

BEGIN {
47
    sub psgi_env { any { /^psgi\./ } keys %ENV }
48

49
    sub safe_exit {
50 51
        if   (psgi_env) { die 'psgi:exit' }
        else            { exit }
52
    }
53 54 55 56 57 58 59

    $debug     = $ENV{DEBUG};
    @ISA       = qw(Exporter);
    @EXPORT    = qw(&checkauth &get_template_and_user &haspermission &get_user_subpermissions);
    @EXPORT_OK = qw(&check_api_auth &get_session &check_cookie_auth &checkpw &checkpw_internal &checkpw_hash
      &get_all_subpermissions &get_user_subpermissions
    );
60
    %EXPORT_TAGS = ( EditPermissions => [qw(get_all_subpermissions get_user_subpermissions)] );
61 62 63 64 65 66
    $ldap      = C4::Context->config('useldapserver') || 0;
    $cas       = C4::Context->preference('casAuthentication');
    $shib      = C4::Context->config('useshibboleth') || 0;
    $caslogout = C4::Context->preference('casLogout');
    require C4::Auth_with_cas;    # no import

67
    if ($ldap) {
68 69
        require C4::Auth_with_ldap;
        import C4::Auth_with_ldap qw(checkpw_ldap);
70
    }
71
    if ($shib) {
72
        require C4::Auth_with_shibboleth;
73
        import C4::Auth_with_shibboleth
74 75 76 77
          qw(shib_ok checkpw_shib logout_shib login_shib_url get_login_shib);

        # Check for good config
        if ( shib_ok() ) {
78

79 80 81
            # Get shibboleth login attribute
            $shib_login = get_login_shib();
        }
82

83 84 85 86
        # Bad config, disable shibboleth
        else {
            $shib = 0;
        }
87
    }
88
    if ($cas) {
89
        import C4::Auth_with_cas qw(check_api_auth_cas checkpw_cas login_cas logout_cas login_cas_url logout_if_required);
90
    }
91

92
}
93

arensb's avatar
arensb committed
94 95 96 97 98 99
=head1 NAME

C4::Auth - Authenticates Koha users

=head1 SYNOPSIS

100
  use CGI qw ( -utf8 );
arensb's avatar
arensb committed
101
  use C4::Auth;
102
  use C4::Output;
arensb's avatar
arensb committed
103

finlayt's avatar
finlayt committed
104 105
  my $query = new CGI;

106
  my ($template, $borrowernumber, $cookie)
107 108
    = get_template_and_user(
        {
109
            template_name   => "opac-main.tt",
110
            query           => $query,
111
      type            => "opac",
112
      authnotrequired => 0,
113
      flagsrequired   => { catalogue => '*', tools => 'import_patrons' },
114 115
  }
    );
finlayt's avatar
finlayt committed
116

117
  output_html_with_http_headers $query, $cookie, $template->output;
arensb's avatar
arensb committed
118 119 120

=head1 DESCRIPTION

121 122 123 124
The main function of this module is to provide
authentification. However the get_template_and_user function has
been provided so that a users login information is passed along
automatically. This gets loaded into the template.
arensb's avatar
arensb committed
125 126 127

=head1 FUNCTIONS

128 129 130 131 132
=head2 get_template_and_user

 my ($template, $borrowernumber, $cookie)
     = get_template_and_user(
       {
133
         template_name   => "opac-main.tt",
134 135
         query           => $query,
         type            => "opac",
136
         authnotrequired => 0,
137
         flagsrequired   => { catalogue => '*', tools => 'import_patrons' },
138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153
       }
     );

This call passes the C<query>, C<flagsrequired> and C<authnotrequired>
to C<&checkauth> (in this module) to perform authentification.
See C<&checkauth> for an explanation of these parameters.

The C<template_name> is then used to find the correct template for
the page. The authenticated users details are loaded onto the
template in the HTML::Template LOOP variable C<USER_INFO>. Also the
C<sessionID> is passed to the template. This can be used in templates
if cookies are disabled. It needs to be put as and input to every
authenticated page.

More information on the C<gettemplate> sub can be found in the
Output.pm module.
finlayt's avatar
finlayt committed
154 155 156

=cut

157
sub get_template_and_user {
158

159
    my $in = shift;
160
    my ( $user, $cookie, $sessionID, $flags );
161

162
    C4::Context->interface( $in->{type} );
163

164
    $in->{'authnotrequired'} ||= 0;
165 166

    # the following call includes a bad template check; might croak
167 168 169 170 171 172
    my $template = C4::Templates::gettemplate(
        $in->{'template_name'},
        $in->{'type'},
        $in->{'query'},
    );

173
    if ( $in->{'template_name'} !~ m/maintenance/ ) {
174
        ( $user, $cookie, $sessionID, $flags ) = checkauth(
175 176 177 178 179 180
            $in->{'query'},
            $in->{'authnotrequired'},
            $in->{'flagsrequired'},
            $in->{'type'}
        );
    }
tipaul's avatar
tipaul committed
181

182
    if ( $in->{type} eq 'opac' && $user ) {
183 184 185 186 187 188 189 190 191 192 193 194 195 196 197
        my $kick_out;

        if (
# If the user logged in is the SCO user and they try to go out of the SCO module,
# log the user out removing the CGISESSID cookie
               $in->{template_name} !~ m|sco/|
            && C4::Context->preference('AutoSelfCheckID')
            && $user eq C4::Context->preference('AutoSelfCheckID')
          )
        {
            $kick_out = 1;
        }
        elsif (
# If the user logged in is the SCI user and they try to go out of the SCI module,
# kick them out unless it is SCO with a valid permission
198
# or they are a superlibrarian
199 200 201 202 203 204
               $in->{template_name} !~ m|sci/|
            && haspermission( $user, { self_check => 'self_checkin_module' } )
            && !(
                $in->{template_name} =~ m|sco/| && haspermission(
                    $user, { self_check => 'self_checkout_module' }
                )
205 206
            )
            && $flags && $flags->{superlibrarian} != 1
207 208 209
          )
        {
            $kick_out = 1;
210 211
        }

212 213 214 215
        if ($kick_out) {
            $template = C4::Templates::gettemplate( 'opac-auth.tt', 'opac',
                $in->{query} );
            $cookie = $in->{query}->cookie(
216 217 218 219 220 221 222 223 224 225
                -name     => 'CGISESSID',
                -value    => '',
                -expires  => '',
                -HttpOnly => 1,
            );

            $template->param(
                loginprompt => 1,
                script_name => get_script_name(),
            );
226

227
            print $in->{query}->header(
228 229
                {
                    type              => 'text/html',
230 231 232 233 234
                    charset           => 'utf-8',
                    cookie            => $cookie,
                    'X-Frame-Options' => 'SAMEORIGIN'
                }
              ),
235
              $template->output;
236 237 238 239
            safe_exit;
        }
    }

tipaul's avatar
tipaul committed
240
    my $borrowernumber;
241
    if ($user) {
242

243 244 245
        # It's possible for $user to be the borrowernumber if they don't have a
        # userid defined (and are logging in through some other method, such
        # as SSL certs against an email address)
246
        my $patron;
247
        $borrowernumber = getborrowernumber($user) if defined($user);
248
        if ( !defined($borrowernumber) && defined($user) ) {
249 250
            $patron = Koha::Patrons->find( $user );
            if ($patron) {
251
                $borrowernumber = $user;
252

253 254
                # A bit of a hack, but I don't know there's a nicer way
                # to do it.
255
                $user = $patron->firstname . ' ' . $patron->surname;
256
            }
257
        } else {
258 259
            $patron = Koha::Patrons->find( $borrowernumber );
            # FIXME What to do if $patron does not exist?
260
        }
261

262
        # user info
263 264 265
        $template->param( loggedinusername   => $user ); # FIXME Should be replaced with something like patron-title.inc
        $template->param( loggedinusernumber => $borrowernumber ); # FIXME Should be replaced with logged_in_user.borrowernumber
        $template->param( logged_in_user     => $patron );
266
        $template->param( sessionID          => $sessionID );
267

268
        if ( $in->{'type'} eq 'opac' ) {
269 270 271 272 273 274 275 276 277 278 279 280
            require Koha::Virtualshelves;
            my $some_private_shelves = Koha::Virtualshelves->get_some_shelves(
                {
                    borrowernumber => $borrowernumber,
                    category       => 1,
                }
            );
            my $some_public_shelves = Koha::Virtualshelves->get_some_shelves(
                {
                    category       => 2,
                }
            );
281
            $template->param(
282 283
                some_private_shelves => $some_private_shelves,
                some_public_shelves  => $some_public_shelves,
284 285
            );
        }
tipaul's avatar
tipaul committed
286

joubu's avatar
joubu committed
287
        $template->param( "USER_INFO" => $patron->unblessed ) if $borrowernumber != 0;
288

289
        my $all_perms = get_all_subpermissions();
tipaul's avatar
tipaul committed
290

291
        my @flagroots = qw(circulate catalogue parameters borrowers permissions reserveforothers borrow
292
          editcatalogue updatecharges management tools editauthorities serials reports acquisition clubs);
293

tipaul's avatar
tipaul committed
294 295 296
        # We are going to use the $flags returned by checkauth
        # to create the template's parameters that will indicate
        # which menus the user can access.
297
        if ( $flags && $flags->{superlibrarian} == 1 ) {
tipaul's avatar
tipaul committed
298 299 300 301
            $template->param( CAN_user_circulate        => 1 );
            $template->param( CAN_user_catalogue        => 1 );
            $template->param( CAN_user_parameters       => 1 );
            $template->param( CAN_user_borrowers        => 1 );
302
            $template->param( CAN_user_permissions      => 1 );
tipaul's avatar
tipaul committed
303 304
            $template->param( CAN_user_reserveforothers => 1 );
            $template->param( CAN_user_editcatalogue    => 1 );
305
            $template->param( CAN_user_updatecharges    => 1 );
tipaul's avatar
tipaul committed
306 307
            $template->param( CAN_user_acquisition      => 1 );
            $template->param( CAN_user_management       => 1 );
308
            $template->param( CAN_user_tools            => 1 );
tipaul's avatar
tipaul committed
309 310 311
            $template->param( CAN_user_editauthorities  => 1 );
            $template->param( CAN_user_serials          => 1 );
            $template->param( CAN_user_reports          => 1 );
312
            $template->param( CAN_user_staffaccess      => 1 );
313
            $template->param( CAN_user_plugins          => 1 );
Kyle M Hall's avatar
Kyle M Hall committed
314
            $template->param( CAN_user_coursereserves   => 1 );
315
            $template->param( CAN_user_clubs            => 1 );
316

317
            foreach my $module ( keys %$all_perms ) {
318
                foreach my $subperm ( keys %{ $all_perms->{$module} } ) {
319 320 321
                    $template->param( "CAN_user_${module}_${subperm}" => 1 );
                }
            }
tipaul's avatar
tipaul committed
322 323
        }

324 325
        if ($flags) {
            foreach my $module ( keys %$all_perms ) {
326
                if ( defined($flags->{$module}) && $flags->{$module} == 1 ) {
327
                    foreach my $subperm ( keys %{ $all_perms->{$module} } ) {
328 329
                        $template->param( "CAN_user_${module}_${subperm}" => 1 );
                    }
330 331
                } elsif ( ref( $flags->{$module} ) ) {
                    foreach my $subperm ( keys %{ $flags->{$module} } ) {
332 333
                        $template->param( "CAN_user_${module}_${subperm}" => 1 );
                    }
334 335
                }
            }
336 337
        }

338
        if ($flags) {
339 340
            foreach my $module ( keys %$flags ) {
                if ( $flags->{$module} == 1 or ref( $flags->{$module} ) ) {
341
                    $template->param( "CAN_user_$module" => 1 );
342
                    if ( $module eq "parameters" ) {
343 344 345 346
                        $template->param( CAN_user_management => 1 );
                    }
                }
            }
347
        }
348

349 350
        # Logged-in opac search history
        # If the requested template is an opac one and opac search history is enabled
351 352
        if ( $in->{type} eq 'opac' && C4::Context->preference('EnableOpacSearchHistory') ) {
            my $dbh   = C4::Context->dbh;
353
            my $query = "SELECT COUNT(*) FROM search_history WHERE userid=?";
354
            my $sth   = $dbh->prepare($query);
355 356 357
            $sth->execute($borrowernumber);

            # If at least one search has already been performed
358 359
            if ( $sth->fetchrow_array > 0 ) {

360 361
                # We show the link in opac
                $template->param( EnableOpacSearchHistory => 1 );
362
            }
363
            if (C4::Context->preference('LoadSearchHistoryToTheFirstLoggedUser'))
364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388
            {
                # And if there are searches performed when the user was not logged in,
                # we add them to the logged-in search history
                my @recentSearches = C4::Search::History::get_from_session( { cgi => $in->{'query'} } );
                if (@recentSearches) {
                    my $dbh   = C4::Context->dbh;
                    my $query = q{
                        INSERT INTO search_history(userid, sessionid, query_desc, query_cgi, type,  total, time )
                        VALUES (?, ?, ?, ?, ?, ?, ?)
                    };
                    my $sth = $dbh->prepare($query);
                    $sth->execute( $borrowernumber,
                        $in->{query}->cookie("CGISESSID"),
                        $_->{query_desc},
                        $_->{query_cgi},
                        $_->{type} || 'biblio',
                        $_->{total},
                        $_->{time},
                    ) foreach @recentSearches;

                    # clear out the search history from the session now that
                    # we've saved it to the database
                 }
              }
              C4::Search::History::set_to_session( { cgi => $in->{'query'}, search_history => [] } );
389

390 391
        } elsif ( $in->{type} eq 'intranet' and C4::Context->preference('EnableSearchHistory') ) {
            $template->param( EnableSearchHistory => 1 );
392
        }
tipaul's avatar
tipaul committed
393
    }
394
    else {    # if this is an anonymous session, setup to display public lists...
395

396
        # If shibboleth is enabled, and we're in an anonymous session, we should allow
397
        # the user to attempt login via shibboleth.
398 399 400
        if ($shib) {
            $template->param( shibbolethAuthentication => $shib,
                shibbolethLoginUrl => login_shib_url( $in->{'query'} ),
401
            );
402

403 404 405
            # If shibboleth is enabled and we have a shibboleth login attribute,
            # but we are in an anonymous session, then we clearly have an invalid
            # shibboleth koha account.
406 407
            if ($shib_login) {
                $template->param( invalidShibLogin => '1' );
408 409 410
            }
        }

411 412
        $template->param( sessionID => $sessionID );

413
        if ( $in->{'type'} eq 'opac' ){
414 415 416 417 418 419
            require Koha::Virtualshelves;
            my $some_public_shelves = Koha::Virtualshelves->get_some_shelves(
                {
                    category       => 2,
                }
            );
420
            $template->param(
421
                some_public_shelves  => $some_public_shelves,
422 423
            );
        }
424
    }
425 426 427 428 429

    # Anonymous opac search history
    # If opac search history is enabled and at least one search has already been performed
    if ( C4::Context->preference('EnableOpacSearchHistory') ) {
        my @recentSearches = C4::Search::History::get_from_session( { cgi => $in->{'query'} } );
430
        if (@recentSearches) {
431
            $template->param( EnableOpacSearchHistory => 1 );
432
        }
433
    }
434

435
    if ( C4::Context->preference('dateformat') ) {
436
        $template->param( dateformat => C4::Context->preference('dateformat') );
437 438
    }

439
    $template->param(auth_forwarded_hash => scalar $in->{'query'}->param('auth_forwarded_hash'));
440

441
    # these template parameters are set the same regardless of $in->{'type'}
442 443 444 445

    # Set the using_https variable for templates
    # FIXME Under Plack the CGI->https method always returns 'OFF'
    my $https = $in->{query}->https();
446
    my $using_https = ( defined $https and $https ne 'OFF' ) ? 1 : 0;
447

448 449
    my $minPasswordLength = C4::Context->preference('minPasswordLength');
    $minPasswordLength = 3 if not $minPasswordLength or $minPasswordLength < 3;
450
    $template->param(
451 452 453 454 455 456 457 458 459 460 461 462 463
        "BiblioDefaultView" . C4::Context->preference("BiblioDefaultView") => 1,
        EnhancedMessagingPreferences                                       => C4::Context->preference('EnhancedMessagingPreferences'),
        GoogleJackets                                                      => C4::Context->preference("GoogleJackets"),
        OpenLibraryCovers                                                  => C4::Context->preference("OpenLibraryCovers"),
        KohaAdminEmailAddress                                              => "" . C4::Context->preference("KohaAdminEmailAddress"),
        LoginBranchcode => ( C4::Context->userenv ? C4::Context->userenv->{"branch"}    : undef ),
        LoginFirstname  => ( C4::Context->userenv ? C4::Context->userenv->{"firstname"} : "Bel" ),
        LoginSurname    => C4::Context->userenv ? C4::Context->userenv->{"surname"}      : "Inconnu",
        emailaddress    => C4::Context->userenv ? C4::Context->userenv->{"emailaddress"} : undef,
        TagsEnabled     => C4::Context->preference("TagsEnabled"),
        hide_marc       => C4::Context->preference("hide_marc"),
        item_level_itypes  => C4::Context->preference('item-level_itypes'),
        patronimages       => C4::Context->preference("patronimages"),
464
        singleBranchMode   => ( Koha::Libraries->search->count == 1 ),
465 466 467 468 469
        XSLTDetailsDisplay => C4::Context->preference("XSLTDetailsDisplay"),
        XSLTResultsDisplay => C4::Context->preference("XSLTResultsDisplay"),
        using_https        => $using_https,
        noItemTypeImages   => C4::Context->preference("noItemTypeImages"),
        marcflavour        => C4::Context->preference("marcflavour"),
470
        OPACBaseURL        => C4::Context->preference('OPACBaseURL'),
471
        minPasswordLength  => $minPasswordLength,
472
    );
tipaul's avatar
tipaul committed
473
    if ( $in->{'type'} eq "intranet" ) {
474
        $template->param(
475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491
            AmazonCoverImages                                                          => C4::Context->preference("AmazonCoverImages"),
            AutoLocation                                                               => C4::Context->preference("AutoLocation"),
            "BiblioDefaultView" . C4::Context->preference("IntranetBiblioDefaultView") => 1,
            CircAutocompl                                                              => C4::Context->preference("CircAutocompl"),
            FRBRizeEditions                                                            => C4::Context->preference("FRBRizeEditions"),
            IndependentBranches                                                        => C4::Context->preference("IndependentBranches"),
            IntranetNav                                                                => C4::Context->preference("IntranetNav"),
            IntranetmainUserblock                                                      => C4::Context->preference("IntranetmainUserblock"),
            LibraryName                                                                => C4::Context->preference("LibraryName"),
            LoginBranchname                                                            => ( C4::Context->userenv ? C4::Context->userenv->{"branchname"} : undef ),
            advancedMARCEditor                                                         => C4::Context->preference("advancedMARCEditor"),
            canreservefromotherbranches                                                => C4::Context->preference('canreservefromotherbranches'),
            intranetcolorstylesheet                                                    => C4::Context->preference("intranetcolorstylesheet"),
            IntranetFavicon                                                            => C4::Context->preference("IntranetFavicon"),
            intranetreadinghistory                                                     => C4::Context->preference("intranetreadinghistory"),
            intranetstylesheet                                                         => C4::Context->preference("intranetstylesheet"),
            IntranetUserCSS                                                            => C4::Context->preference("IntranetUserCSS"),
492
            IntranetUserJS                                                             => C4::Context->preference("IntranetUserJS"),
493 494 495 496 497 498 499 500 501 502 503
            intranetbookbag                                                            => C4::Context->preference("intranetbookbag"),
            suggestion                                                                 => C4::Context->preference("suggestion"),
            virtualshelves                                                             => C4::Context->preference("virtualshelves"),
            StaffSerialIssueDisplayCount                                               => C4::Context->preference("StaffSerialIssueDisplayCount"),
            EasyAnalyticalRecords                                                      => C4::Context->preference('EasyAnalyticalRecords'),
            LocalCoverImages                                                           => C4::Context->preference('LocalCoverImages'),
            OPACLocalCoverImages                                                       => C4::Context->preference('OPACLocalCoverImages'),
            AllowMultipleCovers                                                        => C4::Context->preference('AllowMultipleCovers'),
            EnableBorrowerFiles                                                        => C4::Context->preference('EnableBorrowerFiles'),
            UseKohaPlugins                                                             => C4::Context->preference('UseKohaPlugins'),
            UseCourseReserves                                                          => C4::Context->preference("UseCourseReserves"),
504
            useDischarge                                                               => C4::Context->preference('useDischarge')
505
        );
tipaul's avatar
tipaul committed
506 507
    }
    else {
508
        warn "template type should be OPAC, here it is=[" . $in->{'type'} . "]" unless ( $in->{'type'} eq 'opac' );
509

510
        #TODO : replace LibraryName syspref with 'system name', and remove this html processing
tipaul's avatar
tipaul committed
511 512 513
        my $LibraryNameTitle = C4::Context->preference("LibraryName");
        $LibraryNameTitle =~ s/<(?:\/?)(?:br|p)\s*(?:\/?)>/ /sgi;
        $LibraryNameTitle =~ s/<(?:[^<>'"]|'(?:[^']*)'|"(?:[^"]*)")*>//sg;
514

515 516 517
        # clean up the busc param in the session
        # if the page is not opac-detail and not the "add to list" page
        # and not the "edit comments" page
518
        if ( C4::Context->preference("OpacBrowseResults")
519 520
            && $in->{'template_name'} =~ /opac-(.+)\.(?:tt|tmpl)$/ ) {
            my $pagename = $1;
521
            unless ( $pagename =~ /^(?:MARC|ISBD)?detail$/
522 523
                or $pagename =~ /^addbybiblionumber$/
                or $pagename =~ /^review$/ ) {
524 525
                my $sessionSearch = get_session( $sessionID || $in->{'query'}->cookie("CGISESSID") );
                $sessionSearch->clear( ["busc"] ) if ( $sessionSearch->param("busc") );
526
            }
527
        }
528

529
        # variables passed from CGI: opac_css_override and opac_search_limits.
530
        my $opac_search_limit   = $ENV{'OPAC_SEARCH_LIMIT'};
531
        my $opac_limit_override = $ENV{'OPAC_LIMIT_OVERRIDE'};
532
        my $opac_name           = '';
533
        if (
534 535 536 537 538
            ( $opac_limit_override && $opac_search_limit && $opac_search_limit =~ /branch:(\w+)/ ) ||
            ( $in->{'query'}->param('limit') && $in->{'query'}->param('limit') =~ /branch:(\w+)/ ) ||
            ( $in->{'query'}->param('multibranchlimit') && $in->{'query'}->param('multibranchlimit') =~ /multibranchlimit-(\w+)/ )
          ) {
            $opac_name = $1;    # opac_search_limit is a branch, so we use it.
539 540
        } elsif ( $in->{'query'}->param('multibranchlimit') ) {
            $opac_name = $in->{'query'}->param('multibranchlimit');
541
        } elsif ( C4::Context->preference("SearchMyLibraryFirst") && C4::Context->userenv && C4::Context->userenv->{'branch'} ) {
542
            $opac_name = C4::Context->userenv->{'branch'};
543
        }
544

545
        my @search_groups = Koha::Library::Groups->get_search_groups({ interface => 'opac' });
546
        $template->param(
547
            OpacAdditionalStylesheet                   => C4::Context->preference("OpacAdditionalStylesheet"),
548
            AnonSuggestions                       => "" . C4::Context->preference("AnonSuggestions"),
549
            LibrarySearchGroups                   => \@search_groups,
550
            opac_name                             => $opac_name,
551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586
            LibraryName                           => "" . C4::Context->preference("LibraryName"),
            LibraryNameTitle                      => "" . $LibraryNameTitle,
            LoginBranchname                       => C4::Context->userenv ? C4::Context->userenv->{"branchname"} : "",
            OPACAmazonCoverImages                 => C4::Context->preference("OPACAmazonCoverImages"),
            OPACFRBRizeEditions                   => C4::Context->preference("OPACFRBRizeEditions"),
            OpacHighlightedWords                  => C4::Context->preference("OpacHighlightedWords"),
            OPACShelfBrowser                      => "" . C4::Context->preference("OPACShelfBrowser"),
            OPACURLOpenInNewWindow                => "" . C4::Context->preference("OPACURLOpenInNewWindow"),
            OPACUserCSS                           => "" . C4::Context->preference("OPACUserCSS"),
            OpacAuthorities                       => C4::Context->preference("OpacAuthorities"),
            opac_css_override                     => $ENV{'OPAC_CSS_OVERRIDE'},
            opac_search_limit                     => $opac_search_limit,
            opac_limit_override                   => $opac_limit_override,
            OpacBrowser                           => C4::Context->preference("OpacBrowser"),
            OpacCloud                             => C4::Context->preference("OpacCloud"),
            OpacKohaUrl                           => C4::Context->preference("OpacKohaUrl"),
            OpacMainUserBlock                     => "" . C4::Context->preference("OpacMainUserBlock"),
            OpacNav                               => "" . C4::Context->preference("OpacNav"),
            OpacNavRight                          => "" . C4::Context->preference("OpacNavRight"),
            OpacNavBottom                         => "" . C4::Context->preference("OpacNavBottom"),
            OpacPasswordChange                    => C4::Context->preference("OpacPasswordChange"),
            OPACPatronDetails                     => C4::Context->preference("OPACPatronDetails"),
            OPACPrivacy                           => C4::Context->preference("OPACPrivacy"),
            OPACFinesTab                          => C4::Context->preference("OPACFinesTab"),
            OpacTopissue                          => C4::Context->preference("OpacTopissue"),
            RequestOnOpac                         => C4::Context->preference("RequestOnOpac"),
            'Version'                             => C4::Context->preference('Version'),
            hidelostitems                         => C4::Context->preference("hidelostitems"),
            mylibraryfirst                        => ( C4::Context->preference("SearchMyLibraryFirst") && C4::Context->userenv ) ? C4::Context->userenv->{'branch'} : '',
            opaclayoutstylesheet                  => "" . C4::Context->preference("opaclayoutstylesheet"),
            opacbookbag                           => "" . C4::Context->preference("opacbookbag"),
            opaccredits                           => "" . C4::Context->preference("opaccredits"),
            OpacFavicon                           => C4::Context->preference("OpacFavicon"),
            opacheader                            => "" . C4::Context->preference("opacheader"),
            opaclanguagesdisplay                  => "" . C4::Context->preference("opaclanguagesdisplay"),
            opacreadinghistory                    => C4::Context->preference("opacreadinghistory"),
587
            OPACUserJS                            => C4::Context->preference("OPACUserJS"),
588
            opacuserlogin                         => "" . C4::Context->preference("opacuserlogin"),
589
            OpenLibrarySearch                     => C4::Context->preference("OpenLibrarySearch"),
590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610
            ShowReviewer                          => C4::Context->preference("ShowReviewer"),
            ShowReviewerPhoto                     => C4::Context->preference("ShowReviewerPhoto"),
            suggestion                            => "" . C4::Context->preference("suggestion"),
            virtualshelves                        => "" . C4::Context->preference("virtualshelves"),
            OPACSerialIssueDisplayCount           => C4::Context->preference("OPACSerialIssueDisplayCount"),
            OPACXSLTDetailsDisplay                => C4::Context->preference("OPACXSLTDetailsDisplay"),
            OPACXSLTResultsDisplay                => C4::Context->preference("OPACXSLTResultsDisplay"),
            SyndeticsClientCode                   => C4::Context->preference("SyndeticsClientCode"),
            SyndeticsEnabled                      => C4::Context->preference("SyndeticsEnabled"),
            SyndeticsCoverImages                  => C4::Context->preference("SyndeticsCoverImages"),
            SyndeticsTOC                          => C4::Context->preference("SyndeticsTOC"),
            SyndeticsSummary                      => C4::Context->preference("SyndeticsSummary"),
            SyndeticsEditions                     => C4::Context->preference("SyndeticsEditions"),
            SyndeticsExcerpt                      => C4::Context->preference("SyndeticsExcerpt"),
            SyndeticsReviews                      => C4::Context->preference("SyndeticsReviews"),
            SyndeticsAuthorNotes                  => C4::Context->preference("SyndeticsAuthorNotes"),
            SyndeticsAwards                       => C4::Context->preference("SyndeticsAwards"),
            SyndeticsSeries                       => C4::Context->preference("SyndeticsSeries"),
            SyndeticsCoverImageSize               => C4::Context->preference("SyndeticsCoverImageSize"),
            OPACLocalCoverImages                  => C4::Context->preference("OPACLocalCoverImages"),
            PatronSelfRegistration                => C4::Context->preference("PatronSelfRegistration"),
611
            PatronSelfRegistrationDefaultCategory => C4::Context->preference("PatronSelfRegistrationDefaultCategory"),
612
            useDischarge                 => C4::Context->preference('useDischarge'),
tipaul's avatar
tipaul committed
613
        );
614

615
        $template->param( OpacPublic => '1' ) if ( $user || C4::Context->preference("OpacPublic") );
tipaul's avatar
tipaul committed
616
    }
617 618 619

    # Check if we were asked using parameters to force a specific language
    if ( defined $in->{'query'}->param('language') ) {
620

621
        # Extract the language, let C4::Languages::getlanguage choose
622
        # what to do
623 624
        my $language = C4::Languages::getlanguage( $in->{'query'} );
        my $languagecookie = C4::Templates::getlanguagecookie( $in->{'query'}, $language );
625
        if ( ref $cookie eq 'ARRAY' ) {
626
            push @{$cookie}, $languagecookie;
627
        } else {
628
            $cookie = [ $cookie, $languagecookie ];
629
        }
630 631
    }

632
    return ( $template, $borrowernumber, $cookie, $flags );
633
}
finlayt's avatar
finlayt committed
634

635
=head2 checkauth
636

finlayt's avatar
finlayt committed
637
  ($userid, $cookie, $sessionID) = &checkauth($query, $noauth, $flagsrequired, $type);
arensb's avatar
arensb committed
638

639 640
Verifies that the user is authorized to run this script.  If
the user is authorized, a (userid, cookie, session-id, flags)
641 642
quadruple is returned.  If the user is not authorized but does
not have the required privilege (see $flagsrequired below), it
643 644 645 646 647 648 649
displays an error page and exits.  Otherwise, it displays the
login page and exits.

Note that C<&checkauth> will return if and only if the user
is authorized, so it should be called early on, before any
unfinished operations (e.g., if you've opened a file, then
C<&checkauth> won't close it for you).
arensb's avatar
arensb committed
650 651 652 653 654 655 656 657 658 659

C<$query> is the CGI object for the script calling C<&checkauth>.

The C<$noauth> argument is optional. If it is set, then no
authorization is required for the script.

C<&checkauth> fetches user and session information from C<$query> and
ensures that the user is authorized to run scripts that require
authorization.

660 661 662 663 664 665 666 667 668 669
The C<$flagsrequired> argument specifies the required privileges
the user must have if the username and password are correct.
It should be specified as a reference-to-hash; keys in the hash
should be the "flags" for the user, as specified in the Members
intranet module. Any key specified must correspond to a "flag"
in the userflags table. E.g., { circulate => 1 } would specify
that the user must have the "circulate" privilege in order to
proceed. To make sure that access control is correct, the
C<$flagsrequired> parameter must be specified correctly.

670 671 672 673
Koha also has a concept of sub-permissions, also known as
granular permissions.  This makes the value of each key
in the C<flagsrequired> hash take on an additional
meaning, i.e.,
674

675
 1
676 677 678 679

The user must have access to all subfunctions of the module
specified by the hash key.

680
 *
681 682 683 684

The user must have access to at least one subfunction of the module
specified by the hash key.

685
 specific permission, e.g., 'export_catalog'
686 687 688 689

The user must have access to the specific subfunction list, which
must correspond to a row in the permissions table.

690 691 692 693
The C<$type> argument specifies whether the template should be
retrieved from the opac or intranet directory tree.  "opac" is
assumed if it is not specified; however, if C<$type> is specified,
"intranet" is assumed if it is not "opac".
finlayt's avatar
finlayt committed
694

arensb's avatar
arensb committed
695 696 697 698 699 700 701
If C<$query> does not have a valid session ID associated with it
(i.e., the user has not logged in) or if the session has expired,
C<&checkauth> presents the user with a login page (from the point of
view of the original script, C<&checkauth> does not return). Once the
user has authenticated, C<&checkauth> restarts the original script
(this time, C<&checkauth> returns).

finlayt's avatar
finlayt committed
702
The login page is provided using a HTML::Template, which is set in the
tipaul's avatar
tipaul committed
703
systempreferences table or at the top of this file. The variable C<$type>
704
selects which template to use, either the opac or the intranet
finlayt's avatar
finlayt committed
705 706
authentification template.

arensb's avatar
arensb committed
707 708 709 710 711
C<&checkauth> returns a user ID, a cookie, and a session ID. The
cookie should be sent back to the browser; it verifies that the user
has authenticated.

=cut
finlayt's avatar
finlayt committed
712

713
sub _version_check {
714
    my $type  = shift;
715 716
    my $query = shift;
    my $version;
717

718
    # If version syspref is unavailable, it means Koha is being installed,
719
    # and so we must redirect to OPAC maintenance page or to the WebInstaller
720
    # also, if OpacMaintenance is ON, OPAC should redirect to maintenance
721
    if ( C4::Context->preference('OpacMaintenance') && $type eq 'opac' ) {
722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740
        warn "OPAC Install required, redirecting to maintenance";
        print $query->redirect("/cgi-bin/koha/maintenance.pl");
        safe_exit;
    }
    unless ( $version = C4::Context->preference('Version') ) {    # assignment, not comparison
        if ( $type ne 'opac' ) {
            warn "Install required, redirecting to Installer";
            print $query->redirect("/cgi-bin/koha/installer/install.pl");
        } else {
            warn "OPAC Install required, redirecting to maintenance";
            print $query->redirect("/cgi-bin/koha/maintenance.pl");
        }
        safe_exit;
    }

    # check that database and koha version are the same
    # there is no DB version, it's a fresh install,
    # go to web installer
    # there is a DB version, compare it to the code version
741
    my $kohaversion = Koha::version();
742

743 744 745
    # remove the 3 last . to have a Perl number
    $kohaversion =~ s/(.*\..*)\.(.*)\.(.*)/$1$2$3/;
    $debug and print STDERR "kohaversion : $kohaversion\n";
746
    if ( $version < $kohaversion ) {
747
        my $warning = "Database update needed, redirecting to %s. Database is $version and Koha is $kohaversion";
748 749
        if ( $type ne 'opac' ) {
            warn sprintf( $warning, 'Installer' );
750
            print $query->redirect("/cgi-bin/koha/installer/install.pl?step=1&op=updatestructure");
751
        } else {
752
            warn sprintf( "OPAC: " . $warning, 'maintenance' );
753 754 755 756 757 758
            print $query->redirect("/cgi-bin/koha/maintenance.pl");
        }
        safe_exit;
    }
}

759
sub _session_log {
760
    (@_) or return 0;
761
    open my $fh, '>>', "/tmp/sessionlog" or warn "ERROR: Cannot append to /tmp/sessionlog";
762
    printf $fh join( "\n", @_ );
763
    close $fh;
764 765
}

766 767
sub _timeout_syspref {
    my $timeout = C4::Context->preference('timeout') || 600;
768

769
    # value in days, convert in seconds
770
    if ( $timeout =~ /(\d+)[dD]/ ) {
771
        $timeout = $1 * 86400;
772
    }
773 774 775
    return $timeout;
}

tipaul's avatar
tipaul committed
776 777
sub checkauth {
    my $query = shift;
778
    $debug and warn "Checking Auth";
779
    # $authnotrequired will be set for scripts which will run without authentication
tipaul's avatar
tipaul committed
780 781 782
    my $authnotrequired = shift;
    my $flagsrequired   = shift;
    my $type            = shift;
783
    my $emailaddress    = shift;
tipaul's avatar
tipaul committed
784
    $type = 'opac' unless $type;
finlayt's avatar
finlayt committed
785

tipaul's avatar
tipaul committed
786
    my $dbh     = C4::Context->dbh;
787
    my $timeout = _timeout_syspref();
788

789 790
    _version_check( $type, $query );

tipaul's avatar
tipaul committed
791 792 793
    # state variables
    my $loggedin = 0;
    my %info;
794
    my ( $userid, $cookie, $sessionID, $flags );
tipaul's avatar
tipaul committed
795
    my $logout = $query->param('logout.x');
796

797
    my $anon_search_history;
798
    my $cas_ticket = '';
799 800 801
    # This parameter is the name of the CAS server we want to authenticate against,
    # when using authentication against multiple CAS servers, as configured in Auth_cas_servers.yaml
    my $casparam = $query->param('cas');
802
    my $q_userid = $query->param('userid') // '';
803

804 805 806 807 808 809 810 811 812
    # Basic authentication is incompatible with the use of Shibboleth,
    # as Shibboleth may return REMOTE_USER as a Shibboleth attribute,
    # and it may not be the attribute we want to use to match the koha login.
    #
    # Also, do not consider an empty REMOTE_USER.
    #
    # Finally, after those tests, we can assume (although if it would be better with
    # a syspref) that if we get a REMOTE_USER, that's from basic authentication,
    # and we can affect it to $userid.
813
    if ( !$shib and defined( $ENV{'REMOTE_USER'} ) and $ENV{'REMOTE_USER'} ne '' and $userid = $ENV{'REMOTE_USER'} ) {
814 815

        # Using Basic Authentication, no cookies required
tipaul's avatar
tipaul committed
816
        $cookie = $query->cookie(
817 818 819 820
            -name     => 'CGISESSID',
            -value    => '',
            -expires  => '',
            -HttpOnly => 1,
tipaul's avatar
tipaul committed
821 822 823
        );
        $loggedin = 1;
    }
824 825 826
    elsif ( $emailaddress) {
        # the Google OpenID Connect passes an email address
    }
827 828
    elsif ( $sessionID = $query->cookie("CGISESSID") )
    {    # assignment, not comparison
829
        my $session = get_session($sessionID);
tipaul's avatar
tipaul committed
830
        C4::Context->_new_userenv($sessionID);
831
        my ( $ip, $lasttime, $sessiontype );
832
        my $s_userid = '';
833
        if ($session) {
834
            $s_userid = $session->param('id') // '';
835
            C4::Context->set_userenv(
836
                $session->param('number'),       $s_userid,
837 838 839
                $session->param('cardnumber'),   $session->param('firstname'),
                $session->param('surname'),      $session->param('branch'),
                $session->param('branchname'),   $session->param('flags'),
840
                $session->param('emailaddress'), $session->param('branchprinter'),
841
                $session->param('shibboleth')
tipaul's avatar
tipaul committed
842
            );
843 844 845 846 847 848 849
            C4::Context::set_shelves_userenv( 'bar', $session->param('barshelves') );
            C4::Context::set_shelves_userenv( 'pub', $session->param('pubshelves') );
            C4::Context::set_shelves_userenv( 'tot', $session->param('totshelves') );
            $debug and printf STDERR "AUTH_SESSION: (%s)\t%s %s - %s\n", map { $session->param($_) } qw(cardnumber firstname surname branch);
            $ip          = $session->param('ip');
            $lasttime    = $session->param('lasttime');
            $userid      = $s_userid;
850
            $sessiontype = $session->param('sessiontype') || '';
Paul POULAIN's avatar
Paul POULAIN committed
851
        }
852
        if ( ( $query->param('koha_login_context') && ( $q_userid ne $s_userid ) )
853 854 855
            || ( $cas && $query->param('ticket') && !C4::Context->userenv->{'id'} )
            || ( $shib && $shib_login && !$logout && !C4::Context->userenv->{'id'} )
        ) {
856

857 858
            #if a user enters an id ne to the id in the current session, we need to log them in...
            #first we need to clear the anonymous session...
859
            $debug and warn "query id = $q_userid but session id = $s_userid";
860
            $anon_search_history = $session->param('search_history');
861
            $session->delete();
862
            $session->flush;
863
            C4::Context->_unset_userenv($sessionID);
864
            $sessionID = undef;
865
            $userid    = undef;
866
        }
867
        elsif ($logout) {
868

tipaul's avatar
tipaul committed
869
            # voluntary logout the user
870
            # check wether the user was using their shibboleth session or a local one
871
            my $shibSuccess = C4::Context->userenv->{'shibboleth'};
872
            $session->delete();
873
            $session->flush;
tipaul's avatar
tipaul committed
874
            C4::Context->_unset_userenv($sessionID);
875

876
            #_session_log(sprintf "%20s from %16s logged out at %30s (manually).\n", $userid,$ip,(strftime "%c",localtime));
tipaul's avatar
tipaul committed
877 878
            $sessionID = undef;
            $userid    = undef;
879

880 881
            if ($cas and $caslogout) {
                logout_cas($query, $type);
882 883
            }

884
            # If we are in a shibboleth session (shibboleth is enabled, a shibboleth match attribute is set and matches koha matchpoint)
885 886 887
            if ( $shib and $shib_login and $shibSuccess and $type eq 'opac' ) {

                # (Note: $type eq 'opac' condition should be removed when shibboleth authentication for intranet will be implemented)
888 889
                logout_shib($query);
            }
tipaul's avatar
tipaul committed
890
        }
891 892
        elsif ( !$lasttime || ( $lasttime < time() - $timeout ) ) {

893 894
            # timed logout
            $info{'timed_out'} = 1;
895 896 897 898
            if ($session) {
                $session->delete();
                $session->flush;
            }
899
            C4::Context->_unset_userenv($sessionID);
900

901
            #_session_log(sprintf "%20s from %16s logged out at %30s (inactivity).\n", $userid,$ip,(strftime "%c",localtime));
902 903 904
            $userid    = undef;
            $sessionID = undef;
        }
905
        elsif ( C4::Context->preference('SessionRestrictionByIP') && $ip ne $ENV{'REMOTE_ADDR'} ) {
906

907 908 909 910 911
            # Different ip than originally logged in from
            $info{'oldip'}        = $ip;
            $info{'newip'}        = $ENV{'REMOTE_ADDR'};
            $info{'different_ip'} = 1;
            $session->delete();
912
            $session->flush;
913
            C4::Context->_unset_userenv($sessionID);
914

915
            #_session_log(sprintf "%20s from %16s logged out at %30s (ip changed to %16s).\n", $userid,$ip,(strftime "%c",localtime), $info{'newip'});
916 917 918 919
            $sessionID = undef;
            $userid    = undef;
        }
        else {
920 921 922 923 924 925
            $cookie = $query->cookie(
                -name     => 'CGISESSID',
                -value    => $session->id,
                -HttpOnly => 1
            );
            $session->param( 'lasttime', time() );
926 927
            unless ( $sessiontype && $sessiontype eq 'anon' ) {    #if this is an anonymous session, we want to update the session, but not behave as if they are logged in...
                $flags = haspermission( $userid, $flagsrequired );
928 929 930 931 932 933 934
                if ($flags) {
                    $loggedin = 1;
                } else {
                    $info{'nopermission'} = 1;
                }
            }
        }
tipaul's avatar
tipaul committed
935
    }
936
    unless ( $userid || $sessionID ) {
937
        #we initiate a session prior to checking for a username to allow for anonymous sessions...
938
        my $session = get_session("") or die "Auth ERROR: Cannot get_session()";
939 940 941 942 943

        # Save anonymous search history in new session so it can be retrieved
        # by get_template_and_user to store it in user's search history after
        # a successful login.
        if ($anon_search_history) {
944
            $session->param( 'search_history', $anon_search_history );
945 946
        }

947
        my $sessionID = $session->id;
948
        C4::Context->_new_userenv($sessionID);
949 950 951 952 953
        $cookie = $query->cookie(
            -name     => 'CGISESSID',
            -value    => $session->id,
            -HttpOnly => 1
        );
954
        my $pki_field = C4::Context->preference('AllowPKIAuth');
955
        if ( !defined($pki_field) ) {
956 957 958
            print STDERR "ERROR: Missing system preference AllowPKIAuth.\n";
            $pki_field = 'None';
        }
959
        if ( ( $cas && $query->param('ticket') )
960
            || $q_userid
961
            || ( $shib && $shib_login )
962 963
            || $pki_field ne 'None'
            || $emailaddress )
964
        {
965 966
            my $password    = $query->param('password');
            my $shibSuccess = 0;
967
            my ( $return, $cardnumber );
968 969

            # If shib is enabled and we have a shib login, does the login match a valid koha user
970
            if ( $shib && $shib_login && $type eq 'opac' ) {
971
                my $retuserid;
972 973

                # Do not pass password here, else shib will not be checked in checkpw.
974
                ( $return, $cardnumber, $retuserid ) = checkpw( $dbh, $q_userid, undef, $query );
975 976
                $userid      = $retuserid;
                $shibSuccess = $return;
977
                $info{'invalidShibLogin'} = 1 unless ($return);
978
            }
979

980
            # If shib login and match were successful, skip further login methods
981 982 983
            unless ($shibSuccess) {
                if ( $cas && $query->param('ticket') ) {
                    my $retuserid;
984
                    ( $return, $cardnumber, $retuserid, $cas_ticket ) =
985
                      checkpw( $dbh, $userid, $password, $query, $type );
986 987
                    $userid = $retuserid;
                    $info{'invalidCasLogin'} = 1 unless ($return);
988 989
                }

990 991 992 993 994 995 996
                elsif ( $emailaddress ) {
                    my $value = $emailaddress;

                    # If we're looking up the email, there's a chance that the person
                    # doesn't have a userid. So if there is none, we pass along the
                    # borrower number, and the bits of code that need to know the user
                    # ID will have to be smart enough to handle that.
997 998
                    my $patrons = Koha::Patrons->search({ email => $value });
                    if ($patrons->count) {
999 1000

                        # First the userid, then the borrowernum
1001 1002
                        my $patron = $patrons->next;
                        $value = $patron->userid || $patron->borrowernumber;
1003 1004 1005 1006 1007 1008 1009
                    } else {
                        undef $value;
                    }
                    $return = $value ? 1 : 0;
                    $userid = $value;
                }

1010 1011 1012 1013 1014 1015 1016 1017 1018 1019 1020 1021 1022 1023 1024 1025 1026
                elsif (
                    ( $pki_field eq 'Common Name' && $ENV{'SSL_CLIENT_S_DN_CN'} )
                    || ( $pki_field eq 'emailAddress'
                        && $ENV{'SSL_CLIENT_S_DN_Email'} )
                  )
                {
                    my $value;
                    if ( $pki_field eq 'Common Name' ) {
                        $value = $ENV{'SSL_CLIENT_S_DN_CN'};
                    }
                    elsif ( $pki_field eq 'emailAddress' ) {
                        $value = $ENV{'SSL_CLIENT_S_DN_Email'};

                        # If we're looking up the email, there's a chance that the person
                        # doesn't have a userid. So if there is none, we pass along the
                        # borrower number, and the bits of code that need to know the user
                        # ID will have to be smart enough to handle that.
1027 1028
                        my $patrons = Koha::Patrons->search({ email => $value });
                        if ($patrons->count) {
1029 1030

                            # First the userid, then the borrowernum
1031 1032
                            my $patron = $patrons->next;
                            $value = $patron->userid || $patron->borrowernumber;
1033 1034 1035 1036
                        } else {
                            undef $value;
                        }
                    }
1037

1038 1039
                    $return = $value ? 1 : 0;
                    $userid = $value;
1040

1041 1042 1043
                }
                else {
                    my $retuserid;
1044
                    ( $return, $cardnumber, $retuserid, $cas_ticket ) =
1045
                      checkpw( $dbh, $q_userid, $password, $query, $type );
1046 1047
                    $userid = $retuserid if ($retuserid);
                    $info{'invalid_username_or_password'} = 1 unless ($return);
1048 1049 1050
                }
            }

joubu's avatar
joubu committed
1051
            # $return: 1 = valid user
1052 1053 1054 1055
            if ($return) {

                #_session_log(sprintf "%20s from %16s logged in  at %30s.\n", $userid,$ENV{'REMOTE_ADDR'},(strftime '%c', localtime));
                if ( $flags = haspermission( $userid, $flagsrequired ) ) {
1056 1057
                    $loggedin = 1;
                }
1058
                else {
1059 1060 1061
                    $info{'nopermission'} = 1;
                    C4::Context->_unset_userenv($sessionID);
                }
1062 1063
                my ( $borrowernumber, $firstname, $surname, $userflags,
                    $branchcode, $branchname, $branchprinter, $emailaddress );
1064 1065 1066 1067 1068 1069 1070 1071 1072 1073 1074 1075

                if ( $return == 1 ) {
                    my $select = "
                    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
                    ";
                    my $sth = $dbh->prepare("$select where userid=?");
                    $sth->execute($userid);
1076
                    unless ( $sth->rows ) {
1077 1078 1079 1080
                        $debug and print STDERR "AUTH_1: no rows for userid='$userid'\n";
                        $sth = $dbh->prepare("$select where cardnumber=?");
                        $sth->execute($cardnumber);

1081
                        unless ( $sth->rows ) {
1082 1083
                            $debug and print STDERR "AUTH_2a: no rows for cardnumber='$cardnumber'\n";
                            $sth->execute($userid);
1084
                            unless ( $sth->rows ) {
1085 1086 1087 1088
                                $debug and print STDERR "AUTH_2b: no rows for userid='$userid' AS cardnumber\n";
                            }
                        }
                    }
1089 1090 1091
                    if ( $sth->rows ) {
                        ( $borrowernumber, $firstname, $surname, $userflags,
                            $branchcode, $branchname, $branchprinter, $emailaddress ) = $sth->fetchrow;
1092
                        $debug and print STDERR "AUTH_3 results: " .
1093
                          "$cardnumber,$borrowernumber,$userid,$firstname,$surname,$userflags,$branchcode,$emailaddress\n";
1094 1095 1096
                    } else {
                        print STDERR "AUTH_3: no results for userid='$userid', cardnumber='$cardnumber'.\n";
                    }
tipaul's avatar
tipaul committed
1097

1098 1099 1100 1101
                    # launch a sequence to check if we have a ip for the branch, i
                    # if we have one we replace the branchcode of the userenv by the branch bound in the ip.

                    my $ip = $ENV{'REMOTE_ADDR'};
1102

1103
                    # if they specify at login, use that
1104 1105
                    if ( $query->param('branch') ) {
                        $branchcode = $query->param('branch');
1106 1107
                        my $library = Koha::Libraries->find($branchcode);
                        $branchname = $library? $library->branchname: '';
1108
                    }
1109
                    my $branches = { map { $_->branchcode => $_->unblessed } Koha::Libraries->search };
1110
                    if ( $type ne 'opac' and C4::Context->boolean_preference('AutoLocation') ) {
1111

1112 1113
                        # we have to check they are coming from the right ip range
                        my $domain = $branches->{$branchcode}->{'branchip'};
1114
                        $domain =~ s|\.\*||g;
1115 1116
                        if ( $ip !~ /^$domain/ ) {
                            $loggedin = 0;
1117 1118 1119 1120 1121
                            $cookie = $query->cookie(
                                -name     => 'CGISESSID',
                                -value    => '',
                                -HttpOnly => 1
                            );
1122 1123 1124 1125 1126
                            $info{'wrongip'} = 1;
                        }
                    }

                    foreach my $br ( keys %$branches ) {
1127

1128 1129 1130 1131 1132 1133