File Coverage

blib/lib/PlugAuth.pm
Criterion Covered Total %
statement 100 108 92.5
branch 27 36 75.0
condition 7 9 77.7
subroutine 19 20 95.0
pod 2 2 100.0
total 155 175 88.5


line stmt bran cond sub pod time code
1             package PlugAuth;
2              
3             # ABSTRACT: Pluggable authentication and authorization server.
4             our $VERSION = '0.38'; # VERSION
5              
6              
7 41     41   3709890 use strict;
  41         138  
  41         1644  
8 41     41   291 use warnings;
  41         116  
  41         1476  
9 41     41   1078 use 5.010001;
  41         239  
10 41     41   316 use base 'Clustericious::App';
  41         112  
  41         7492  
11 41     41   6994832 use PlugAuth::Routes;
  41         533  
  41         1775  
12 41     41   379 use Log::Log4perl qw( :easy );
  41         109  
  41         321  
13 41     41   44988 use Role::Tiny ();
  41         160663  
  41         1163  
14 41     41   6683 use PlugAuth::Role::Plugin;
  41         123  
  41         1394  
15 41     41   301 use Clustericious::Config;
  41         97  
  41         1167  
16 41     41   364 use Mojo::Base 'Mojo::EventEmitter';
  41         95  
  41         611  
