X-Git-Url: http://koha-dev.rot13.org:8081/gitweb/?a=blobdiff_plain;f=C4%2FAuth.pm;h=a2b1b84aea004a4cd191f7f9b031ee1fa457fff2;hb=eded6edacc5e3bf8dd0be21ed05842c3b78aadce;hp=cb35984b2ef88238f42314b73c6a34f03686f899;hpb=a502ae7b68bd2edeeeaaa163834b5a7854a6ac22;p=koha-ffzg.git diff --git a/C4/Auth.pm b/C4/Auth.pm index cb35984b2e..a2b1b84aea 100644 --- a/C4/Auth.pm +++ b/C4/Auth.pm @@ -1,31 +1,3 @@ -package CGI::Session::Serialize::yamlxs; -# Proof of concept: CGI::Session::Serialize::yamlxs for CGI::Session: - -use strict; -use warnings; - -# hacky hack to trick CGI::Session loader for serializers not to die in its "require": -$INC{'CGI/Session/Serialize/yamlxs.pm'} = '1'; - -use CGI::Session::ErrorHandler; -use YAML::XS (); - -$CGI::Session::Serialize::yamlxs::VERSION = '0.1'; -@CGI::Session::Serialize::yamlxs::ISA = ( "CGI::Session::ErrorHandler" ); - -sub freeze { - my ($self, $data) = @_; - return YAML::XS::Dump($data); -} - -sub thaw { - my ($self, $string) = @_; - return (YAML::XS::Load($string))[0]; -} -# ******************************************************************** - - - package C4::Auth; # Copyright 2000-2002 Katipo Communications @@ -47,39 +19,42 @@ package C4::Auth; use strict; use warnings; -use Carp qw/croak/; +use Carp qw( croak ); -use Digest::MD5 qw(md5_base64); -use JSON qw/encode_json/; -use URI::Escape; +use Digest::MD5 qw( md5_base64 ); use CGI::Session; +use CGI::Session::ErrorHandler; +use URI; +use URI::QueryParam; -require Exporter; use C4::Context; use C4::Templates; # to get the template use C4::Languages; use C4::Search::History; use Koha; +use Koha::Logger; use Koha::Caches; -use Koha::AuthUtils qw(get_script_name hash_password); +use Koha::AuthUtils qw( get_script_name hash_password ); +use Koha::Auth::TwoFactorAuth; use Koha::Checkouts; -use Koha::DateUtils qw(dt_from_string); +use Koha::DateUtils qw( dt_from_string ); use Koha::Library::Groups; use Koha::Libraries; use Koha::Cash::Registers; use Koha::Desks; use Koha::Patrons; use Koha::Patron::Consents; -use POSIX qw/strftime/; -use List::MoreUtils qw/ any /; -use Encode qw( encode is_utf8); -use C4::Auth_with_shibboleth; +use List::MoreUtils qw( any ); +use Encode; +use C4::Auth_with_shibboleth qw( shib_ok get_login_shib login_shib_url logout_shib checkpw_shib ); use Net::CIDR; -use C4::Log qw/logaction/; +use C4::Log qw( logaction ); +use Koha::CookieManager; # use utf8; -use vars qw(@ISA @EXPORT @EXPORT_OK %EXPORT_TAGS $debug $ldap $cas $caslogout); +use vars qw($ldap $cas $caslogout); +our (@ISA, @EXPORT_OK); BEGIN { sub psgi_env { any { /^psgi\./ } keys %ENV } @@ -90,24 +65,26 @@ BEGIN { C4::Context->set_remote_address; - $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 track_login_daily &in_iprange + require Exporter; + @ISA = qw(Exporter); + + @EXPORT_OK = qw( + checkauth check_api_auth get_session check_cookie_auth checkpw checkpw_internal checkpw_hash + get_all_subpermissions get_user_subpermissions track_login_daily in_iprange + get_template_and_user haspermission ); - %EXPORT_TAGS = ( EditPermissions => [qw(get_all_subpermissions get_user_subpermissions)] ); + $ldap = C4::Context->config('useldapserver') || 0; $cas = C4::Context->preference('casAuthentication'); $caslogout = C4::Context->preference('casLogout'); - require C4::Auth_with_cas; # no import if ($ldap) { require C4::Auth_with_ldap; import C4::Auth_with_ldap qw(checkpw_ldap); } if ($cas) { - import C4::Auth_with_cas qw(check_api_auth_cas checkpw_cas login_cas logout_cas login_cas_url logout_if_required); + require C4::Auth_with_cas; # no import + import C4::Auth_with_cas qw(check_api_auth_cas checkpw_cas login_cas logout_cas login_cas_url logout_if_required multipleAuth getMultipleAuth); } } @@ -179,6 +156,9 @@ sub get_template_and_user { my $in = shift; my ( $user, $cookie, $sessionID, $flags ); + $cookie = []; + + my $cookie_mgr = Koha::CookieManager->new; # Get shibboleth login attribute my $shib = C4::Context->config('useshibboleth') && shib_ok(); @@ -224,14 +204,26 @@ sub get_template_and_user { } if ( $in->{type} eq 'opac' && $user ) { + my $is_sco_user; + if ($sessionID){ + my $session = get_session($sessionID); + if ($session){ + $is_sco_user = $session->param('sco_user'); + } + } 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/| && $in->{template_name} !~ m|errors/errorpage.tt| - && C4::Context->preference('AutoSelfCheckID') - && $user eq C4::Context->preference('AutoSelfCheckID') + && ( + $is_sco_user || + ( + C4::Context->preference('AutoSelfCheckID') + && $user eq C4::Context->preference('AutoSelfCheckID') + ) + ) ) { $kick_out = 1; @@ -256,13 +248,13 @@ sub get_template_and_user { if ($kick_out) { $template = C4::Templates::gettemplate( 'opac-auth.tt', 'opac', $in->{query} ); - $cookie = $in->{query}->cookie( + $cookie = $cookie_mgr->replace_in_list( $cookie, $in->{query}->cookie( -name => 'CGISESSID', -value => '', - -expires => '', -HttpOnly => 1, -secure => ( C4::Context->https_enabled() ? 1 : 0 ), - ); + -sameSite => 'Lax', + )); $template->param( loginprompt => 1, @@ -315,12 +307,12 @@ sub get_template_and_user { my $some_private_shelves = Koha::Virtualshelves->get_some_shelves( { borrowernumber => $borrowernumber, - category => 1, + public => 0, } ); my $some_public_shelves = Koha::Virtualshelves->get_some_shelves( { - category => 2, + public => 1, } ); $template->param( @@ -361,6 +353,7 @@ sub get_template_and_user { $template->param( CAN_user_stockrotation => 1 ); $template->param( CAN_user_cash_management => 1 ); $template->param( CAN_user_problem_reports => 1 ); + $template->param( CAN_user_recalls => 1 ); foreach my $module ( keys %$all_perms ) { foreach my $subperm ( keys %{ $all_perms->{$module} } ) { @@ -411,7 +404,6 @@ sub get_template_and_user { # 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 (?, ?, ?, ?, ?, ?, ?) @@ -459,12 +451,15 @@ sub get_template_and_user { require Koha::Virtualshelves; my $some_public_shelves = Koha::Virtualshelves->get_some_shelves( { - category => 2, + public => 1, } ); $template->param( some_public_shelves => $some_public_shelves, ); + + # Set default branch if one has been passed by the environment. + $template->param( default_branch => $ENV{OPAC_BRANCH_DEFAULT} ) if $ENV{OPAC_BRANCH_DEFAULT}; } } @@ -507,7 +502,6 @@ sub get_template_and_user { my $minPasswordLength = C4::Context->preference('minPasswordLength'); $minPasswordLength = 3 if not $minPasswordLength or $minPasswordLength < 3; $template->param( - "BiblioDefaultView" . C4::Context->preference("BiblioDefaultView") => 1, EnhancedMessagingPreferences => C4::Context->preference('EnhancedMessagingPreferences'), GoogleJackets => C4::Context->preference("GoogleJackets"), OpenLibraryCovers => C4::Context->preference("OpenLibraryCovers"), @@ -520,8 +514,6 @@ sub get_template_and_user { item_level_itypes => C4::Context->preference('item-level_itypes'), patronimages => C4::Context->preference("patronimages"), singleBranchMode => ( Koha::Libraries->search->count == 1 ), - XSLTDetailsDisplay => C4::Context->preference("XSLTDetailsDisplay"), - XSLTResultsDisplay => C4::Context->preference("XSLTResultsDisplay"), noItemTypeImages => C4::Context->preference("noItemTypeImages"), marcflavour => C4::Context->preference("marcflavour"), OPACBaseURL => C4::Context->preference('OPACBaseURL'), @@ -531,7 +523,6 @@ sub get_template_and_user { $template->param( AmazonCoverImages => C4::Context->preference("AmazonCoverImages"), AutoLocation => C4::Context->preference("AutoLocation"), - "BiblioDefaultView" . C4::Context->preference("IntranetBiblioDefaultView") => 1, PatronAutoComplete => C4::Context->preference("PatronAutoComplete"), FRBRizeEditions => C4::Context->preference("FRBRizeEditions"), IndependentBranches => C4::Context->preference("IndependentBranches"), @@ -556,7 +547,7 @@ sub get_template_and_user { EnableBorrowerFiles => C4::Context->preference('EnableBorrowerFiles'), UseCourseReserves => C4::Context->preference("UseCourseReserves"), useDischarge => C4::Context->preference('useDischarge'), - pending_checkout_notes => scalar Koha::Checkouts->search({ noteseen => 0 }), + pending_checkout_notes => Koha::Checkouts->search({ noteseen => 0 }), ); } else { @@ -574,10 +565,12 @@ sub get_template_and_user { && $in->{'template_name'} =~ /opac-(.+)\.(?:tt|tmpl)$/ ) { my $pagename = $1; unless ( $pagename =~ /^(?:MARC|ISBD)?detail$/ + or $pagename =~ /^showmarc$/ or $pagename =~ /^addbybiblionumber$/ - or $pagename =~ /^review$/ ) { - my $sessionSearch = get_session( $sessionID || $in->{'query'}->cookie("CGISESSID") ); - $sessionSearch->clear( ["busc"] ) if ( $sessionSearch->param("busc") ); + or $pagename =~ /^review$/ ) + { + my $sessionSearch = get_session( $sessionID ); + $sessionSearch->clear( ["busc"] ) if $sessionSearch; } } @@ -588,7 +581,7 @@ sub get_template_and_user { if ( ( $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+)/ ) + ( $in->{'query'}->param('limit') && $in->{'query'}->param('limit') =~ /multibranchlimit:(\w+)/ ) ) { $opac_name = $1; # opac_search_limit is a branch, so we use it. } elsif ( $in->{'query'}->param('multibranchlimit') ) { @@ -597,7 +590,7 @@ sub get_template_and_user { $opac_name = C4::Context->userenv->{'branch'}; } - my @search_groups = Koha::Library::Groups->get_search_groups({ interface => 'opac' }); + my @search_groups = Koha::Library::Groups->get_search_groups({ interface => 'opac' })->as_list; $template->param( AnonSuggestions => "" . C4::Context->preference("AnonSuggestions"), LibrarySearchGroups => \@search_groups, @@ -617,14 +610,11 @@ sub get_template_and_user { OpacBrowser => C4::Context->preference("OpacBrowser"), OpacCloud => C4::Context->preference("OpacCloud"), OpacKohaUrl => C4::Context->preference("OpacKohaUrl"), - OpacNav => "" . C4::Context->preference("OpacNav"), - 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'} : '', @@ -640,8 +630,6 @@ sub get_template_and_user { 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"), @@ -670,11 +658,7 @@ sub get_template_and_user { # what to do my $language = C4::Languages::getlanguage( $in->{'query'} ); my $languagecookie = C4::Templates::getlanguagecookie( $in->{'query'}, $language ); - if ( ref $cookie eq 'ARRAY' ) { - push @{$cookie}, $languagecookie; - } else { - $cookie = [ $cookie, $languagecookie ]; - } + $cookie = $cookie_mgr->replace_in_list( $cookie, $languagecookie ); } return ( $template, $borrowernumber, $cookie, $flags ); @@ -790,7 +774,7 @@ sub _version_check { # remove the 3 last . to have a Perl number $kohaversion =~ s/(.*\..*)\.(.*)\.(.*)/$1$2$3/; - $debug and print STDERR "kohaversion : $kohaversion\n"; + Koha::Logger->get->debug("kohaversion : $kohaversion"); if ( $version < $kohaversion ) { my $warning = "Database update needed, redirecting to %s. Database is $version and Koha is $kohaversion"; if ( $type ne 'opac' ) { @@ -804,13 +788,6 @@ sub _version_check { } } -sub _session_log { - (@_) or return 0; - open my $fh, '>>', "/tmp/sessionlog" or warn "ERROR: Cannot append to /tmp/sessionlog"; - printf $fh join( "\n", @_ ); - close $fh; -} - sub _timeout_syspref { my $default_timeout = 600; my $timeout = C4::Context->preference('timeout') || $default_timeout; @@ -833,7 +810,6 @@ sub _timeout_syspref { sub checkauth { my $query = shift; - $debug and warn "Checking Auth"; # Get shibboleth login attribute my $shib = C4::Context->config('useshibboleth') && shib_ok(); @@ -847,27 +823,31 @@ sub checkauth { my $template_name = shift; $type = 'opac' unless $type; - unless ( C4::Context->preference("OpacPublic") ) { + if ( $type eq 'opac' && !C4::Context->preference("OpacPublic") ) { my @allowed_scripts_for_private_opac = qw( opac-memberentry.tt opac-registration-email-sent.tt opac-registration-confirmation.tt opac-memberentry-update-submitted.tt opac-password-recovery.tt + opac-reset-password.tt ); $authnotrequired = 0 unless grep { $_ eq $template_name } @allowed_scripts_for_private_opac; } - my $dbh = C4::Context->dbh; my $timeout = _timeout_syspref(); + my $cookie_mgr = Koha::CookieManager->new; + _version_check( $type, $query ); # state variables my $loggedin = 0; + my $auth_state = 'failed'; my %info; my ( $userid, $cookie, $sessionID, $flags ); + $cookie = []; my $logout = $query->param('logout.x'); my $anon_search_history; @@ -878,6 +858,8 @@ sub checkauth { my $q_userid = $query->param('userid') // ''; my $session; + my $invalid_otp_token; + my $require_2FA = ( C4::Context->preference('TwoFactorAuthentication') && $type ne "opac" ) ? 1 : 0; # Basic authentication is incompatible with the use of Shibboleth, # as Shibboleth may return REMOTE_USER as a Shibboleth attribute, @@ -891,130 +873,139 @@ sub checkauth { if ( !$shib and defined( $ENV{'REMOTE_USER'} ) and $ENV{'REMOTE_USER'} ne '' and $userid = $ENV{'REMOTE_USER'} ) { # Using Basic Authentication, no cookies required - $cookie = $query->cookie( + $cookie = $cookie_mgr->replace_in_list( $cookie, $query->cookie( -name => 'CGISESSID', -value => '', - -expires => '', -HttpOnly => 1, -secure => ( C4::Context->https_enabled() ? 1 : 0 ), - ); + -sameSite => 'Lax', + )); $loggedin = 1; } elsif ( $emailaddress) { # the Google OpenID Connect passes an email address } - elsif ( $sessionID = $query->cookie("CGISESSID") ) - { # assignment, not comparison - $session = get_session($sessionID); - C4::Context->_new_userenv($sessionID); - my ( $ip, $lasttime, $sessiontype ); - my $s_userid = ''; - if ($session) { - $s_userid = $session->param('id') // ''; - C4::Context->set_userenv( - $session->param('number'), $s_userid, - $session->param('cardnumber'), $session->param('firstname'), - $session->param('surname'), $session->param('branch'), - $session->param('branchname'), $session->param('flags'), - $session->param('emailaddress'), $session->param('shibboleth'), - $session->param('desk_id'), $session->param('desk_name'), - $session->param('register_id'), $session->param('register_name') - ); - 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; - $sessiontype = $session->param('sessiontype') || ''; - } - if ( ( $query->param('koha_login_context') && ( $q_userid ne $s_userid ) ) - || ( $cas && $query->param('ticket') && !C4::Context->userenv->{'id'} ) - || ( $shib && $shib_login && !$logout && !C4::Context->userenv->{'id'} ) - ) { - - #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... - $debug and warn "query id = $q_userid but session id = $s_userid"; - $anon_search_history = $session->param('search_history'); - $session->delete(); - $session->flush; - C4::Context->_unset_userenv($sessionID); - $sessionID = undef; - $userid = undef; - } - elsif ($logout) { + elsif ( $sessionID = $query->cookie("CGISESSID") ) { # assignment, not comparison + my ( $return, $more_info ); + # NOTE: $flags in the following call is still undefined ! + ( $return, $session, $more_info ) = check_cookie_auth( $sessionID, $flags, + { remote_addr => $ENV{REMOTE_ADDR}, skip_version_check => 1 } + ); - # voluntary logout the user - # check wether the user was using their shibboleth session or a local one - my $shibSuccess = C4::Context->userenv->{'shibboleth'}; - $session->delete(); - $session->flush; - C4::Context->_unset_userenv($sessionID); + if ( $return eq 'ok' || $return eq 'additional-auth-needed' ) { + $userid = $session->param('id'); + } - #_session_log(sprintf "%20s from %16s logged out at %30s (manually).\n", $userid,$ip,(strftime "%c",localtime)); - $sessionID = undef; - $userid = undef; + $auth_state = + $return eq 'ok' ? 'completed' + : $return eq 'additional-auth-needed' ? 'additional-auth-needed' + : 'failed'; - if ($cas and $caslogout) { - logout_cas($query, $type); + # We are at the second screen if the waiting-for-2FA is set in session + # and otp_token param has been passed + if ( $require_2FA + && $auth_state eq 'additional-auth-needed' + && ( my $otp_token = $query->param('otp_token') ) ) + { + my $patron = Koha::Patrons->find( { userid => $userid } ); + my $auth = Koha::Auth::TwoFactorAuth->new( { patron => $patron } ); + my $verified = $auth->verify($otp_token, 1); + $auth->clear; + if ( $verified ) { + # The token is correct, the user is fully logged in! + $auth_state = 'completed'; + $session->param( 'waiting-for-2FA', 0 ); + + # This is an ugly trick to pass the test + # $query->param('koha_login_context') && ( $q_userid ne $userid ) + # few lines later + $q_userid = $userid; } - - # If we are in a shibboleth session (shibboleth is enabled, a shibboleth match attribute is set and matches koha matchpoint) - if ( $shib and $shib_login and $shibSuccess) { - logout_shib($query); + else { + $invalid_otp_token = 1; } } - elsif ( !$lasttime || ( $lasttime < time() - $timeout ) ) { - # timed logout - $info{'timed_out'} = 1; - if ($session) { + if ( $auth_state eq 'completed' ) { + Koha::Logger->get->debug(sprintf "AUTH_SESSION: (%s)\t%s %s - %s", map { $session->param($_) || q{} } qw(cardnumber firstname surname branch)); + + if ( ( $query->param('koha_login_context') && ( $q_userid ne $userid ) ) + || ( $cas && $query->param('ticket') && !C4::Context->userenv->{'id'} ) + || ( $shib && $shib_login && !$logout && !C4::Context->userenv->{'id'} ) + ) { + + #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... + $anon_search_history = $session->param('search_history'); $session->delete(); $session->flush; - } - C4::Context->_unset_userenv($sessionID); + $cookie = $cookie_mgr->clear_unless( $query->cookie, @$cookie ); + C4::Context::_unset_userenv($sessionID); + $sessionID = undef; + } elsif (!$logout) { + + $cookie = $cookie_mgr->replace_in_list( $cookie, $query->cookie( + -name => 'CGISESSID', + -value => $session->id, + -HttpOnly => 1, + -secure => ( C4::Context->https_enabled() ? 1 : 0 ), + -sameSite => 'Lax', + )); - #_session_log(sprintf "%20s from %16s logged out at %30s (inactivity).\n", $userid,$ip,(strftime "%c",localtime)); - $userid = undef; - $sessionID = undef; + $flags = haspermission( $userid, $flagsrequired ); + unless ( $flags ) { + $auth_state = 'failed'; + $info{'nopermission'} = 1; + } + } + } elsif ( !$logout ) { + if ( $return eq 'expired' ) { + $info{timed_out} = 1; + } elsif ( $return eq 'restricted' ) { + $info{oldip} = $more_info->{old_ip}; + $info{newip} = $more_info->{new_ip}; + $info{different_ip} = 1; + } elsif ( $return eq 'password_expired' ) { + $info{password_has_expired} = 1; + } } - elsif ( C4::Context->preference('SessionRestrictionByIP') && $ip ne $ENV{'REMOTE_ADDR'} ) { + } + + if ( $auth_state eq 'failed' || $logout ) { + $sessionID = undef; + $userid = undef; + } - # Different ip than originally logged in from - $info{'oldip'} = $ip; - $info{'newip'} = $ENV{'REMOTE_ADDR'}; - $info{'different_ip'} = 1; + if ($logout) { + + # voluntary logout the user + # check wether the user was using their shibboleth session or a local one + my $shibSuccess = C4::Context->userenv ? C4::Context->userenv->{'shibboleth'} : undef; + if ( $session ) { $session->delete(); $session->flush; - C4::Context->_unset_userenv($sessionID); + } + C4::Context::_unset_userenv($sessionID); + $cookie = $cookie_mgr->clear_unless( $query->cookie, @$cookie ); - #_session_log(sprintf "%20s from %16s logged out at %30s (ip changed to %16s).\n", $userid,$ip,(strftime "%c",localtime), $info{'newip'}); - $sessionID = undef; - $userid = undef; + if ($cas and $caslogout) { + logout_cas($query, $type); } - else { - $cookie = $query->cookie( - -name => 'CGISESSID', - -value => $session->id, - -HttpOnly => 1, - -secure => ( C4::Context->https_enabled() ? 1 : 0 ), - ); - $session->param( 'lasttime', time() ); - 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 ); - if ($flags) { - $loggedin = 1; - } else { - $info{'nopermission'} = 1; - } - } + + # If we are in a shibboleth session (shibboleth is enabled, a shibboleth match attribute is set and matches koha matchpoint) + if ( $shib and $shib_login and $shibSuccess) { + logout_shib($query); } + + $session = undef; + $auth_state = 'logout'; } - unless ( $userid || $sessionID ) { + + unless ( $userid ) { #we initiate a session prior to checking for a username to allow for anonymous sessions... - my $session = get_session("") or die "Auth ERROR: Cannot get_session()"; + if( !$session or !$sessionID ) { # if we cleared sessionID, we need a new session + $session = get_session() or die "Auth ERROR: Cannot get_session()"; + } # 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 @@ -1025,12 +1016,13 @@ sub checkauth { $sessionID = $session->id; C4::Context->_new_userenv($sessionID); - $cookie = $query->cookie( + $cookie = $cookie_mgr->replace_in_list( $cookie, $query->cookie( -name => 'CGISESSID', - -value => $session->id, + -value => $sessionID, -HttpOnly => 1, -secure => ( C4::Context->https_enabled() ? 1 : 0 ), - ); + -sameSite => 'Lax', + )); my $pki_field = C4::Context->preference('AllowPKIAuth'); if ( !defined($pki_field) ) { print STDERR "ERROR: Missing system preference AllowPKIAuth.\n"; @@ -1051,7 +1043,7 @@ sub checkauth { my $retuserid; # Do not pass password here, else shib will not be checked in checkpw. - ( $return, $cardnumber, $retuserid ) = checkpw( $dbh, $q_userid, undef, $query ); + ( $return, $cardnumber, $retuserid ) = checkpw( $q_userid, undef, $query ); $userid = $retuserid; $shibSuccess = $return; $info{'invalidShibLogin'} = 1 unless ($return); @@ -1062,7 +1054,7 @@ sub checkauth { if ( $cas && $query->param('ticket') ) { my $retuserid; ( $return, $cardnumber, $retuserid, $cas_ticket ) = - checkpw( $dbh, $userid, $password, $query, $type ); + checkpw( $userid, $password, $query, $type ); $userid = $retuserid; $info{'invalidCasLogin'} = 1 unless ($return); } @@ -1122,9 +1114,16 @@ sub checkauth { else { my $retuserid; my $request_method = $query->request_method(); - if ($request_method eq 'POST'){ + + if ( + $request_method eq 'POST' + || ( C4::Context->preference('AutoSelfCheckID') + && $q_userid eq C4::Context->preference('AutoSelfCheckID') ) + ) + { + ( $return, $cardnumber, $retuserid, $cas_ticket ) = - checkpw( $dbh, $q_userid, $password, $query, $type ); + checkpw( $q_userid, $password, $query, $type ); $userid = $retuserid if ($retuserid); $info{'invalid_username_or_password'} = 1 unless ($return); } @@ -1149,15 +1148,14 @@ sub checkauth { } # $return: 1 = valid user - if ($return) { + if ($return > 0) { - #_session_log(sprintf "%20s from %16s logged in at %30s.\n", $userid,$ENV{'REMOTE_ADDR'},(strftime '%c', localtime)); if ( $flags = haspermission( $userid, $flagsrequired ) ) { - $loggedin = 1; + $auth_state = "logged_in"; } else { $info{'nopermission'} = 1; - C4::Context->_unset_userenv($sessionID); + C4::Context::_unset_userenv($sessionID); } my ( $borrowernumber, $firstname, $surname, $userflags, $branchcode, $branchname, $emailaddress, $desk_id, @@ -1170,28 +1168,20 @@ sub checkauth { FROM borrowers LEFT JOIN branches on borrowers.branchcode=branches.branchcode "; + my $dbh = C4::Context->dbh; my $sth = $dbh->prepare("$select where userid=?"); $sth->execute($userid); unless ( $sth->rows ) { - $debug and print STDERR "AUTH_1: no rows for userid='$userid'\n"; $sth = $dbh->prepare("$select where cardnumber=?"); $sth->execute($cardnumber); unless ( $sth->rows ) { - $debug and print STDERR "AUTH_2a: no rows for cardnumber='$cardnumber'\n"; $sth->execute($userid); - unless ( $sth->rows ) { - $debug and print STDERR "AUTH_2b: no rows for userid='$userid' AS cardnumber\n"; - } } } if ( $sth->rows ) { ( $borrowernumber, $firstname, $surname, $userflags, $branchcode, $branchname, $emailaddress ) = $sth->fetchrow; - $debug and print STDERR "AUTH_3 results: " . - "$cardnumber,$borrowernumber,$userid,$firstname,$surname,$userflags,$branchcode,$emailaddress\n"; - } else { - print STDERR "AUTH_3: no results for userid='$userid', cardnumber='$cardnumber'.\n"; } # launch a sequence to check if we have a ip for the branch, i @@ -1220,7 +1210,7 @@ sub checkauth { $register_id = $register->id if ($register); $register_name = $register->name if ($register); } - my $branches = { map { $_->branchcode => $_->unblessed } Koha::Libraries->search }; + my $branches = { map { $_->branchcode => $_->unblessed } Koha::Libraries->search->as_list }; if ( $type ne 'opac' and C4::Context->preference('AutoLocation') ) { # we have to check they are coming from the right ip range @@ -1228,12 +1218,13 @@ sub checkauth { $domain =~ s|\.\*||g; if ( $ip !~ /^$domain/ ) { $loggedin = 0; - $cookie = $query->cookie( + $cookie = $cookie_mgr->replace_in_list( $cookie, $query->cookie( -name => 'CGISESSID', -value => '', -HttpOnly => 1, -secure => ( C4::Context->https_enabled() ? 1 : 0 ), - ); + -sameSite => 'Lax', + )); $info{'wrongip'} = 1; } } @@ -1249,6 +1240,12 @@ sub checkauth { $branchname = $branches->{$br}->{'branchname'}; } } + + my $is_sco_user = 0; + if ( $query->param('sco_user_login') && ( $query->param('sco_user_login') eq '1' ) ){ + $is_sco_user = 1; + } + $session->param( 'number', $borrowernumber ); $session->param( 'id', $userid ); $session->param( 'cardnumber', $cardnumber ); @@ -1266,7 +1263,7 @@ sub checkauth { $session->param( 'shibboleth', $shibSuccess ); $session->param( 'register_id', $register_id ); $session->param( 'register_name', $register_name ); - $debug and printf STDERR "AUTH_4: (%s)\t%s %s - %s\n", map { $session->param($_) } qw(cardnumber firstname surname branch); + $session->param( 'sco_user', $is_sco_user ); } $session->param('cas_ticket', $cas_ticket) if $cas_ticket; C4::Context->set_userenv( @@ -1283,10 +1280,9 @@ sub checkauth { # $return: 0 = invalid user # reset to anonymous session else { - $debug and warn "Login failed, resetting anonymous session..."; if ($userid) { $info{'invalid_username_or_password'} = 1; - C4::Context->_unset_userenv($sessionID); + C4::Context::_unset_userenv($sessionID); } $session->param( 'lasttime', time() ); $session->param( 'ip', $session->remote_addr() ); @@ -1296,9 +1292,7 @@ sub checkauth { } # END if ( $q_userid elsif ( $type eq "opac" ) { - # if we are here this is an anonymous session; add public lists to it and a few other items... # anonymous sessions are created only for the OPAC - $debug and warn "Initiating an anonymous session..."; # setting a couple of other session vars... $session->param( 'ip', $session->remote_addr() ); @@ -1306,19 +1300,36 @@ sub checkauth { $session->param( 'sessiontype', 'anon' ); $session->param( 'interface', $type); } + $session->flush; } # END unless ($userid) + + if ( $auth_state eq 'logged_in' ) { + $auth_state = 'completed'; + + # Auth is completed unless an additional auth is needed + if ( $require_2FA ) { + my $patron = Koha::Patrons->find({userid => $userid}); + if ( $patron->auth_method eq 'two-factor' ) { + # Ask for the OTP token + $auth_state = 'additional-auth-needed'; + $session->param('waiting-for-2FA', 1); + %info = ();# We remove the warnings/errors we may have set incorrectly before + } + } + } + # finished authentification, now respond - if ( $loggedin || $authnotrequired ) - { + if ( $auth_state eq 'completed' || $authnotrequired ) { # successful login - unless ($cookie) { - $cookie = $query->cookie( + unless (@$cookie) { + $cookie = $cookie_mgr->replace_in_list( $cookie, $query->cookie( -name => 'CGISESSID', -value => '', -HttpOnly => 1, -secure => ( C4::Context->https_enabled() ? 1 : 0 ), - ); + -sameSite => 'Lax', + )); } track_login_daily( $userid ); @@ -1344,16 +1355,17 @@ sub checkauth { # # + my $patron = Koha::Patrons->find({ userid => $q_userid }); # Not necessary logged in! + # get the inputs from the incoming query my @inputs = (); + my @inputs_to_clean = qw( userid password ticket logout.x otp_token ); foreach my $name ( param $query) { - (next) if ( $name eq 'userid' || $name eq 'password' || $name eq 'ticket' ); + next if grep { $name eq $_ } @inputs_to_clean; my @value = $query->multi_param($name); push @inputs, { name => $name, value => $_ } for @value; } - my $patron = Koha::Patrons->find({ userid => $q_userid }); # Not necessary logged in! - my $LibraryNameTitle = C4::Context->preference("LibraryName"); $LibraryNameTitle =~ s/<(?:\/?)(?:br|p)\s*(?:\/?)>/ /sgi; $LibraryNameTitle =~ s/<(?:[^<>'"]|'(?:[^']*)'|"(?:[^"]*)")*>//sg; @@ -1366,14 +1378,11 @@ sub checkauth { script_name => get_script_name(), casAuthentication => C4::Context->preference("casAuthentication"), shibbolethAuthentication => $shib, - SessionRestrictionByIP => C4::Context->preference("SessionRestrictionByIP"), suggestion => C4::Context->preference("suggestion"), virtualshelves => C4::Context->preference("virtualshelves"), LibraryName => "" . C4::Context->preference("LibraryName"), LibraryNameTitle => "" . $LibraryNameTitle, opacuserlogin => C4::Context->preference("opacuserlogin"), - OpacNav => C4::Context->preference("OpacNav"), - OpacNavBottom => C4::Context->preference("OpacNavBottom"), OpacFavicon => C4::Context->preference("OpacFavicon"), opacreadinghistory => C4::Context->preference("opacreadinghistory"), opaclanguagesdisplay => C4::Context->preference("opaclanguagesdisplay"), @@ -1397,19 +1406,28 @@ sub checkauth { PatronSelfRegistration => C4::Context->preference("PatronSelfRegistration"), PatronSelfRegistrationDefaultCategory => C4::Context->preference("PatronSelfRegistrationDefaultCategory"), opac_css_override => $ENV{'OPAC_CSS_OVERRIDE'}, - too_many_login_attempts => ( $patron and $patron->account_locked ) + too_many_login_attempts => ( $patron and $patron->account_locked ), + password_has_expired => ( $patron and $patron->password_expired ), ); $template->param( SCO_login => 1 ) if ( $query->param('sco_user_login') ); $template->param( SCI_login => 1 ) if ( $query->param('sci_user_login') ); $template->param( OpacPublic => C4::Context->preference("OpacPublic") ); $template->param( loginprompt => 1 ) unless $info{'nopermission'}; + if ( $auth_state eq 'additional-auth-needed' ) { + my $patron = Koha::Patrons->find( { userid => $userid } ); + $template->param( + TwoFA_prompt => 1, + invalid_otp_token => $invalid_otp_token, + notice_email_address => $patron->notice_email_address, # We could also pass logged_in_user if necessary + ); + } if ( $type eq 'opac' ) { require Koha::Virtualshelves; my $some_public_shelves = Koha::Virtualshelves->get_some_shelves( { - category => 2, + public => 1, } ); $template->param( @@ -1420,8 +1438,9 @@ sub checkauth { if ($cas) { # Is authentication against multiple CAS servers enabled? - if ( C4::Auth_with_cas::multipleAuth && !$casparam ) { - my $casservers = C4::Auth_with_cas::getMultipleAuth(); + require C4::Auth_with_cas; + if ( multipleAuth() && !$casparam ) { + my $casservers = getMultipleAuth(); my @tmplservers; foreach my $key ( keys %$casservers ) { push @tmplservers, { name => $key, value => login_cas_url( $query, $key, $type ) . "?cas=$key" }; @@ -1472,7 +1491,8 @@ sub checkauth { { type => 'text/html', charset => 'utf-8', cookie => $cookie, - 'X-Frame-Options' => 'SAMEORIGIN' + 'X-Frame-Options' => 'SAMEORIGIN', + -sameSite => 'Lax' } ), $template->output; @@ -1509,6 +1529,10 @@ Possible return values in C<$status> are: =item "expired -- session cookie has expired; API user should resubmit userid and password +=item "restricted" -- The IP has changed (if SessionRestrictionByIP) + +=item "additional-auth-needed -- User is in an authentication process that is not finished + =back =cut @@ -1517,7 +1541,6 @@ sub check_api_auth { my $query = shift; my $flagsrequired = shift; - my $dbh = C4::Context->dbh; my $timeout = _timeout_syspref(); unless ( C4::Context->preference('Version') ) { @@ -1535,80 +1558,28 @@ sub check_api_auth { return ( "maintenance", undef, undef ); } - # FIXME -- most of what follows is a copy-and-paste - # of code from checkauth. There is an obvious need - # for refactoring to separate the various parts of - # the authentication code, but as of 2007-11-19 this - # is deferred so as to not introduce bugs into the - # regular authentication code for Koha 3.0. - - # see if we have a valid session cookie already - # however, if a userid parameter is present (i.e., from - # a form submission, assume that any current cookie - # is to be ignored - my $sessionID = undef; + my ( $sessionID, $session ); unless ( $query->param('userid') ) { $sessionID = $query->cookie("CGISESSID"); } if ( $sessionID && not( $cas && $query->param('PT') ) ) { - my $session = get_session($sessionID); - C4::Context->_new_userenv($sessionID); - if ($session) { - C4::Context->interface($session->param('interface')); - C4::Context->set_userenv( - $session->param('number'), $session->param('id'), - $session->param('cardnumber'), $session->param('firstname'), - $session->param('surname'), $session->param('branch'), - $session->param('branchname'), $session->param('flags'), - $session->param('emailaddress'), $session->param('shibboleth'), - $session->param('desk_id'), $session->param('desk_name'), - $session->param('register_id'), $session->param('register_name') - ); - my $ip = $session->param('ip'); - my $lasttime = $session->param('lasttime'); - my $userid = $session->param('id'); - if ( $lasttime < time() - $timeout ) { + my $return; + ( $return, $session, undef ) = check_cookie_auth( + $sessionID, $flagsrequired, { remote_addr => $ENV{REMOTE_ADDR} } ); - # time out - $session->delete(); - $session->flush; - C4::Context->_unset_userenv($sessionID); - $userid = undef; - $sessionID = undef; - return ( "expired", undef, undef ); - } elsif ( C4::Context->preference('SessionRestrictionByIP') && $ip ne $ENV{'REMOTE_ADDR'} ) { + return ( $return, undef, undef ) # Cookie auth failed + if $return ne "ok"; + + my $cookie = $query->cookie( + -name => 'CGISESSID', + -value => $session->id, + -HttpOnly => 1, + -secure => ( C4::Context->https_enabled() ? 1 : 0 ), + -sameSite => 'Lax' + ); + return ( $return, $cookie, $session ); # return == 'ok' here - # IP address changed - $session->delete(); - $session->flush; - C4::Context->_unset_userenv($sessionID); - $userid = undef; - $sessionID = undef; - return ( "expired", undef, undef ); - } else { - my $cookie = $query->cookie( - -name => 'CGISESSID', - -value => $session->id, - -HttpOnly => 1, - -secure => ( C4::Context->https_enabled() ? 1 : 0 ), - ); - $session->param( 'lasttime', time() ); - my $flags = haspermission( $userid, $flagsrequired ); - if ($flags) { - return ( "ok", $cookie, $sessionID ); - } else { - $session->delete(); - $session->flush; - C4::Context->_unset_userenv($sessionID); - $userid = undef; - $sessionID = undef; - return ( "failed", undef, undef ); - } - } - } else { - return ( "expired", undef, undef ); - } } else { # new login @@ -1619,11 +1590,10 @@ sub check_api_auth { # Proxy CAS auth if ( $cas && $query->param('PT') ) { my $retuserid; - $debug and print STDERR "## check_api_auth - checking CAS\n"; # In case of a CAS authentication, we use the ticket instead of the password my $PT = $query->param('PT'); - ( $return, $cardnumber, $userid, $cas_ticket ) = check_api_auth_cas( $dbh, $PT, $query ); # EXTERNAL AUTH + ( $return, $cardnumber, $userid, $cas_ticket ) = check_api_auth_cas( $PT, $query ); # EXTERNAL AUTH } else { # User / password auth @@ -1633,7 +1603,7 @@ sub check_api_auth { return ( "failed", undef, undef ); } my $newuserid; - ( $return, $cardnumber, $newuserid, $cas_ticket ) = checkpw( $dbh, $userid, $password, $query ); + ( $return, $cardnumber, $newuserid, $cas_ticket ) = checkpw( $userid, $password, $query ); } if ( $return and haspermission( $userid, $flagsrequired ) ) { @@ -1647,6 +1617,7 @@ sub check_api_auth { -value => $sessionID, -HttpOnly => 1, -secure => ( C4::Context->https_enabled() ? 1 : 0 ), + -sameSite => 'Lax' ); if ( $return == 1 ) { my ( @@ -1654,6 +1625,7 @@ sub check_api_auth { $userflags, $branchcode, $branchname, $emailaddress ); + my $dbh = C4::Context->dbh; my $sth = $dbh->prepare( "select borrowernumber, firstname, surname, flags, borrowers.branchcode, branches.branchname as branchname, email from borrowers left join branches on borrowers.branchcode=branches.branchcode where userid=?" @@ -1693,7 +1665,7 @@ sub check_api_auth { my $library = Koha::Libraries->find($branchcode); $branchname = $library? $library->branchname: ''; } - my $branches = { map { $_->branchcode => $_->unblessed } Koha::Libraries->search }; + my $branches = { map { $_->branchcode => $_->unblessed } Koha::Libraries->search->as_list }; foreach my $br ( keys %$branches ) { # now we work with the treatment of ip @@ -1737,7 +1709,7 @@ sub check_api_auth { =head2 check_cookie_auth - ($status, $sessionId) = check_api_auth($cookie, $userflags); + ($status, $sessionId) = check_cookie_auth($cookie, $userflags); Given a CGISESSID cookie set during a previous login to Koha, determine if the user has the privileges specified by C<$userflags>. C<$userflags> @@ -1756,103 +1728,106 @@ Possible return values in C<$status> are: =item "ok" -- user authenticated; C<$sessionID> have valid values. +=item "anon" -- user not authenticated but valid for anonymous session. + =item "failed" -- credentials are not correct; C<$sessionid> are undef =item "maintenance" -- DB is in maintenance mode; no login possible at the moment =item "expired -- session cookie has expired; API user should resubmit userid and password +=item "restricted" -- The IP has changed (if SessionRestrictionByIP) + =back =cut sub check_cookie_auth { - my $cookie = shift; + my $sessionID = shift; my $flagsrequired = shift; my $params = shift; my $remote_addr = $params->{remote_addr} || $ENV{REMOTE_ADDR}; - my $dbh = C4::Context->dbh; - my $timeout = _timeout_syspref(); - unless ( C4::Context->preference('Version') ) { + my $skip_version_check = $params->{skip_version_check}; # Only for checkauth - # database has not been installed yet - return ( "maintenance", undef ); - } - my $kohaversion = Koha::version(); - $kohaversion =~ s/(.*\..*)\.(.*)\.(.*)/$1$2$3/; - if ( C4::Context->preference('Version') < $kohaversion ) { + unless ( $skip_version_check ) { + unless ( C4::Context->preference('Version') ) { - # database in need of version update; assume that - # no API should be called while databsae is in - # this condition. - return ( "maintenance", undef ); + # database has not been installed yet + return ( "maintenance", undef ); + } + my $kohaversion = Koha::version(); + $kohaversion =~ s/(.*\..*)\.(.*)\.(.*)/$1$2$3/; + if ( C4::Context->preference('Version') < $kohaversion ) { + + # database in need of version update; assume that + # no API should be called while databsae is in + # this condition. + return ( "maintenance", undef ); + } } - # FIXME -- most of what follows is a copy-and-paste - # of code from checkauth. There is an obvious need - # for refactoring to separate the various parts of - # the authentication code, but as of 2007-11-23 this - # is deferred so as to not introduce bugs into the - # regular authentication code for Koha 3.0. - # see if we have a valid session cookie already # however, if a userid parameter is present (i.e., from # a form submission, assume that any current cookie # is to be ignored - unless ( defined $cookie and $cookie ) { + unless ( $sessionID ) { return ( "failed", undef ); } - my $sessionID = $cookie; + C4::Context::_unset_userenv($sessionID); # remove old userenv first my $session = get_session($sessionID); - C4::Context->_new_userenv($sessionID); if ($session) { - C4::Context->interface($session->param('interface')); - C4::Context->set_userenv( - $session->param('number'), $session->param('id'), - $session->param('cardnumber'), $session->param('firstname'), - $session->param('surname'), $session->param('branch'), - $session->param('branchname'), $session->param('flags'), - $session->param('emailaddress'), $session->param('shibboleth'), - $session->param('desk_id'), $session->param('desk_name'), - $session->param('register_id'), $session->param('register_name') - ); - + my $userid = $session->param('id'); my $ip = $session->param('ip'); my $lasttime = $session->param('lasttime'); - my $userid = $session->param('id'); - if ( $lasttime < time() - $timeout ) { + my $timeout = _timeout_syspref(); + if ( !$lasttime || ( $lasttime < time() - $timeout ) ) { # time out $session->delete(); $session->flush; - C4::Context->_unset_userenv($sessionID); - $userid = undef; - $sessionID = undef; return ("expired", undef); - } elsif ( C4::Context->preference('SessionRestrictionByIP') && $ip ne $remote_addr ) { + } elsif ( C4::Context->preference('SessionRestrictionByIP') && $ip ne $remote_addr ) { # IP address changed $session->delete(); $session->flush; - C4::Context->_unset_userenv($sessionID); - $userid = undef; - $sessionID = undef; - return ( "expired", undef ); - } else { + return ( "restricted", undef, { old_ip => $ip, new_ip => $remote_addr}); + + } elsif ( $userid ) { $session->param( 'lasttime', time() ); + my $patron = Koha::Patrons->find({ userid => $userid }); + $patron = Koha::Patron->find({ cardnumber => $userid }) unless $patron; + return ("password_expired", undef ) if $patron->password_expired; my $flags = defined($flagsrequired) ? haspermission( $userid, $flagsrequired ) : 1; if ($flags) { - return ( "ok", $sessionID ); + C4::Context->_new_userenv($sessionID); + C4::Context->interface($session->param('interface')); + C4::Context->set_userenv( + $session->param('number'), $session->param('id') // '', + $session->param('cardnumber'), $session->param('firstname'), + $session->param('surname'), $session->param('branch'), + $session->param('branchname'), $session->param('flags'), + $session->param('emailaddress'), $session->param('shibboleth'), + $session->param('desk_id'), $session->param('desk_name'), + $session->param('register_id'), $session->param('register_name') + ); + return ( "additional-auth-needed", $session ) + if $session->param('waiting-for-2FA'); + + return ( "ok", $session ); } else { $session->delete(); $session->flush; - C4::Context->_unset_userenv($sessionID); - $userid = undef; - $sessionID = undef; return ( "failed", undef ); } + + } else { + C4::Context->_new_userenv($sessionID); + C4::Context->interface($session->param('interface')); + C4::Context->set_userenv( undef, q{} ); + return ( "anon", $session ); } } else { return ( "expired", undef ); @@ -1899,7 +1874,15 @@ sub _get_session_params { sub get_session { my $sessionID = shift; my $params = _get_session_params(); - return CGI::Session->new( $params->{dsn}, $sessionID, $params->{dsn_args} ); + my $session; + if( $sessionID ) { # find existing + CGI::Session::ErrorHandler->set_error( q{} ); # clear error, cpan issue #111463 + $session = CGI::Session->load( $params->{dsn}, $sessionID, $params->{dsn_args} ); + } else { + $session = CGI::Session->new( $params->{dsn}, $sessionID, $params->{dsn_args} ); + # no need to flush here + } + return $session; } @@ -1908,7 +1891,7 @@ sub get_session { # Currently it's only passed from C4::SIP::ILS::Patron::check_password, but # not having a userenv defined could cause a crash. sub checkpw { - my ( $dbh, $userid, $password, $query, $type, $no_set_userenv ) = @_; + my ( $userid, $password, $query, $type, $no_set_userenv ) = @_; $type = 'opac' unless $type; # Get shibboleth login attribute @@ -1928,10 +1911,9 @@ sub checkpw { # 0 if auth is nok # -1 if user bind failed (LDAP only) - if ( $patron and $patron->account_locked ) { + if ( $patron and ( $patron->account_locked ) ) { # Nothing to check, account is locked } elsif ($ldap && defined($password)) { - $debug and print STDERR "## checkpw - checking LDAP\n"; my ( $retval, $retcard, $retuserid ) = checkpw_ldap(@_); # EXTERNAL AUTH if ( $retval == 1 ) { @return = ( $retval, $retcard, $retuserid ); @@ -1940,12 +1922,11 @@ sub checkpw { $check_internal_as_fallback = 1 if $retval == 0; } elsif ( $cas && $query && $query->param('ticket') ) { - $debug and print STDERR "## checkpw - checking CAS\n"; # In case of a CAS authentication, we use the ticket instead of the password my $ticket = $query->param('ticket'); $query->delete('ticket'); # remove ticket to come back to original URL - my ( $retval, $retcard, $retuserid, $cas_ticket ) = checkpw_cas( $dbh, $ticket, $query, $type ); # EXTERNAL AUTH + my ( $retval, $retcard, $retuserid, $cas_ticket ) = checkpw_cas( $ticket, $query, $type ); # EXTERNAL AUTH if ( $retval ) { @return = ( $retval, $retcard, $retuserid, $cas_ticket ); } else { @@ -1959,8 +1940,6 @@ sub checkpw { # time around. elsif ( $shib && $shib_login && !$password ) { - $debug and print STDERR "## checkpw - checking Shibboleth\n"; - # In case of a Shibboleth authentication, we expect a shibboleth user attribute # (defined under shibboleth mapping in koha-conf.xml) to contain the login of the # shibboleth-authenticated user @@ -1979,13 +1958,16 @@ sub checkpw { # INTERNAL AUTH if ( $check_internal_as_fallback ) { - @return = checkpw_internal( $dbh, $userid, $password, $no_set_userenv); + @return = checkpw_internal( $userid, $password, $no_set_userenv); $passwd_ok = 1 if $return[0] > 0; # 1 or 2 } if( $patron ) { if ( $passwd_ok ) { $patron->update({ login_attempts => 0 }); + if( $patron->password_expired ){ + @return = (-2); + } } elsif( !$patron->account_locked ) { $patron->update({ login_attempts => $patron->login_attempts + 1 }); } @@ -2002,11 +1984,12 @@ sub checkpw { } sub checkpw_internal { - my ( $dbh, $userid, $password, $no_set_userenv ) = @_; + my ( $userid, $password, $no_set_userenv ) = @_; $password = Encode::encode( 'UTF-8', $password ) if Encode::is_utf8($password); + my $dbh = C4::Context->dbh; my $sth = $dbh->prepare( "select password,cardnumber,borrowernumber,userid,firstname,surname,borrowers.branchcode,branches.branchname,flags from borrowers join branches on borrowers.branchcode=branches.branchcode where userid=?" @@ -2266,7 +2249,7 @@ sub in_iprange { if (scalar @allowedipranges > 0) { my @rangelist; eval { @rangelist = Net::CIDR::range2cidr(@allowedipranges); }; return 0 if $@; - eval { $result = Net::CIDR::cidrlookup($ENV{'REMOTE_ADDR'}, @rangelist) } || ( $ENV{DEBUG} && warn 'cidrlookup failed for ' . join(' ',@rangelist) ); + eval { $result = Net::CIDR::cidrlookup($ENV{'REMOTE_ADDR'}, @rangelist) } || Koha::Logger->get->warn('cidrlookup failed for ' . join(' ',@rangelist) ); } return $result ? 1 : 0; }