c1654626a0e7d5e755bae5e95276439b89954612
[working/Evergreen.git] / Open-ILS / src / perlmods / lib / OpenILS / Application / AuthProxy.pm
1 #!/usr/bin/perl
2
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.
7 #
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.
12 #
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.
16
17 =head1 NAME
18
19 OpenILS::Application::AuthProxy - Negotiator for proxy-style authentication
20
21 =head1 AUTHOR
22
23 Dan Wells, dbw2@calvin.edu
24
25 =cut
26
27 package OpenILS::Application::AuthProxy;
28
29 use strict;
30 use warnings;
31 use OpenILS::Application;
32 use base qw/OpenILS::Application/;
33 use OpenSRF::Utils::Logger qw(:logger);
34 use OpenSRF::Utils::SettingsClient;
35 use OpenILS::Application::AppUtils;
36 use OpenILS::Utils::Fieldmapper;
37 use OpenILS::Event;
38 use UNIVERSAL::require;
39 use Digest::MD5 qw/md5_hex/;
40 my $U = 'OpenILS::Application::AppUtils';
41
42 # NOTE: code assumes throughout that '0' is never a valid username, barcode,
43 # or password; some logic will need to be tweaked to support it if needed.
44
45 my @authenticators;
46 my %authenticators_by_name;
47 my $enabled = 'false';
48
49 sub initialize {
50     my $conf = OpenSRF::Utils::SettingsClient->new;
51     my @pfx = ( "apps", "open-ils.auth_proxy", "app_settings" );
52
53     $enabled = $conf->config_value( @pfx, 'enabled' );
54
55     my $auth_configs = $conf->config_value( @pfx, 'authenticators', 'authenticator' );
56     $auth_configs = [$auth_configs] if ref($auth_configs) eq 'HASH';
57
58     if ( !@$auth_configs ) {
59         $logger->error("AuthProxy: authenticators list not found!");
60     } else {
61         foreach my $auth_config (@$auth_configs) {
62             my $auth_handler;
63             if ($auth_config->{'name'} eq 'native') {
64                 $auth_handler = 'OpenILS::Application::AuthProxy::Native';
65             } else {
66                 $auth_handler = $auth_config->{module};
67                 next unless $auth_handler;
68
69                 $logger->debug("Attempting to load AuthProxy handler: $auth_handler");
70                 $auth_handler->use;
71                 if($@) {
72                     $logger->error("Unable to load AuthProxy handler [$auth_handler]: $@");
73                     next;
74                 }
75             }
76
77             &_make_option_array($auth_config, 'login_types', 'type');
78             &_make_option_array($auth_config, 'org_units', 'unit');
79
80             my $authenticator = $auth_handler->new($auth_config);
81             push @authenticators, $authenticator;
82             $authenticators_by_name{$authenticator->name} = $authenticator;
83             $logger->debug("Successfully loaded AuthProxy handler: $auth_handler");
84         }
85         $logger->debug("AuthProxy: authenticators loaded");
86     }
87 }
88
89 # helper function to simplify the config structure
90 sub _make_option_array {
91     my ($auth_config, $container_name, $node_name) = @_;
92
93     if (exists $auth_config->{$container_name}
94         and ref $auth_config->{$container_name} eq 'HASH') {
95         my $nodes = $auth_config->{$container_name}{$node_name};
96         if ($nodes) {
97             if (ref $nodes ne 'ARRAY') {
98                 $auth_config->{$container_name} = [$nodes];
99             } else {
100                 $auth_config->{$container_name} = $nodes;
101             }
102         } else {
103             delete $auth_config->{$container_name};
104         }
105     } else {
106         delete $auth_config->{$container_name};
107     }
108 }
109
110
111
112 __PACKAGE__->register_method(
113     method    => "enabled",
114     api_name  => "open-ils.auth_proxy.enabled",
115     api_level => 1,
116     stream    => 1,
117     argc      => 0,
118     signature => {
119         desc => q/Check if AuthProxy is enabled/,
120         return => {
121             desc => "True if enabled, false if not",
122             type => "bool"
123         }
124     }
125 );
126 sub enabled {
127     return (!$enabled or $enabled eq 'false') ? 0 : 1;
128 }
129
130 __PACKAGE__->register_method(
131     method    => "login",
132     api_name  => "open-ils.auth_proxy.login",
133     api_level => 1,
134     stream    => 1,
135     argc      => 1,
136     signature => {
137         desc => q/Basic single-factor login method/,
138         params => [
139             {name=> "args", desc => q/A hash of arguments.  Valid keys and their meanings:
140     username := Username to authenticate.
141     barcode  := Barcode of user to authenticate (currently supported by 'native' only!)
142     password := Password for verifying the user.
143     type     := Type of login being attempted (Staff Client, OPAC, etc.).
144     org      := Org unit id
145 /,
146                 type => "hash"}
147         ],
148         return => {
149             desc => "Authentication seed or failure event",
150             type => "mixed"
151         }
152     }
153 );
154 sub login {
155     my ( $self, $conn, $args ) = @_;
156
157     return OpenILS::Event->new( 'LOGIN_FAILED' )
158       unless (&enabled() and ($args->{'username'} or $args->{'barcode'}));
159
160     my @error_events;
161     my $authenticated = 0;
162     my $auths;
163
164     # if they specify an authenticator by name, only try that one
165     if ($args->{'name'}) {
166         $auths = [$authenticators_by_name{$args->{'name'}}];
167     } else {
168         $auths = \@authenticators;
169     }
170
171     foreach my $authenticator (@$auths) {
172         # skip authenticators specified for a different login type
173         # or org unit id
174         if ($authenticator->login_types and $args->{'type'}) {
175             next unless grep(/^(all|$args->{'type'})$/, @{$authenticator->{'login_types'}});
176         }
177         if ($authenticator->org_units and $args->{'org'}) {
178             next unless grep(/^(all|$args->{'org'})$/, @{$authenticator->{'org_units'}});
179         }
180
181         my $event;
182         # treat native specially
183         if ($authenticator->name eq 'native') {
184             $event = &_do_login($args);
185         } else {
186             $event = $authenticator->authenticate($args);
187         }
188         my $code = $U->event_code($event);
189         if ($code) {
190             push @error_events, $event;
191         } elsif (defined $code) { # code is '0', i.e. SUCCESS
192             if (exists $event->{'payload'}) { # we have a complete native login
193                 return $event;
194             } else { # do a 'forced' login
195                 return &_do_login($args, 1);
196             }
197         }
198     }
199
200     # if we got this far, we failed
201     # TODO: send back some form of collected error events
202     return OpenILS::Event->new( 'LOGIN_FAILED' );
203 }
204
205 sub _do_login {
206     my $args = shift;
207     my $authenticated = shift;
208
209     my $seeder = $args->{'username'} ? $args->{'username'} : $args->{'barcode'};
210     my $seed =
211       OpenSRF::AppSession->create("open-ils.auth")
212       ->request( 'open-ils.auth.authenticate.init', $seeder )->gather(1);
213
214     return OpenILS::Event->new( 'LOGIN_FAILED' )
215       unless $seed;
216
217     my $real_password = $args->{'password'};
218     # if we have already authenticated, look up the password needed to finish
219     if ($authenticated) {
220         # barcode-based login is supported only for 'native' logins
221         return OpenILS::Event->new( 'LOGIN_FAILED' ) if !$args->{'username'};
222         my $user = $U->cstorereq(
223             "open-ils.cstore.direct.actor.user.search.atomic",
224             { usrname => $args->{'username'} }
225         );
226         $args->{'password'} = md5_hex( $seed . $user->[0]->passwd );
227     } else {
228         $args->{'password'} = md5_hex( $seed . md5_hex($real_password) );
229     }
230     my $response = OpenSRF::AppSession->create("open-ils.auth")->request(
231         'open-ils.auth.authenticate.complete',
232         $args
233     )->gather(1);
234     $args->{'password'} = $real_password;
235
236     return OpenILS::Event->new( 'LOGIN_FAILED' )
237       unless $response;
238
239     return $response;
240 }
241
242 __PACKAGE__->register_method(
243     method    => "authenticators",
244     api_name  => "open-ils.auth_proxy.authenticators",
245     api_level => 1,
246     stream    => 1,
247     argc      => 1,
248     signature => {
249         desc => q/Get a list of viable authenticators/,
250         params => [
251             {name=> "args", desc => q/A hash of arguments.  Valid keys and their meanings:
252     type     := Type of login being attempted (Staff Client, OPAC, etc.).
253     org      := Org unit id
254 /,
255                 type => "hash"}
256         ],
257         return => {
258             desc => "List of viable authenticators",
259             type => "array"
260         }
261     }
262 );
263 sub authenticators {
264     my ( $self, $conn, $args ) = @_;
265
266     my @viable_auths;
267
268     foreach my $authenticator (@authenticators) {
269         # skip authenticators specified for a different login type
270         # or org unit id
271         if ($authenticator->login_types and $args->{'type'}) {
272             next unless grep(/^(all|$args->{'type'})$/, @{$authenticator->login_types});
273         }
274         if ($authenticator->org_units and $args->{'org'}) {
275             next unless grep(/^(all|$args->{'org'})$/, @{$authenticator->org_units});
276         }
277
278         push @viable_auths, $authenticator->name;
279     }
280
281     return \@viable_auths;
282 }
283
284
285 # --------------------------------------------------------------------------
286 # Stub package for 'native' authenticator
287 # --------------------------------------------------------------------------
288 package OpenILS::Application::AuthProxy::Native;
289 use strict; use warnings;
290 use base 'OpenILS::Application::AuthProxy::AuthBase';
291
292 1;