From 9da9035d3ac96c3efed7c9f40374032a0ff777b1 Mon Sep 17 00:00:00 2001 From: Jeff Davis Date: Fri, 20 Dec 2019 16:28:55 -0800 Subject: [PATCH] LP#1887196: RemoteAuth PatronAPI authentication Signed-off-by: Jeff Davis Signed-off-by: Galen Charlton --- Open-ILS/examples/apache_24/eg_startup.in | 2 +- Open-ILS/examples/apache_24/eg_vhost.conf.in | 37 ++++ .../lib/OpenILS/WWW/RemoteAuth/PatronAPI.pm | 174 ++++++++++++++++++ .../remoteauth/patronapi/default.tt2 | 6 + .../templates/remoteauth/patronapi/dump.tt2 | 14 ++ .../remoteauth/patronapi/pintest.tt2 | 14 ++ Open-ILS/tests/datasets/sql/remoteauth.sql | 11 ++ 7 files changed, 257 insertions(+), 1 deletion(-) create mode 100644 Open-ILS/src/perlmods/lib/OpenILS/WWW/RemoteAuth/PatronAPI.pm create mode 100644 Open-ILS/src/templates/remoteauth/patronapi/default.tt2 create mode 100644 Open-ILS/src/templates/remoteauth/patronapi/dump.tt2 create mode 100644 Open-ILS/src/templates/remoteauth/patronapi/pintest.tt2 diff --git a/Open-ILS/examples/apache_24/eg_startup.in b/Open-ILS/examples/apache_24/eg_startup.in index b5c679a293..67d7ca947b 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', 'OpenILS::WWW::RemoteAuth::EZProxyCGI'); +use OpenILS::WWW::RemoteAuth ('@sysconfdir@/opensrf_core.xml', 'OpenILS::WWW::RemoteAuth::Basic', 'OpenILS::WWW::RemoteAuth::EZProxyCGI', 'OpenILS::WWW::RemoteAuth::PatronAPI'); # 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 0d1f4e203e..07c12ac8a8 100644 --- a/Open-ILS/examples/apache_24/eg_vhost.conf.in +++ b/Open-ILS/examples/apache_24/eg_vhost.conf.in @@ -902,6 +902,43 @@ RewriteRule ^/openurl$ ${openurl:%1} [NE,PT] PerlSetVar OILSRemoteAuthEZProxySecret "secret" + + SetHandler perl-script + PerlHandler OpenILS::WWW::RemoteAuth + Options +ExecCGI + + # access restricted to localhost by default; since this module provides no + # client authentication and can be configured to return detailed personal + # information, restricting access by IP or other means is stongly + # recommended + Require local + + # remoteauth profile name + PerlSetVar OILSRemoteAuthProfile "PatronAPI" + # Perl module for processing requests + PerlSetVar OILSRemoteAuthHandler "OpenILS::WWW::RemoteAuth::PatronAPI" + + # staff username/password for config lookup and patron retrieval + PerlSetVar OILSRemoteAuthClientUsername "admin" + PerlSetVar OILSRemoteAuthClientPassword "demo123" + + # Location of TT2 templates for PatronAPI responses. + # Templates will be loaded from the following paths in reverse order. + PerlAddVar OILSRemoteAuthTemplatePath "@localstatedir@/templates/remoteauth/patronapi" + #PerlAddVar OILSRemoteAuthTemplatePath "@localstatedir@/templates_localskin/remoteauth/patronapi" + + # Locale (defaults to en_us) + #PerlAddVar OILSRemoteAuthLocale "en_us" + + # set to "true" to allow retrieval of detailed patron information + # without patron authorization + PerlSetVar OILSRemoteAuthPatronAPIAllowDump "false" + + # identifier type for patron information requests (/dump) + # permitted values: "barcode" (default), "username" + #PerlSetVar OILSRemoteAuthPatronAPIIDType "barcode" + + # Uncomment the following to force SSL for everything. Note that this defeats caching # and you will suffer a performance hit. #RewriteCond %{HTTPS} off diff --git a/Open-ILS/src/perlmods/lib/OpenILS/WWW/RemoteAuth/PatronAPI.pm b/Open-ILS/src/perlmods/lib/OpenILS/WWW/RemoteAuth/PatronAPI.pm new file mode 100644 index 0000000000..68c87022af --- /dev/null +++ b/Open-ILS/src/perlmods/lib/OpenILS/WWW/RemoteAuth/PatronAPI.pm @@ -0,0 +1,174 @@ +# 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 PatronAPI authentication: +# https://csdirect.iii.com/sierrahelp/Content/sril/sril_patronapi.html +# ====================================================================== + +package OpenILS::WWW::RemoteAuth::PatronAPI; +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 File::Spec; +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->{request_type} ||= 'default'; + $class = ref $class || $class; + return bless($args, $class); +} + +sub r { + my ($self, $r) = @_; + $self->{r} = $r if $r; + return $self->{r}; +} + +sub request_type { + my ($self, $request_type) = @_; + $self->{request_type} = $request_type if $request_type; + return $self->{request_type}; +} + +sub process { + my ($self, $r) = @_; + my ($authtoken, $editor, $config); + + $self->r($r); + + # 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 PatronAPI 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 PatronAPI failed on load config: @_"); + return Apache2::Const::HTTP_INTERNAL_SERVER_ERROR; + }; + return $self->backend_error unless $config; + + my $allow_dump = $r->dir_config('OILSRemoteAuthPatronAPIAllowDump') || 'false'; + my $id_type = $r->dir_config('OILSRemoteAuthPatronAPIIDType') || 'barcode'; + + # parse request + my $path = $r->path_info; + $path =~ s|/*\$||; # strip any trailing slashes + my @params = reverse File::Spec->splitdir($path); + $self->request_type(shift @params); + + if ($self->request_type eq 'dump') { + unless ($allow_dump eq 'true') { + return $self->client_not_authorized; + } + my ($id, @leftovers) = @params; + return $self->get_patron_info($editor, $config, { $id_type => $id }); + + } elsif ($self->request_type eq 'pintest') { + my ($password, $id, @leftovers) = @params; + return $self->do_patron_auth($editor, $config, $id, $password); + + } else { + $logger->error("RemoteAuth PatronAPI: invalid request format"); + return Apache2::Const::HTTP_INTERNAL_SERVER_ERROR; + } +} + +sub success { + my ($self, $user) = @_; + my $template = $self->request_type; + my $ctx = { + result => 'success' + }; + my $tt = new OpenILS::WWW::RemoteAuth::Template; + return $tt->process($template, $ctx, $self->r); +} + +# wrapper method for auth failures +sub error { + my ($self, $msg) = @_; + my $template = $self->request_type; + my $ctx = { + result => 'error', + error_msg => $msg + }; + my $tt = new OpenILS::WWW::RemoteAuth::Template; + return $tt->process($template, $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/patronapi/default.tt2 b/Open-ILS/src/templates/remoteauth/patronapi/default.tt2 new file mode 100644 index 0000000000..d9ceeaece4 --- /dev/null +++ b/Open-ILS/src/templates/remoteauth/patronapi/default.tt2 @@ -0,0 +1,6 @@ + + +ERRNUM=1
+ERRMSG=Requested record not found
+ + diff --git a/Open-ILS/src/templates/remoteauth/patronapi/dump.tt2 b/Open-ILS/src/templates/remoteauth/patronapi/dump.tt2 new file mode 100644 index 0000000000..8d7550109c --- /dev/null +++ b/Open-ILS/src/templates/remoteauth/patronapi/dump.tt2 @@ -0,0 +1,14 @@ +[%- USE date %] + + +[%- IF ctx.result == 'success' %] +EXP DATE[p43]=[% date.format(ctx.user.expiry_date, '%m-%d-%y') %]
+P TYPE[p47]=[% ctx.user.profile %]
+HOME LIBR[p53]=[% ctx.user.home_ou %]
+P BARCODE[pb]=[% ctx.user.barcode %]
+[%- ELSE %] +ERRNUM=1
+ERRMSG=Requested record not found
+[%- END %] + + diff --git a/Open-ILS/src/templates/remoteauth/patronapi/pintest.tt2 b/Open-ILS/src/templates/remoteauth/patronapi/pintest.tt2 new file mode 100644 index 0000000000..169cc679c0 --- /dev/null +++ b/Open-ILS/src/templates/remoteauth/patronapi/pintest.tt2 @@ -0,0 +1,14 @@ + + +[%- IF ctx.result == 'success' %] +RETCOD=0
+[%- ELSIF ctx.error_msg == 'patron_not_authenticated' %] +RETCOD=1
+ERRNUM=4
+ERRMSG=Invalid patron PIN
+[%- ELSE %] +ERRNUM=1
+ERRMSG=Requested record not found
+[%- END %] + + diff --git a/Open-ILS/tests/datasets/sql/remoteauth.sql b/Open-ILS/tests/datasets/sql/remoteauth.sql index dff2454ce9..93c2e81e76 100644 --- a/Open-ILS/tests/datasets/sql/remoteauth.sql +++ b/Open-ILS/tests/datasets/sql/remoteauth.sql @@ -20,3 +20,14 @@ INSERT INTO config.remoteauth_profile VALUES ('EZProxyCGI', 'EZProxy CGI Authentication for SYS2', 3, TRUE, 1, TRUE, FALSE, FALSE, NULL, 1002); +INSERT INTO config.usr_activity_type (id, ewho, ewhat, ehow, egroup, label) VALUES + ( 1003, 'patronapi', 'login', 'apache', 'authen', + oils_i18n_gettext(1003, 'RemoteAuth Login: PatronAPI Authentication', 'cuat', 'label')); + +-- config for PatronAPI Authentication (SYS1) +INSERT INTO config.remoteauth_profile + (name, description, context_org, enabled, perm, + restrict_to_org, allow_inactive, allow_expired, block_list, usr_activity_type) + VALUES ('PatronAPI', 'PatronAPI Authentication for SYS1', 2, TRUE, 1, + TRUE, FALSE, FALSE, NULL, 1003); + -- 2.43.2