LP#1887196: RemoteAuth PatronAPI authentication
authorJeff Davis <jeff.davis@bc.libraries.coop>
Sat, 21 Dec 2019 00:28:55 +0000 (16:28 -0800)
committerGalen Charlton <gmc@equinoxinitiative.org>
Mon, 14 Sep 2020 15:22:46 +0000 (11:22 -0400)
Signed-off-by: Jeff Davis <jeff.davis@bc.libraries.coop>
Signed-off-by: Galen Charlton <gmc@equinoxinitiative.org>
Open-ILS/examples/apache_24/eg_startup.in
Open-ILS/examples/apache_24/eg_vhost.conf.in
Open-ILS/src/perlmods/lib/OpenILS/WWW/RemoteAuth/PatronAPI.pm [new file with mode: 0644]
Open-ILS/src/templates/remoteauth/patronapi/default.tt2 [new file with mode: 0644]
Open-ILS/src/templates/remoteauth/patronapi/dump.tt2 [new file with mode: 0644]
Open-ILS/src/templates/remoteauth/patronapi/pintest.tt2 [new file with mode: 0644]
Open-ILS/tests/datasets/sql/remoteauth.sql

index b5c679a..67d7ca9 100755 (executable)
@@ -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);
index 0d1f4e2..07c12ac 100644 (file)
@@ -902,6 +902,43 @@ RewriteRule ^/openurl$ ${openurl:%1} [NE,PT]
     PerlSetVar OILSRemoteAuthEZProxySecret "secret"
 </Location>
 
+<Location /api/patronapi>
+    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"
+</Location>
+
 # 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 (file)
index 0000000..68c8702
--- /dev/null
@@ -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 (file)
index 0000000..d9ceeae
--- /dev/null
@@ -0,0 +1,6 @@
+<HTML>
+<BODY>
+ERRNUM=1<BR>
+ERRMSG=Requested record not found<BR>
+</BODY>
+</HTML>
diff --git a/Open-ILS/src/templates/remoteauth/patronapi/dump.tt2 b/Open-ILS/src/templates/remoteauth/patronapi/dump.tt2
new file mode 100644 (file)
index 0000000..8d75501
--- /dev/null
@@ -0,0 +1,14 @@
+[%- USE date %]
+<HTML>
+<BODY>
+[%- IF ctx.result == 'success' %]
+EXP DATE[p43]=[% date.format(ctx.user.expiry_date, '%m-%d-%y') %]<BR>
+P TYPE[p47]=[% ctx.user.profile %]<BR>
+HOME LIBR[p53]=[% ctx.user.home_ou %]<BR>
+P BARCODE[pb]=[% ctx.user.barcode %]<BR>
+[%- ELSE %]
+ERRNUM=1<BR>
+ERRMSG=Requested record not found<BR>
+[%- END %]
+</BODY>
+</HTML>
diff --git a/Open-ILS/src/templates/remoteauth/patronapi/pintest.tt2 b/Open-ILS/src/templates/remoteauth/patronapi/pintest.tt2
new file mode 100644 (file)
index 0000000..169cc67
--- /dev/null
@@ -0,0 +1,14 @@
+<HTML>
+<BODY>
+[%- IF ctx.result == 'success' %]
+RETCOD=0<BR>
+[%- ELSIF ctx.error_msg == 'patron_not_authenticated' %]
+RETCOD=1<BR>
+ERRNUM=4<BR>
+ERRMSG=Invalid patron PIN<BR>
+[%- ELSE %]
+ERRNUM=1<BR>
+ERRMSG=Requested record not found<BR>
+[%- END %]
+</BODY>
+</HTML>
index dff2454..93c2e81 100644 (file)
@@ -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);
+