From 2234f5ea25f9db51de157e8480d481d6abf996a6 Mon Sep 17 00:00:00 2001 From: Jeff Davis Date: Thu, 21 Nov 2019 14:43:33 -0800 Subject: [PATCH] RemoteAuth EZProxy CGI authentication This commit adds a RemoteAuth handler (and associated templates, configuration, and sample data) for EZProxy CGI user authentication: https://help.oclc.org/Library_Management/EZproxy/Authenticate_users/EZproxy_authentication_methods/CGI_authentication The user is presented with a login form. If their account is authorized, they will be redirected to EZProxy with a valid authentication ticket, allowing them to access online resources. If they are not authorized, an error message is displayed indicating why the auth attempt failed. The login form and error messages use Template Toolkit (TT2) templates and can be customized. Signed-off-by: Jeff Davis Signed-off-by: Jane Sandberg Signed-off-by: Galen Charlton --- Open-ILS/examples/apache_24/eg_startup.in | 2 +- Open-ILS/examples/apache_24/eg_vhost.conf.in | 33 ++++ .../lib/OpenILS/WWW/RemoteAuth/EZProxyCGI.pm | 201 +++++++++++++++++++++ .../src/templates/remoteauth/ezproxycgi/error.tt2 | 18 ++ .../src/templates/remoteauth/ezproxycgi/footer.tt2 | 2 + .../src/templates/remoteauth/ezproxycgi/header.tt2 | 14 ++ .../src/templates/remoteauth/ezproxycgi/login.tt2 | 6 + .../templates/remoteauth/ezproxycgi/login_form.tt2 | 23 +++ Open-ILS/tests/datasets/sql/remoteauth.sql | 11 ++ 9 files changed, 309 insertions(+), 1 deletion(-) create mode 100644 Open-ILS/src/perlmods/lib/OpenILS/WWW/RemoteAuth/EZProxyCGI.pm create mode 100644 Open-ILS/src/templates/remoteauth/ezproxycgi/error.tt2 create mode 100644 Open-ILS/src/templates/remoteauth/ezproxycgi/footer.tt2 create mode 100644 Open-ILS/src/templates/remoteauth/ezproxycgi/header.tt2 create mode 100644 Open-ILS/src/templates/remoteauth/ezproxycgi/login.tt2 create mode 100644 Open-ILS/src/templates/remoteauth/ezproxycgi/login_form.tt2 diff --git a/Open-ILS/examples/apache_24/eg_startup.in b/Open-ILS/examples/apache_24/eg_startup.in index 89afdfa2fc..b5c679a293 100755 --- a/Open-ILS/examples/apache_24/eg_startup.in +++ b/Open-ILS/examples/apache_24/eg_startup.in @@ -14,7 +14,7 @@ use OpenILS::WWW::EGWeb ('@sysconfdir@/opensrf_core.xml', 'OpenILS::WWW::EGCatLo use OpenILS::WWW::IDL2js ('@sysconfdir@/opensrf_core.xml'); use OpenILS::WWW::FlatFielder; use OpenILS::WWW::PhoneList ('@sysconfdir@/opensrf_core.xml'); -use OpenILS::WWW::RemoteAuth ('@sysconfdir@/opensrf_core.xml', 'OpenILS::WWW::RemoteAuth::Basic'); +use OpenILS::WWW::RemoteAuth ('@sysconfdir@/opensrf_core.xml', 'OpenILS::WWW::RemoteAuth::Basic', 'OpenILS::WWW::RemoteAuth::EZProxyCGI'); # Pass second argument of '1' to enable template caching. use OpenILS::WWW::PrintTemplate ('@sysconfdir@/opensrf_core.xml', 0); diff --git a/Open-ILS/examples/apache_24/eg_vhost.conf.in b/Open-ILS/examples/apache_24/eg_vhost.conf.in index 4953fc9762..b7183fe900 100644 --- a/Open-ILS/examples/apache_24/eg_vhost.conf.in +++ b/Open-ILS/examples/apache_24/eg_vhost.conf.in @@ -856,6 +856,39 @@ RewriteRule ^/openurl$ ${openurl:%1} [NE,PT] PerlSetVar OILSRemoteAuthClientPassword "demo123" + + SetHandler perl-script + PerlHandler OpenILS::WWW::RemoteAuth + Options +ExecCGI + + # access restricted to localhost by default; since this module provides no + # client authentiation, restricting access by IP or other means is stongly + # recommended + Require local + + # remoteauth profile name + PerlSetVar OILSRemoteAuthProfile "EZProxyCGI" + # Perl module for processing requests + PerlSetVar OILSRemoteAuthHandler "OpenILS::WWW::RemoteAuth::EZProxyCGI" + + # staff username/password for config lookup and patron retrieval + PerlSetVar OILSRemoteAuthClientUsername "admin" + PerlSetVar OILSRemoteAuthClientPassword "demo123" + + # Location of TT2 templates for EZProxy login form and error pages. + # Templates will be loaded from the following paths in reverse order. + PerlAddVar OILSRemoteAuthTemplatePath "@localstatedir@/templates/remoteauth/ezproxycgi" + #PerlAddVar OILSRemoteAuthTemplatePath "@localstatedir@/templates_localskin/remoteauth/ezproxycgi" + + # Locale (defaults to en_us) + #PerlAddVar OILSRemoteAuthLocale "en_us" + + # Base URI of your EZProxy server + PerlSetVar OILSRemoteAuthEZProxyBaseURI "http://example.com/ezproxy/" + + # shared secret used to generate EZProxy authentication ticket + PerlSetVar OILSRemoteAuthEZProxySecret "secret" + # Uncomment the following to force SSL for everything. Note that this defeats caching # and you will suffer a performance hit. diff --git a/Open-ILS/src/perlmods/lib/OpenILS/WWW/RemoteAuth/EZProxyCGI.pm b/Open-ILS/src/perlmods/lib/OpenILS/WWW/RemoteAuth/EZProxyCGI.pm new file mode 100644 index 0000000000..f18063e369 --- /dev/null +++ b/Open-ILS/src/perlmods/lib/OpenILS/WWW/RemoteAuth/EZProxyCGI.pm @@ -0,0 +1,201 @@ +# Copyright (C) 2019 BC Libraries Cooperative +# +# This program 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 2 +# of the License, or (at your option) any later version. +# +# This program 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 this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + +# ====================================================================== +# - RemoteAuth handler for EZProxy CGI authentication: +# https://help.oclc.org/Library_Management/EZproxy/Authenticate_users/EZproxy_authentication_methods/CGI_authentication +# ====================================================================== + +package OpenILS::WWW::RemoteAuth::EZProxyCGI; +use strict; use warnings; +use OpenILS::WWW::RemoteAuth; +use base "OpenILS::WWW::RemoteAuth"; + +use Apache2::Const -compile => qw(OK DECLINED FORBIDDEN AUTH_REQUIRED HTTP_INTERNAL_SERVER_ERROR REDIRECT HTTP_BAD_REQUEST); +use CGI qw(:all -utf8); +use URI::Escape; +use Digest::MD5 qw/md5_hex/; +use OpenSRF::EX qw(:try); +use OpenSRF::Utils::Logger qw/$logger/; +use OpenILS::Utils::CStoreEditor qw/:funcs/; +use OpenSRF::Utils::JSON; +use OpenILS::WWW::RemoteAuth::Template; + +sub new { + my( $class, $args ) = @_; + $args ||= {}; + $args->{cgi} ||= new CGI; + $class = ref $class || $class; + return bless($args, $class); +} + +sub r { + my ($self, $r) = @_; + $self->{r} = $r if $r; + return $self->{r}; +} + +# CGI handle +sub cgi { + my($self, $cgi) = @_; + $self->{cgi} = $cgi if $cgi; + return $self->{cgi}; +} + +sub process { + my ($self, $r) = @_; + my ($authtoken, $editor, $config); + + $self->r($r); + + # get params from incoming request + $self->{args} = { + id => scalar $self->cgi->param('id'), + password => scalar $self->cgi->param('password'), + url => scalar $self->cgi->param('url') + }; + + return $self->login unless (defined $self->{args}->{id} and defined $self->{args}->{password}); + + # authorize client + try { + my $client_user = $r->dir_config('OILSRemoteAuthClientUsername'); + my $client_pw = $r->dir_config('OILSRemoteAuthClientPassword'); + $authtoken = $self->do_client_auth($client_user, $client_pw); + } catch Error with { + $logger->error("RemoteAuth EZProxyCGI failed on client auth: @_"); + return Apache2::Const::HTTP_INTERNAL_SERVER_ERROR; + }; + return $self->client_not_authorized unless $authtoken; + + # load config + try { + $editor = new_editor( authtoken => $authtoken ); + $config = $self->load_config($editor, $r); + } catch Error with { + $logger->error("RemoteAuth EZProxyCGI failed on load config: @_"); + return Apache2::Const::HTTP_INTERNAL_SERVER_ERROR; + }; + return $self->backend_error unless $config; + + # authenticate patron + # this uses our util methods (success, patron_not_found, etc) to return the + # appropriate response depending on the outcome of the auth request: + # - if auth succeeded, redirect to EZProxy + # - otherwise, TT2-based error page or login form + return $self->do_patron_auth($editor, $config, $self->{args}->{id}, $self->{args}->{password}); +} + +sub ezproxy_url { + my ($r, $url, $user, $groups) = @_; + my ($packet, $ticket); + + my $secret = $r->dir_config('OILSRemoteAuthEZProxySecret'); + my $base_uri = $r->dir_config('OILSRemoteAuthEZProxyBaseURI'); + + return unless defined($secret); + + # generate ticket + $packet = '$u' . time(); + if ($groups) { + $packet .= '$g' . $groups; + } + $packet .= '$e'; + $ticket = md5_hex($secret . $user . $packet) . $packet; + + # escape our URL params + $user = uri_escape($user); + $ticket = uri_escape($ticket); + $url = uri_escape($url); + + return "$base_uri/login?user=$user&ticket=$ticket&url=$url"; +} + +# redirect to EZProxy URL on successful auth +sub success { + my ($self, $user) = @_; + my $redirect_url = ezproxy_url($self->r, $self->{args}->{url}, $user->usrname); + print $self->cgi->redirect($redirect_url); + return Apache2::Const::REDIRECT; +} + +# show login form +sub login { + my $self = shift; + my $ctx = { + page => 'login', + args => $self->{args} + }; + my $tt = new OpenILS::WWW::RemoteAuth::Template; + return $tt->process('login', $ctx, $self->r); +} + +# wrapper method for auth failures +sub error { + my ($self, $msg) = @_; + my $ctx = { + page => 'error', + args => $self->{args}, + error_msg => $msg + }; + my $tt = new OpenILS::WWW::RemoteAuth::Template; + return $tt->process('error', $ctx, $self->r); +} + +# generic backend error +sub backend_error { + my $self = shift; + return $self->error('backend_error'); +} + +# client error (e.g. missing params) +sub client_error { + my $self = shift; + return $self->error('client_error'); +} + +# client auth failed +sub client_not_authorized { + my $self = shift; + return $self->error('client_not_authorized'); +} + +# patron auth failed (bad password etc) +sub patron_not_authenticated { + my $self = shift; + return $self->error('patron_not_authenticated'); +} + +# patron does not exist or is inactive/deleted +sub patron_not_found { + my $self = shift; + return $self->error('patron_not_found'); +} + +# patron is barred or has blocking penalties +sub patron_is_blocked { + my $self = shift; + return $self->error('patron_is_blocked'); +} + +# patron is expired +sub patron_is_expired { + my $self = shift; + return $self->error('patron_is_expired'); +} + +1; + diff --git a/Open-ILS/src/templates/remoteauth/ezproxycgi/error.tt2 b/Open-ILS/src/templates/remoteauth/ezproxycgi/error.tt2 new file mode 100644 index 0000000000..d078a08f7f --- /dev/null +++ b/Open-ILS/src/templates/remoteauth/ezproxycgi/error.tt2 @@ -0,0 +1,18 @@ +[% INCLUDE "header.tt2" %] + +[% IF ctx.error_msg %] +
+ [% IF ctx.error_msg == 'patron_not_found' %] + Patron not found. + [% ELSIF ctx.error_msg == 'patron_is_blocked' %] + Your account is blocked. + [% ELSIF ctx.error_msg == 'patron_is_expired' %] + Your account is expired. + [% ELSE %] + Unable to authenticate. + [% END %] +
+[% END %] + +[% INCLUDE "login_form.tt2" %] +[% INCLUDE "footer.tt2" %] diff --git a/Open-ILS/src/templates/remoteauth/ezproxycgi/footer.tt2 b/Open-ILS/src/templates/remoteauth/ezproxycgi/footer.tt2 new file mode 100644 index 0000000000..2ab5c0d1fc --- /dev/null +++ b/Open-ILS/src/templates/remoteauth/ezproxycgi/footer.tt2 @@ -0,0 +1,2 @@ + + diff --git a/Open-ILS/src/templates/remoteauth/ezproxycgi/header.tt2 b/Open-ILS/src/templates/remoteauth/ezproxycgi/header.tt2 new file mode 100644 index 0000000000..fb4db7280c --- /dev/null +++ b/Open-ILS/src/templates/remoteauth/ezproxycgi/header.tt2 @@ -0,0 +1,14 @@ + + + + + + + [% IF ctx.page == 'error' %] + Login Failed + [% ELSE %] + Login + [% END %] + + + diff --git a/Open-ILS/src/templates/remoteauth/ezproxycgi/login.tt2 b/Open-ILS/src/templates/remoteauth/ezproxycgi/login.tt2 new file mode 100644 index 0000000000..e97f30aad9 --- /dev/null +++ b/Open-ILS/src/templates/remoteauth/ezproxycgi/login.tt2 @@ -0,0 +1,6 @@ +[% INCLUDE "header.tt2" %] + +

