X-Git-Url: http://koha-dev.rot13.org:8081/gitweb/?a=blobdiff_plain;f=C4%2FAuth.pm;h=9ebc3aee4340f0b81c2e81cc68dba75d90f841c6;hb=6eeb9bc1b30b437febd19e5a60f5ae2a0283e761;hp=f7372673c89274e3c70df96b939c34c5e08f12f4;hpb=89b9c441faf862aa1c5c7b8420c15fe8e9afcbd2;p=koha-ffzg.git diff --git a/C4/Auth.pm b/C4/Auth.pm index f7372673c8..9ebc3aee43 100644 --- a/C4/Auth.pm +++ b/C4/Auth.pm @@ -35,6 +35,7 @@ use Koha; use Koha::Logger; use Koha::Caches; 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::Library::Groups; @@ -48,6 +49,7 @@ 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 Koha::CookieManager; # use utf8; @@ -154,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(); @@ -243,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, @@ -498,7 +503,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,7 +524,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"), @@ -565,9 +568,10 @@ sub get_template_and_user { 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; } } @@ -655,11 +659,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 ); @@ -839,12 +839,15 @@ sub checkauth { 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 %info; my ( $userid, $cookie, $sessionID, $flags ); + $cookie = []; my $logout = $query->param('logout.x'); my $anon_search_history; @@ -855,6 +858,9 @@ 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; + my $auth_challenge_complete; # Basic authentication is incompatible with the use of Shibboleth, # as Shibboleth may return REMOTE_USER as a Shibboleth attribute, @@ -868,13 +874,13 @@ 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) { @@ -882,17 +888,48 @@ sub checkauth { } 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 } ); + if ( $return eq 'ok' || $return eq 'additional-auth-needed' ) { + $userid = $session->param('id'); + } + + $additional_auth_needed = ( $return eq 'additional-auth-needed' ) ? 1 : 0; + + # 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 + && $additional_auth_needed + && ( my $otp_token = $query->param('otp_token') ) ) + { + my $patron = Koha::Patrons->find( { userid => $userid } ); + my $auth = Koha::Auth::TwoFactorAuth::get_auth( { patron => $patron } ); + my $verified = $auth->verify($otp_token); + $auth->clear; + if ( $verified ) { + # The token is correct, the user is fully logged in! + $additional_auth_needed = 0; + $session->param( 'waiting-for-2FA', 0 ); + $return = "ok"; + $auth_challenge_complete = 1; + + # This is an ugly trick to pass the test + # $query->param('koha_login_context') && ( $q_userid ne $userid ) + # few lines later + $q_userid = $userid; + } + else { + $invalid_otp_token = 1; + } + } + if ( $return eq 'ok' ) { Koha::Logger->get->debug(sprintf "AUTH_SESSION: (%s)\t%s %s - %s", map { $session->param($_) || q{} } qw(cardnumber firstname surname branch)); - my $s_userid = $session->param('id'); - $userid = $s_userid; - - if ( ( $query->param('koha_login_context') && ( $q_userid ne $s_userid ) ) + 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'} ) ) { @@ -902,33 +939,18 @@ sub checkauth { $anon_search_history = $session->param('search_history'); $session->delete(); $session->flush; + $cookie = $cookie_mgr->clear_unless( $query->cookie, @$cookie ); C4::Context::_unset_userenv($sessionID); - } - elsif ($logout) { - - # 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 ($cas and $caslogout) { - logout_cas($query, $type); - } + $sessionID = undef; + } elsif (!$logout) { - # 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 { - - $cookie = $query->cookie( + $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', + )); $flags = haspermission( $userid, $flagsrequired ); if ($flags) { @@ -948,14 +970,41 @@ sub checkauth { } } - unless ( $loggedin ) { + if ( ( !$loggedin && !$additional_auth_needed ) || $logout ) { $sessionID = undef; $userid = undef; } + if ($logout) { + + # voluntary logout the user + # check wether the user was using their shibboleth session or a local one + my $shibSuccess = C4::Context->userenv->{'shibboleth'}; + if ( $session ) { + $session->delete(); + $session->flush; + } + C4::Context::_unset_userenv($sessionID); + $cookie = $cookie_mgr->clear_unless( $query->cookie, @$cookie ); + + if ($cas and $caslogout) { + logout_cas($query, $type); + } + + # 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; + $additional_auth_needed = 0; + } + unless ( $userid ) { #we initiate a session prior to checking for a username to allow for anonymous sessions... - $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 @@ -966,12 +1015,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 => $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"; @@ -1166,12 +1216,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; } } @@ -1247,19 +1298,30 @@ sub checkauth { $session->param( 'sessiontype', 'anon' ); $session->param( 'interface', $type); } + $session->flush; } # END unless ($userid) + if ( $require_2FA && ( $loggedin && !$auth_challenge_complete)) { + my $patron = Koha::Patrons->find({userid => $userid}); + if ( $patron->auth_method eq 'two-factor' ) { + # Ask for the OTP token + $additional_auth_needed = 1; + $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 ( ( $loggedin || $authnotrequired ) && !$additional_auth_needed ) { # 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 ); @@ -1285,16 +1347,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; @@ -1342,6 +1405,12 @@ sub checkauth { $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 ( $additional_auth_needed ) { + $template->param( + TwoFA_prompt => 1, + invalid_otp_token => $invalid_otp_token, + ); + } if ( $type eq 'opac' ) { require Koha::Virtualshelves; @@ -1411,7 +1480,8 @@ sub checkauth { { type => 'text/html', charset => 'utf-8', cookie => $cookie, - 'X-Frame-Options' => 'SAMEORIGIN' + 'X-Frame-Options' => 'SAMEORIGIN', + -sameSite => 'Lax' } ), $template->output; @@ -1450,6 +1520,8 @@ Possible return values in C<$status> are: =item "restricted" -- The IP has changed (if SessionRestrictionByIP) +=item "additional-auth-needed -- User is in an authentication process that is not finished + =back =cut @@ -1494,8 +1566,9 @@ sub check_api_auth { -value => $session->id, -HttpOnly => 1, -secure => ( C4::Context->https_enabled() ? 1 : 0 ), + -sameSite => 'Lax' ); - return ( $return, undef, $session ); + return ( $return, $cookie, $session ); # return == 'ok' here } else { @@ -1534,6 +1607,7 @@ sub check_api_auth { -value => $sessionID, -HttpOnly => 1, -secure => ( C4::Context->https_enabled() ? 1 : 0 ), + -sameSite => 'Lax' ); if ( $return == 1 ) { my ( @@ -1624,7 +1698,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> @@ -1725,6 +1799,9 @@ sub check_cookie_auth { $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(); @@ -1789,7 +1866,7 @@ sub get_session { $session = CGI::Session->load( $params->{dsn}, $sessionID, $params->{dsn_args} ); } else { $session = CGI::Session->new( $params->{dsn}, $sessionID, $params->{dsn_args} ); - # $session->flush; + # no need to flush here } return $session; }