diff --git a/Koha/Auth/Identity/Referer.pm b/Koha/Auth/Identity/Referer.pm new file mode 100644 index 0000000000..166136c3a4 --- /dev/null +++ b/Koha/Auth/Identity/Referer.pm @@ -0,0 +1,121 @@ +package Koha::Auth::Identity::Referer; + +# +# This file is part of Koha. +# +# 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. +# +# 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. +# +# You should have received a copy of the GNU General Public License +# along with Koha; if not, see . + +use Modern::Perl; + +use C4::Context; + +=head1 NAME + +Koha::Auth::Identity::Referer + +=head1 SYNOPSIS + + use Koha::Auth::Identity::Referer; + + Koha::Auth::Identity::Referer->store_referer({ + referer => $c->req->headers->referer, + interface => $interface, + session => $session, + }); + + my $target_uri = Koha::Auth::Identity::Referer->get_referer({ + session => $session, + }); + +=head1 DESCRIPTION + + Class for working with HTTP referrers specifically for + when doing SSO. + +=head1 FUNCTIONS + +=head2 store_referer + + Koha::Auth::Identity::Referer->store_referer({ + referer => $c->req->headers->referer, + interface => $interface, + session => $session, + }); + + If the referer is for a Koha URL, then it gets saved + in a session variable for use after a successful SSO login. + +=cut + +sub store_referer { + my ( $class, $args ) = @_; + my $referer = $args->{referer}; + my $interface = $args->{interface}; + my $session = $args->{session}; + my $valid_referer; + if ( $referer && $interface && $session ) { + my $base_url; + if ( $interface eq 'opac' ) { + $base_url = C4::Context->preference('OPACBaseURL'); + } else { + $base_url = C4::Context->preference('staffClientBaseURL'); + } + if ( $base_url && $referer && $referer =~ /^\Q$base_url\E/ ) { + my $referer_uri = URI->new($referer); + + #NOTE: Remove logout.x query param to prevent logout loops + $referer_uri->query_param_delete('logout.x') if $referer_uri->query_param('logout.x'); + my $referer_to_save = $referer_uri->path_query(); + if ($referer_to_save) { + + #NOTE: Don't bother saving a root referer + #NOTE: Don't save opac-main.pl because it should redirect to opac-user.pl + unless ( ( $referer_to_save eq '/' ) || ( $referer_to_save eq '/cgi-bin/koha/opac-main.pl' ) ) { + $session->param( 'idp_referer', $referer_to_save ); + $session->flush(); + } + } + } + } + return $valid_referer; +} + +=head2 get_referer + + my $target_uri = Koha::Auth::Identity::Referer->get_referer({ + session => $session, + }); + + If a referer was stored in a session variable, then we retrieve + it for use by SSO functionality. That is, after a successful SSO, we'll + redirect to this referer, so we wind up back where we started. + +=cut + +sub get_referer { + my ( $class, $args ) = @_; + my $referer; + my $session = $args->{session}; + if ($session) { + my $idp_referer = $session->param('idp_referer'); + if ($idp_referer) { + $referer = $idp_referer; + $session->clear('idp_referer'); + $session->flush(); + } + } + return $referer; +} + +1; diff --git a/Koha/REST/V1/OAuth/Client.pm b/Koha/REST/V1/OAuth/Client.pm index e724b20f3b..87343a1b15 100644 --- a/Koha/REST/V1/OAuth/Client.pm +++ b/Koha/REST/V1/OAuth/Client.pm @@ -25,6 +25,8 @@ use Scalar::Util qw(blessed); use Try::Tiny; use Koha::Token; use URI::Escape qw(uri_escape_utf8); +use Koha::Session; +use Koha::Auth::Identity::Referer; =head1 NAME @@ -63,6 +65,13 @@ sub login { $uri = '/cgi-bin/koha/mainpage.pl'; } + my $current_session; + my $current_session_cookie = $c->req->cookie('CGISESSID'); + if ($current_session_cookie) { + my $current_session_id = $current_session_cookie->value; + $current_session = Koha::Session->get_session( { sessionID => $current_session_id } ); + } + unless ( $provider_config && $provider_config->{authorize_url} ) { my $error = "No configuration found for your provider"; return $c->redirect_to( $uri . "?auth_error=$error" ); @@ -98,6 +107,14 @@ sub login { # initial request, generate CSRF token $state = Koha::Token->new->generate_csrf( { session_id => $c->req->cookie('CGISESSID')->value } ); + + Koha::Auth::Identity::Referer->store_referer( + { + referer => $c->req->headers->referer, + interface => $interface, + session => $current_session, + } + ); } return $c->oauth2->get_token_p( @@ -133,6 +150,14 @@ sub login { $c->cookie( CGISESSID => $session_id, { path => "/" } ); + my $target_uri = Koha::Auth::Identity::Referer->get_referer( + { + session => $current_session, + } + ); + if ($target_uri) { + $uri = $target_uri; + } $c->redirect_to($uri); } catch { my $error = $_; diff --git a/t/Koha/Auth/Identity/Referer.t b/t/Koha/Auth/Identity/Referer.t new file mode 100755 index 0000000000..3fdc56e8bd --- /dev/null +++ b/t/Koha/Auth/Identity/Referer.t @@ -0,0 +1,234 @@ +#!/usr/bin/perl + +# This file is part of Koha. +# +# 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. +# +# 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. +# +# You should have received a copy of the GNU General Public License +# along with Koha; if not, see . + +use Modern::Perl; + +use Test::NoWarnings; +use Test::More tests => 10; +use t::lib::Mocks; + +use C4::Auth qw(); + +use_ok('Koha::Auth::Identity::Referer'); + +subtest 'Typical staff success' => sub { + plan tests => 1; + my $session = C4::Auth::get_session(""); + + t::lib::Mocks::mock_preference( 'staffClientBaseURL', 'http://mystaffadmin' ); + + Koha::Auth::Identity::Referer->store_referer( + { + referer => 'http://mystaffadmin/cgi-bin/koha/catalogue/search.pl?q=test', + interface => 'staff', + session => $session, + } + ); + + my $target_uri = Koha::Auth::Identity::Referer->get_referer( + { + session => $session, + } + ); + + is( $target_uri, '/cgi-bin/koha/catalogue/search.pl?q=test', 'Correct staff referer processed' ); + + $session->delete; + $session->flush; +}; + +subtest 'External staff referrers do not get saved' => sub { + plan tests => 1; + my $session = C4::Auth::get_session(""); + + t::lib::Mocks::mock_preference( 'staffClientBaseURL', 'http://mystaffadmin' ); + + Koha::Auth::Identity::Referer->store_referer( + { + referer => 'http://externalreferer', + interface => 'staff', + session => $session, + } + ); + + my $target_uri = Koha::Auth::Identity::Referer->get_referer( + { + session => $session, + } + ); + + is( $target_uri, undef, 'Bad referrers do not get stored' ); + + $session->delete; + $session->flush; +}; + +subtest 'Staff logout referer does not lead to loop' => sub { + plan tests => 1; + my $session = C4::Auth::get_session(""); + + t::lib::Mocks::mock_preference( 'staffClientBaseURL', 'http://mystaffadmin' ); + + Koha::Auth::Identity::Referer->store_referer( + { + referer => 'http://mystaffadmin/cgi-bin/koha/mainpage.pl?logout.x=1', + interface => 'staff', + session => $session, + } + ); + + my $target_uri = Koha::Auth::Identity::Referer->get_referer( + { + session => $session, + } + ); + + is( $target_uri, '/cgi-bin/koha/mainpage.pl', 'Staff logout URL does not lead to loop' ); + + $session->delete; + $session->flush; +}; + +subtest 'Typical OPAC success' => sub { + plan tests => 1; + my $session = C4::Auth::get_session(""); + + t::lib::Mocks::mock_preference( 'OPACBaseURL', 'http://myopac' ); + + Koha::Auth::Identity::Referer->store_referer( + { + referer => 'http://myopac/cgi-bin/koha/opac-detail.pl?biblionumber=999', + interface => 'opac', + session => $session, + } + ); + + my $target_uri = Koha::Auth::Identity::Referer->get_referer( + { + session => $session, + } + ); + + is( $target_uri, '/cgi-bin/koha/opac-detail.pl?biblionumber=999', 'Correct OPAC referer processed' ); + + $session->delete; + $session->flush; +}; + +subtest 'OPAC logout referer does not lead to loop' => sub { + plan tests => 1; + my $session = C4::Auth::get_session(""); + + t::lib::Mocks::mock_preference( 'OPACBaseURL', 'http://myopac' ); + + Koha::Auth::Identity::Referer->store_referer( + { + referer => 'http://myopac/cgi-bin/koha/opac-main.pl?logout.x=1', + interface => 'opac', + session => $session, + } + ); + + my $target_uri = Koha::Auth::Identity::Referer->get_referer( + { + session => $session, + } + ); + + is( $target_uri, undef, 'OPAC logout URL does not lead to loop' ); + + $session->delete; + $session->flush; +}; + +subtest 'Do not save referer to OPAC root' => sub { + plan tests => 1; + my $session = C4::Auth::get_session(""); + + t::lib::Mocks::mock_preference( 'OPACBaseURL', 'http://myopac' ); + + Koha::Auth::Identity::Referer->store_referer( + { + referer => 'http://myopac', + interface => 'opac', + session => $session, + } + ); + + my $target_uri = Koha::Auth::Identity::Referer->get_referer( + { + session => $session, + } + ); + + is( $target_uri, undef, 'Do not save referer to OPAC root' ); + + $session->delete; + $session->flush; +}; + +subtest 'Do not save referer to OPAC root (ie opac-main.pl)' => sub { + plan tests => 1; + my $session = C4::Auth::get_session(""); + + t::lib::Mocks::mock_preference( 'OPACBaseURL', 'http://myopac' ); + + Koha::Auth::Identity::Referer->store_referer( + { + referer => 'http://myopac/cgi-bin/koha/opac-main.pl', + interface => 'opac', + session => $session, + } + ); + + my $target_uri = Koha::Auth::Identity::Referer->get_referer( + { + session => $session, + } + ); + + is( $target_uri, undef, 'Do not save referer to OPAC root (ie opac-main.pl)' ); + + $session->delete; + $session->flush; +}; + +subtest 'External OPAC referrers do not get saved' => sub { + plan tests => 1; + my $session = C4::Auth::get_session(""); + + t::lib::Mocks::mock_preference( 'OPACBaseURL', 'http://myopac' ); + + Koha::Auth::Identity::Referer->store_referer( + { + referer => 'http://externalreferer', + interface => 'opac', + session => $session, + } + ); + + my $target_uri = Koha::Auth::Identity::Referer->get_referer( + { + session => $session, + } + ); + + is( $target_uri, undef, 'Bad referrers do not get stored' ); + + $session->delete; + $session->flush; +};