EZProxy Login

+[% INCLUDE "login_form.tt2" %] + +[% INCLUDE "footer.tt2" %] diff --git a/Open-ILS/src/templates/remoteauth/ezproxycgi/login_form.tt2 b/Open-ILS/src/templates/remoteauth/ezproxycgi/login_form.tt2 new file mode 100644 index 0000000000..82a2739757 --- /dev/null +++ b/Open-ILS/src/templates/remoteauth/ezproxycgi/login_form.tt2 @@ -0,0 +1,23 @@ + + diff --git a/Open-ILS/tests/datasets/sql/remoteauth.sql b/Open-ILS/tests/datasets/sql/remoteauth.sql index f967efb090..dff2454ce9 100644 --- a/Open-ILS/tests/datasets/sql/remoteauth.sql +++ b/Open-ILS/tests/datasets/sql/remoteauth.sql @@ -9,3 +9,14 @@ INSERT INTO config.remoteauth_profile VALUES ('Basic', 'Basic HTTP Authentication for SYS1', 2, TRUE, 1, TRUE, FALSE, FALSE, NULL, 1001); +INSERT INTO config.usr_activity_type (id, ewho, ewhat, ehow, egroup, label) VALUES + ( 1002, 'ezproxy', 'login', 'apache', 'authen', + oils_i18n_gettext(1002, 'RemoteAuth Login: EZProxy CGI Authentication', 'cuat', 'label')); + +-- config for EZProxy CGI Authentication (SYS2) +INSERT INTO config.remoteauth_profile + (name, description, context_org, enabled, perm, + restrict_to_org, allow_inactive, allow_expired, block_list, usr_activity_type) + VALUES ('EZProxyCGI', 'EZProxy CGI Authentication for SYS2', 3, TRUE, 1, + TRUE, FALSE, FALSE, NULL, 1002); + -- 2.11.0