1 # Copyright (C) 2019 BC Libraries Cooperative
3 # This program is free software; you can redistribute it and/or
4 # modify it under the terms of the GNU General Public License
5 # as published by the Free Software Foundation; either version 2
6 # of the License, or (at your option) any later version.
8 # This program is distributed in the hope that it will be useful,
9 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # GNU General Public License for more details.
13 # You should have received a copy of the GNU General Public License
14 # along with this program; if not, write to the Free Software
15 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17 # ======================================================================
18 # - base class for configurable HTTP API for patron auth/retrieval
19 # - provides generic methods shared by all handler subclasses
20 # - handlers take care of endpoint-specific implementation details
21 # ======================================================================
23 package OpenILS::WWW::RemoteAuth;
24 use strict; use warnings;
25 use Apache2::Const -compile => qw(OK DECLINED FORBIDDEN AUTH_REQUIRED HTTP_INTERNAL_SERVER_ERROR REDIRECT HTTP_BAD_REQUEST);
26 use DateTime::Format::ISO8601;
28 use OpenSRF::EX qw(:try);
29 use OpenSRF::Utils::Logger qw/$logger/;
31 use OpenILS::Utils::CStoreEditor qw/:funcs/;
32 use OpenILS::Application::AppUtils;
33 our $U = "OpenILS::Application::AppUtils";
36 my @handlers_to_preinit = ();
39 my ($self, $editor) = @_;
40 $self->{editor} = $editor if $editor;
41 return $self->{editor};
45 my ($self, $config) = @_;
46 $self->{config} = $config if $config;
47 return $self->{config};
51 my ($self, $bootstrap_config, $handlers) = @_;
52 @handlers_to_preinit = split /\s+/, $handlers, -1 if defined($handlers);
56 OpenSRF::System->bootstrap_client(config_file => $bootstrap_config);
57 my $idl = OpenSRF::Utils::SettingsClient->new->config_value("IDL");
58 Fieldmapper->import(IDL => $idl);
59 OpenILS::Utils::CStoreEditor->init;
60 foreach my $module (@handlers_to_preinit) {
65 return Apache2::Const::OK;
71 my $stat = Apache2::Const::AUTH_REQUIRED;
73 # load the appropriate module and process our request
75 my $module = $r->dir_config('OILSRemoteAuthHandler');
77 my $handler = $module->new;
78 $stat = $handler->process($r);
80 $logger->error("processing RemoteAuth handler failed: @_");
81 $stat = Apache2::Const::HTTP_INTERNAL_SERVER_ERROR;
88 my ($self, $e, $r) = @_;
90 # name to use for config lookup
91 my $name = $r->dir_config('OILSRemoteAuthProfile');
92 return undef unless $name;
95 my $config = $e->retrieve_config_remoteauth_profile($name);
96 if ($config and $U->is_true($config->enabled)) {
99 $logger->info("RemoteAuth: config profile $name not found (or not enabled)");
104 my ($self, $client_username, $client_password) = @_;
105 my $login_resp = $U->simplereq(
107 'open-ils.auth.login', {
108 username => $client_username,
109 password => $client_password,
113 if ($login_resp->{textcode} eq 'SUCCESS') {
114 return $login_resp->{payload}->{authtoken};
116 $logger->info("RemoteAuth: failed to authenticate client $client_username");
121 my ($self, $e, $config, $id, $password) = @_;
122 my $org_unit = $config->context_org;
124 return $self->backend_error unless $e->checkauth;
131 password => $password,
132 agent => 'remoteauth'
135 my $response = $U->simplereq(
137 'open-ils.auth.login', $args);
138 if($U->event_code($response)) {
139 $logger->info("RemoteAuth: failed to authenticate user $id at org unit $org_unit");
140 return $self->patron_not_authenticated;
143 # get basic patron info via user authtoken
144 my $authtoken = $response->{payload}->{authtoken};
145 my $user = $U->simplereq(
147 'open-ils.auth.session.retrieve', $authtoken);
148 if (!$user or $U->event_code($user)) {
149 $logger->error("RemoteAuth: failed to retrieve user for session $authtoken");
150 return $self->backend_error;
152 my $userid = $user->id;
153 my $home_ou = $user->home_ou;
155 unless ($e->allowed('VIEW_USER', $home_ou)) {
156 $logger->info("RemoteAuth: client does not have permission to view user $userid");
157 return $self->client_not_authorized;
160 # do basic validation (and skip the permit test where applicable)
161 if ($U->is_true($user->deleted)) {
162 $logger->info("RemoteAuth: user $userid is deleted");
163 return $self->patron_not_found;
166 if ($U->is_true($user->barred)) {
167 $logger->info("RemoteAuth: user $userid is barred");
168 return $self->patron_is_blocked;
171 # check if remoteauth is permitted for this user
172 my $permit_test = $e->json_query(
173 {from => ['actor.permit_remoteauth', $config->name, $userid]}
174 )->[0]{'actor.permit_remoteauth'};;
176 if ($permit_test eq 'success') {
177 return $self->success($user);
178 } elsif ($permit_test eq 'not_found') {
179 return $self->patron_not_found;
180 } elsif ($permit_test eq 'expired') {
181 return $self->patron_is_expired;
183 return $self->patron_is_blocked;
187 # Dummy methods for responding to the client based on
188 # different error (or success) conditions.
189 # The handler will normally want to override these methods
190 # with its own version of them.
192 # patron auth succeeded
194 return Apache2::Const::OK;
197 # generic backend error
199 return Apache2::Const::HTTP_INTERNAL_SERVER_ERROR;
202 # client error (e.g. missing params)
204 return Apache2::Const::HTTP_BAD_REQUEST;
208 sub client_not_authorized {
209 return Apache2::Const::AUTH_REQUIRED;
212 # patron auth failed (bad password etc)
213 sub patron_not_authenticated {
214 return Apache2::Const::FORBIDDEN;
217 # patron does not exist or is inactive/deleted
218 sub patron_not_found {
219 return Apache2::Const::DECLINED;
222 # patron is barred or has blocking penalties
223 sub patron_is_blocked {
224 return Apache2::Const::FORBIDDEN;
228 sub patron_is_expired {
229 return Apache2::Const::DECLINED;