From 69a23a05570d4e1d5980c7af852b3abe3f3069a8 Mon Sep 17 00:00:00 2001 From: Jeff Davis Date: Mon, 4 Mar 2019 16:48:23 -0800 Subject: [PATCH] LP#1817645: RemoteAuth handler for basic HTTP authentication (RFC 7617) 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 | 20 +++ .../lib/OpenILS/WWW/RemoteAuth/Basic.pm | 144 ++++++++++++++++++ 3 files changed, 165 insertions(+), 1 deletion(-) create mode 100644 Open-ILS/src/perlmods/lib/OpenILS/WWW/RemoteAuth/Basic.pm diff --git a/Open-ILS/examples/apache_24/eg_startup.in b/Open-ILS/examples/apache_24/eg_startup.in index f805c60f42..27b1abf23b 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'); +use OpenILS::WWW::RemoteAuth ('@sysconfdir@/opensrf_core.xml', 'OpenILS::WWW::RemoteAuth::Basic'); # Pass second argument of '1' to enable template caching. use OpenILS::WWW::PrintTemplate ('/openils/conf/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 43e17704ee..153735a389 100644 --- a/Open-ILS/examples/apache_24/eg_vhost.conf.in +++ b/Open-ILS/examples/apache_24/eg_vhost.conf.in @@ -836,6 +836,26 @@ RewriteRule ^/openurl$ ${openurl:%1} [NE,PT] + + 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 "Basic" + # Perl module for processing requests + PerlSetVar OILSRemoteAuthHandler "OpenILS::WWW::RemoteAuth::Basic" + + # staff username/password for config lookup and patron retrieval + PerlSetVar OILSRemoteAuthClientUsername "admin" + PerlSetVar OILSRemoteAuthClientPassword "demo123" + + # 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/Basic.pm b/Open-ILS/src/perlmods/lib/OpenILS/WWW/RemoteAuth/Basic.pm new file mode 100644 index 0000000000..cae24f9762 --- /dev/null +++ b/Open-ILS/src/perlmods/lib/OpenILS/WWW/RemoteAuth/Basic.pm @@ -0,0 +1,144 @@ +# 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 HTTP basic access authorization (RFC 7617) +# - patron credentials are Bas64-encoded in Authorization header +# - no client authorization - restricting access by IP or other methods +# is strongly recommended! +# ====================================================================== + +package OpenILS::WWW::RemoteAuth::Basic; +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 MIME::Base64; +use OpenSRF::EX qw(:try); +use OpenSRF::Utils::Logger qw/$logger/; +use OpenILS::Utils::CStoreEditor qw/:funcs/; +use OpenSRF::Utils::JSON; + +sub new { + my( $class, $args ) = @_; + $args ||= {}; + $class = ref $class || $class; + return bless($args, $class); +} + +# here's our main method; it controls the various steps of the auth flow, +# prepares the response content, and returns an HTTP status code +sub process { + my ($self, $r) = @_; + my ($authtoken, $editor, $config); + + # 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 Basic 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 Basic failed on load config: @_"); + return Apache2::Const::HTTP_INTERNAL_SERVER_ERROR; + }; + return $self->backend_error unless $config; + + # extract patron id/password from Authorization request header + my $auth_header = $r->headers_in->get('Authorization'); + unless (defined $auth_header && $auth_header =~ /^Basic /) { + # include WWW-Authenticate header on 401 responses, per RFC 7617 + my $name = $config->name; + $r->err_headers_out->add('WWW-Authenticate' => "Basic realm=\"$name\""); + return Apache2::Const::AUTH_REQUIRED; + } + $auth_header =~ s/^Basic //; + my ($id, $password) = split(/:/, decode_base64($auth_header), 2); + + # authenticate patron + my $stat = $self->do_patron_auth($editor, $config, $id, $password); + return $stat unless $stat == Apache2::Const::OK; + + # XXX RFC 7617 doesn't require any particular content in the body of the + # response. The response content could be made configurable, but for now, + # let's respond with a simple JSON message containing the username/barcode + # used to authenticate the user: it's a predictable response, it doesn't + # require us to retrieve any additional patron information, and it's + # compatible with the Apereo CAS server's requirements for remote REST + # authentication, as documented here: + # https://apereo.github.io/cas/5.0.x/installation/Rest-Authentication.html + + my $response_content = { id => $id }; + $r->content_type('application/json'); + $r->print( OpenSRF::Utils::JSON->perl2JSON($response_content) ); + return Apache2::Const::OK; + +} + +# ... and here are all our util methods: + +# success +sub success { + return Apache2::Const::OK; +} + +# generic backend error +sub backend_error { + return Apache2::Const::HTTP_INTERNAL_SERVER_ERROR; +} + +# client error (e.g. missing params) +sub client_error { + return Apache2::Const::HTTP_BAD_REQUEST; +} + +# client auth failed +sub client_not_authorized { + return Apache2::Const::AUTH_REQUIRED; +} + +# patron auth failed (bad password etc) +sub patron_not_authenticated { + return Apache2::Const::FORBIDDEN; +} + +# patron does not exist or is inactive/deleted +sub patron_not_found { + return Apache2::Const::FORBIDDEN; +} + +# patron is barred or has blocking penalties +sub patron_is_blocked { + return Apache2::Const::FORBIDDEN; +} + +# patron is expired +sub patron_is_expired { + return Apache2::Const::FORBIDDEN; +} + +1; + -- 2.43.2