17              
18             sub plugin
19             {
20 430     430 1 1406174 my($self, $name, @rest) = @_;
21            
22             # catch load of plug_auth and use self_plug_auth instead
23 430 100 66     3148 if($name eq 'plug_auth'
24             || $name eq 'PlugAuth')
25             {
26 25         220 TRACE "loading plugin self_plug_auth ($name)";
27 25         15826 return $self->SUPER::plugin('self_plug_auth', @rest);
28             }
29              
30             # load the plugin
31 405         2462 TRACE "loading plugin $name";
32 405         180299 my $ret = $self->SUPER::plugin($name, @rest);
33            
34             # just in case we miss it and plug_auth was loaded,
35             # load self_plug_auth instead.
36 405 50       1153142 if(eval { $ret->isa('Clustericious::Plugin::PlugAuth') })
  405         4806  
37             {
38 0         0 TRACE "detected plug_auth plugin, switching with self_plug_auth";
39 0         0 return $self->SUPER::plugin('self_plug_auth', @rest);
40             }
41             else
42             {
43 405         1906 return $ret;
44             }
45             }
46              
47             sub startup
48             {
49 45     45 1 2417 my $self = shift;
50 45         456 $self->SUPER::startup(@_);
51              
52             #$self->renderer->default_format('txt');
53            
54 45         434647 my @plugins_config = eval {
55 45   100     326 my $plugins = $self->config->{plugins} // [];
56 45 100       1257 ref($plugins) eq 'ARRAY' ? @$plugins : ($plugins);
57             };
58              
59 45         351 my $auth_plugin;
60             my $authz_plugin;
61 45         0 my $welcome_plugin;
62 45         0 my @refresh;
63            
64 45         237 foreach my $plugin_class (reverse @plugins_config)
65             {
66 21         143 my $plugin_config;
67 21 100       83 if(ref $plugin_class)
68             {
69 13         58 ($plugin_config) = values %$plugin_class;
70 13 50       182 $plugin_config = Clustericious::Config->new($plugin_config)
71             unless ref($plugin_config) eq 'ARRAY';
72 13         6816 ($plugin_class) = keys %$plugin_class;
73             }
74             else
75             {
76 8         45 $plugin_config = Clustericious::Config->new({});
77             }
78            
79 21         5640 eval qq{ require $plugin_class };
80 21 50       236 LOGDIE $@ if $@;
81 21 50       118 Role::Tiny::does_role($plugin_class, 'PlugAuth::Role::Plugin')
82             || LOGDIE "$plugin_class is not a PlugAuth plugin";
83            
84 21         615 my $plugin = $plugin_class->new($self->config, $plugin_config, $self);
85              
86 21 100       84 if($plugin->does('PlugAuth::Role::Auth'))
87             {
88 17         360 $plugin->next_auth($auth_plugin);
89 17         37 $auth_plugin = $plugin;
90             }
91            
92 21 50       131 if($plugin->does('PlugAuth::Role::Welcome'))
93             {
94 0         0 $welcome_plugin = $plugin;
95             }
96              
97 21 100       319 $authz_plugin = $plugin if $plugin->does('PlugAuth::Role::Authz');
98 21 100       285 push @refresh, $plugin if $plugin->does('PlugAuth::Role::Refresh')
99             }
100              
101 45 100       480 unless(defined $auth_plugin)
102             {
103 33         2942 require PlugAuth::Plugin::FlatAuth;
104 33 100       474 if($self->config->ldap(default => ''))
105             {
106 1         764 require PlugAuth::Plugin::LDAP;
107 1         9 $auth_plugin = PlugAuth::Plugin::LDAP->new($self->config, {}, $self);
108 1         7 $auth_plugin->next_auth(PlugAuth::Plugin::FlatAuth->new($self->config, Clustericious::Config->new({}), $self));
109 1         5 push @refresh, $auth_plugin->next_auth;
110             }
111             else
112             {
113 32         3179 $auth_plugin = PlugAuth::Plugin::FlatAuth->new($self->config, Clustericious::Config->new({}), $self);
114 32         162 push @refresh, $auth_plugin;
115             }
116             }
117            
118 45 100       229 unless(defined $authz_plugin)
119             {
120 41         4655 require PlugAuth::Plugin::FlatAuthz;
121 41         624 $authz_plugin = PlugAuth::Plugin::FlatAuthz->new($self->config, Clustericious::Config->new({}), $self);
122 41         188 push @refresh, $authz_plugin;
123             }
124              
125 45     4   660 $self->helper(data => sub { $auth_plugin });
  4         3846  
126 45     375   2184 $self->helper(auth => sub { $auth_plugin });
  375         75626  
127 45     674   1057 $self->helper(authz => sub { $authz_plugin });
  674         57355  
128            
129 45 50       885 if(@refresh > 0 )
130             {
131             $self->hook(before_dispatch => sub {
132 578     578   7539676 $_->refresh for @refresh;
133 45         626 });
134 45     83   1174 $self->helper(refresh => sub { $_->refresh for @refresh; 1 });
  83         7732  
  83         359  
135             }
136             else
137             {
138 0     0   0 $self->helper(refresh => sub { 1; });
  0         0  
139             }
140            
141             $self->helper(welcome => sub {
142 2     2   124 my($self) = @_;
143 2 50       14 if($welcome_plugin)
144             {
145 0         0 $welcome_plugin->welcome($self);
146             }
147             else
148             {
149 2         26 $self->render_message("welcome to plug auth");
150             }
151 45         1193 });
152            
153             # for historical reasons, some routes return text by default
154             # in older versions, even if a format (e.g. /auth.json) is specified.
155             # this is a simple helper to render using autodata if a format
156             # is explicitly specified, otherwise fallback on the original
157             # behavior.
158             $self->helper(render_message => sub {
159 429     429   23791 my($self, $message, $status) = @_;
160 429   100     2378 $status //= 200;
161 429   50     1950 my $format = $self->stash->{format} // 'txt';
162 429 50       8255 if($format ne 'txt')
163             {
164 0         0 $self->render(autodata => { message => $message, status => $status }, status => $status);
165             }
166             else
167             {
168 429         2624 $self->render(text => $message, status => $status, format => 'txt');
169             }
170 45         1050 });
171            
172             # Silence warnings; this is only used for for session
173             # cookies, which we don't use.
174 45 50       1388 if($self->can('secrets'))
175 45         334 { $self->secrets([rand]) }
176             else
177 0           { $self->secret(rand) }
178             }
179              
180             1;
181              
182             __END__
183              
184             =pod
185              
186             =encoding UTF-8
187              
188             =head1 NAME
189              
190             PlugAuth - Pluggable authentication and authorization server.
191              
192             =head1 VERSION
193              
194             version 0.38
195              
196             =head1 SYNOPSIS
197              
198             In the configuration file for the Clustericious app that will authenticate
199             against PlugAuth:
200              
201             ---
202             plug_auth:
203             url: http://localhost:1234
204              
205             and I<authenticate> and I<authorize> in your Clustericious application's Routes.pm:
206              
207             authenticate;
208             authorize;
209            
210             get '/resource' => sub {
211             # resource that requires authentication
212             # and authorization
213             };
214              
215             =head1 DESCRIPTION
216              
217             (For a quick start guide on how to setup a PlugAuth server, please see
218             L<PlugAuth::Guide::Server>)
219              
220             PlugAuth is a pluggable authentication and authorization server with a consistent
221             RESTful API. This allows clients to authenticate and query authorization from a
222             PlugAuth server without worrying or caring whether the actual authentication happens
223             against flat files, PAM, LDAP or passed on to another PlugAuth server.
224              
225             The authentication API is HTTP Basic Authentication. The authorization API is based
226             on users, groups, resources and hosts.
227              
228             The implementation for these can be swapped in and out depending on the plugins that
229             you select in the configuration file. The default plugins for authentication
230             (L<PlugAuth::Plugin::FlatAuth>) and authorization (L<PlugAuth::Plugin::FlatAuthz>) are
231             implemented with ordinary flat files and advisory locks using flock.
232              
233             The are other plugins for ldap (L<PlugAuth::Plugin::LDAP>), L<DBI>
234             (L<PlugAuth::Plugin::DBIAuth>), or you can write your own (L<PlugAuth::Guide::Plugin>).
235              
236             Here is a diagram that illustrates the most common use case for PlugAuth being used
237             by a RESTful service:
238              
239             client
240             |
241             | HTTP
242             |
243             +-----------+ +------------+ +--------------+
244             | REST | HTTP | | --> | Auth Plugin | --> files
245             | service | ------> | PlugAuth | +--------------+ --> ldap
246             | | | | --> | Authz Plugin | --> ...
247             +-----------+ +------------+ +--------------+
248              
249             =over 4
250              
251             =item 1.
252              
253             Client (web browser or other) sends an HTTP request to the service.
254              
255             =item 2
256              
257             The service sends an HTTP basic authentication request to PlugAuth with the user's credentials
258              
259             =item 3
260              
261             PlugAuth performs authentication (see L</AUTHENTICATION>) and returns the appropriate
262             HTTP status code.
263              
264             =item 4
265              
266             The REST service sends the HTTP status code to the client if authentication has failed.
267              
268             =item 5
269              
270             The REST service may optionally check the client's host, and if it is "trusted",
271             authorization succeeds (see L</AUTHORIZATION>).
272              
273             =item 6
274              
275             If not, the REST service sends an authorization request to PlugAuth, asking whether
276             the client has permission to perform an "action" on a "resource". Both the action and
277             resource are arbitrary strings, though one reasonable default is sending the HTTP
278             method as the action, and the URL path as the resource. (see L</AUTHORIZATION> below).
279              
280             =item 7
281              
282             PlugAuth returns a response code to the REST service indicating whether or not
283             authorization should succeed.
284              
285             =item 8
286              
287             The REST service returns the appropriate response to the client.
288              
289             =back
290              
291             If the REST service is written in Perl, see L<PlugAuth::Client>.
292              
293             If the REST service uses Clustericious, see L<Clustericious::Plugin::PlugAuth>.
294              
295             PlugAuth was originally written for scientific data processing clusters based on
296             L<Clustericious> in which all the services are RESTful servers distributed over a number
297             of different physical hosts, though it may be applicable in other contexts.
298              
299             =head2 AUTHENTICATION
300              
301             Checking for authentication is done by sending a GET request to URLs of the form
302              
303             /auth
304              
305             With the username and password specified as HTTP Basic credentials. The actual
306             mechanism used to verify authentication will depend on the authentication plugin being
307             used. The default is L<PlugAuth::Plugin::FlatAuth>.
308              
309             =head2 AUTHORIZATION
310              
311             Checking the authorization is done by sending GET requests to URLs of the form
312              
313             /authz/user/user/action/resource
314              
315             where I<user> and I<action> are strings (no slashes), and I<resource> is a string
316             which may have slashes. A response code of 200 indicates that access should be
317             granted, 403 indicates that the resource is forbidden. A user is granted access to a
318             resource if one of of the following conditions are met:
319              
320             =over 4
321              
322             =item
323              
324             the user is specifically granted access to that resource, i.e. a line of the form
325              
326             /resource (action): username
327              
328             appears in the resources file (see L</CONFIGURATION>).
329              
330             =item
331              
332             the user is a member of a group which is granted access to that resource.
333              
334             =item
335              
336             the user or a group containing the user is granted access to a resource which is a
337             prefix of the requested resource. i.e.
338              
339             / (action): username
340              
341             would grant access to "username" to perform "action" on any resource.
342              
343             =item
344              
345             Additionally, given a user, an action, and a regular expression, it is possible to find
346             I<all> of the resources matching that regular expression for which the user has access. This
347             can be done by sending a GET request to
348              
349             /authz/resources/user/action/regex
350              
351             =item
352              
353             Host-based authorization is also possible -- sending a get
354             request to
355              
356             /host/host/trusted
357              
358             where ".host" is a string representing a hostname, returns
359             200 if the host-based authorization should succeed, and
360             403 otherwise.
361              
362             =back
363              
364             For a complete list of the available routes and what they return see L<PlugAuth::Routes>.
365              
366             =head2 CONFIGURATION
367              
368             Server configuration is done in ~/etc/PlugAuth.conf which is a
369             Clustericious::Config style file. The configuration depends on which plugins you
370             choose, consult your plugin's documentation. The default plugins are
371             L<PlugAuth::Plugin::FlatAuth>, L<PlugAuth::Plugin::FlatAuthz>.
372              
373             Once the authentication and authorization has been configured, PlugAuth
374             can be started (like any L<Mojolicious> or L<Clustericious> application)
375             using the daemon command:
376              
377             % plugauth daemon
378              
379             This will use the built-in web server. To use another web server, additional
380             configuration is required. For example, after adding this:
381              
382             start_mode: hypnotoad
383             hypnotoad :
384             listen : 'http://localhost:8099'
385             env :
386             %# Automatically generated configuration file
387             HYPNOTOAD_CONFIG : /var/run/pluginauth/pluginauth.hypnotoad.conf
388              
389             This start command can be used to start a hypnotoad web server.
390              
391             % plugauth start
392              
393             See L<Clustericious::Config> for more examples, including using with nginx,
394             lighttpd, Plack or Apache.
395              
396             =head1 EVENTS
397              
398             See L<PlugAuth::Routes>
399              
400             =head1 SEE ALSO
401              
402             L<Clustericious::Plugin::PlugAuth>,
403             L<PlugAuth::Client>,
404             L<PlugAuth::Guide::Client>,
405             L<PlugAuth::Guide::Plugin>,
406             L<PlugAuth::Guide::Server>
407              
408             =head1 AUTHOR
409              
410             Graham Ollis <gollis@sesda3.com>
411              
412             =head1 COPYRIGHT AND LICENSE
413              
414             This software is copyright (c) 2012 by NASA GSFC.
415              
416             This is free software; you can redistribute it and/or modify it under
417             the same terms as the Perl 5 programming language system itself.
418              
419             =cut