File Coverage

blib/lib/PlugAuth/Routes.pm
Criterion Covered Total %
statement 24 24 100.0
branch n/a
condition n/a
subroutine 8 8 100.0
pod n/a
total 32 32 100.0


line stmt bran cond sub pod time code
1             package PlugAuth::Routes;
2              
3             # ABSTRACT: routes for plugauth
4             our $VERSION = '0.35'; # VERSION
5              
6              
7             # There may be external authentication for these routes, i.e. using
8             # this CI to determine who can check/update other's access.
9              
10 41     41   17444 use strict;
  41         92  
  41         1452  
11 41     41   186 use warnings;
  41         78  
  41         1362  
12 41     41   224 use Log::Log4perl qw/:easy/;
  41         103  
  41         315  
13 41     41   21408 use Mojo::ByteStream qw/b/;
  41         76  
  41         2748  
14 41     41   8900 use List::MoreUtils qw/mesh/;
  41         437174  
  41         393  
15 41     41   34473 use Clustericious::RouteBuilder;
  41         95576  
  41         383  
16 41     41   11532 use Clustericious::Config;
  41         78  
  41         1168  
17 41     41   190 use List::MoreUtils qw( uniq );
  41         74  
  41         287  
18              
19              
20             get '/' => sub { shift->welcome } => 'index';
21             get '/index' => sub { shift->welcome };
22              
23              
24             # Check authentication for a user (http basic auth protocol).
25             get '/auth' => sub {
26             my $self = shift;
27             my $auth = $self->req->headers->authorization or do {
28             $self->res->headers->www_authenticate('Basic "ACPS"');
29             $self->render_message('please authenticate', 401);
30             return;
31             };
32             my ($method,$str) = split / /,$auth;
33             my ($user,$pw) = split /:/, b($str)->b64_decode;
34              
35             if($self->auth->check_credentials($user,$pw))
36             {
37             $self->render_message('ok');
38             INFO "Authentication succeeded for user $user";
39             }
40             else
41             {
42             $self->render_message('not ok', 403);
43             INFO "Authentication failed for user $user";
44             }
45             };
46              
47              
48             # Check authorization for a user to perform $action on $resource.
49             get '/authz/user/#user/#action/(*resource)' => { resource => '/' } => sub {
50             my $c = shift;
51             # Ok iff the user is in a group for which $action on $resource is allowed.
52             my ($user,$resource,$action) = map $c->stash($_), qw/user resource action/;
53             $resource =~ s{^/?}{/};
54             TRACE "Checking authorization for $user to perform $action on $resource...";
55             my $found = $c->authz->can_user_action_resource($user,$action,$resource);
56             if ($found)
57             {
58             TRACE "Authorization succeeded ($found)";
59             return $c->render_message('ok');
60             }
61             TRACE "Authorization failed";
62             $c->render_message("unauthorized : $user cannot $action $resource", 403);
63             };
64              
65              
66             # Given a user, an action and a regex, return a list of resources
67             # on which $user can do $action, where each resource matches that regex.
68             get '/authz/resources/#user/#action/(*resourceregex)' => sub {
69             my $c = shift;
70             my ($user,$action,$resourceregex) = map $c->stash($_), qw/user action resourceregex/;
71             TRACE "Checking $user, $action, $resourceregex";
72             $resourceregex = qr[$resourceregex];
73             my @resources;
74             for my $resource ($c->authz->match_resources($resourceregex))
75             {
76             TRACE "Checking resource $resource";
77             push @resources, $resource if $c->authz->can_user_action_resource($user,$action,$resource);
78             }
79             $c->stash->{autodata} = [sort @resources];
80             };
81              
82              
83             # Return a list of all defined actions
84             get '/actions' => sub {
85             my($self) = @_;
86             $self->stash->{autodata} = [ $self->authz->actions ];
87             };
88              
89              
90             # All the groups for a user :
91             get '/groups/#user' => sub {
92             my $c = shift;
93             my $groups = $c->authz->groups_for_user($c->stash('user'));
94             $c->render_message('not ok', 404) unless defined $groups;
95             $c->stash->{autodata} = $groups;
96             };
97              
98              
99             # Given a host and a tag (e.g. "trusted") return true if that host has
100             # that tag.
101             get '/host/#host/:tag' => sub {
102             my $c = shift;
103             my ($host,$tag) = map $c->stash($_), qw/host tag/;
104             if ($c->authz->host_has_tag($host,$tag))
105             {
106             TRACE "Host $host has tag $tag";
107             return $c->render_message('ok', 200);
108             }
109             TRACE "Host $host does not have tag $tag";
110             return $c->render_message('not ok', 403);
111             };
112              
113              
114             get '/user' => sub {
115             my $c = shift;
116             $c->stash->{autodata} = [ uniq sort $c->auth->all_users ];
117             };
118              
119              
120             get '/group' => sub {
121             my $c = shift;
122             $c->stash->{autodata} = [ $c->authz->all_groups ];
123             };
124              
125              
126             get '/users/:group' => sub {
127             my $c = shift;
128             my $users = $c->authz->users_in_group($c->stash('group'));
129             $c->render_message('not ok', 404) unless defined $users;
130             $c->stash->{autodata} = $users;
131             };
132              
133             authenticate;
134             authorize 'accounts';
135              
136              
137             post '/user' => sub {
138             my $c = shift;
139             $c->parse_autodata;
140             my $user = $c->stash->{autodata}->{user};
141             my $password = $c->stash->{autodata}->{password} || '';
142             my $groups = $c->stash->{autodata}->{groups};
143             delete $c->stash->{autodata};
144            
145             my $method = 'create_user';
146             my $cb;
147            
148             my $auth_plugin = $c->auth;
149            
150             if(defined $groups)
151             {
152             $method = 'create_user_cb';
153             $auth_plugin = $c->auth->_find_create_user_cb;
154             return $c->render_message('not ok', 501)
155             unless defined $auth_plugin;
156             $cb = sub {
157             foreach my $group (split /\s*,\s*/, $groups)
158             {
159             my $users = $c->app->authz->add_user_to_group($group, $user);
160             $c->app->emit(create_group => {
161             admin => $c->stash('user'),
162             group => $group,
163             users => $users,
164             });
165             }
166             };
167             }
168            
169             if($auth_plugin->$method($user, $password, $cb))
170             {
171             $c->render_message('ok', 200);
172             $c->app->emit('user_list_changed'); # deprecated, but documented in a previous version
173             $c->app->emit(create_user => {
174             admin => $c->stash('user'),
175             user => $user,
176             });
177             }
178             else
179             {
180             $c->render_message('not ok', 403);
181             }
182             };
183              
184              
185             del '/user/#username' => sub {
186             my $c = shift;
187             my $user = $c->param('username');
188             if($c->auth->delete_user($user))
189             {
190             $c->render_message('ok', 200);
191             $c->app->emit('user_list_changed'); # deprecated, but documented in a previous version
192             $c->app->emit(delete_user => {
193             admin => $c->stash('user'),
194             user => $user,
195             });
196             }
197             else
198             {
199             $c->render_message('not ok', 404);
200             }
201             };
202              
203              
204             post '/group' => sub {
205             my $c = shift;
206             $c->parse_autodata;
207             my $group = $c->stash->{autodata}->{group};
208             my $users = $c->stash->{autodata}->{users};
209             delete $c->stash->{autodata};
210             if($c->authz->create_group($group, $users))
211             {
212             $c->render_message('ok', 200);
213             $c->app->emit(create_group => {
214             admin => $c->stash('user'),
215             group => $group,
216             users => $users,
217             });
218             }
219             else
220             {
221             $c->render_message('not ok', 403);
222             }
223             };
224              
225              
226             del '/group/:group' => sub {
227             my $c = shift;
228             my $group = $c->param('group');
229             if($c->authz->delete_group($group))
230             {
231             $c->render_message('ok', 200);
232             $c->app->emit(delete_group => {
233             admin => $c->stash('user'),
234             group => $group,
235             });
236             }
237             else
238             {
239             $c->render_message('not ok', 404);
240             }
241             };
242              
243              
244             post '/group/:group' => sub {
245             my $c = shift;
246             $c->parse_autodata;
247             my $users = $c->stash->{autodata}->{users};
248             my $group = $c->param('group');
249             delete $c->stash->{autodata};
250             if($c->authz->update_group($group, $users))
251             {
252             $c->render_message('ok', 200);
253             $c->app->emit(update_group => {
254             admin => $c->stash('user'),
255             group => $group,
256             users => $users,
257             });
258             }
259             else
260             {
261             $c->render_message('not ok', 404);
262             }
263             };
264              
265              
266             post '/group/:group/#username' => sub {
267             my($c) = @_;
268             my $group = $c->stash('group');
269             my $user = $c->stash('username');
270             if(my $users = $c->authz->add_user_to_group($group, $user))
271             {
272             $c->render_message('ok', 200);
273             $c->app->emit(update_group => {
274             admin => $c->stash('user'),
275             group => $group,
276             users => $users,
277             });
278             }
279             else
280             {
281             $c->render_message('not ok', 404);
282             }
283             };
284              
285              
286             del '/group/:group/#username' => sub {
287             my($c) = @_;
288             my $group = $c->stash('group');
289             my $user = $c->stash('username');
290             if(my $users = $c->authz->remove_user_from_group($group, $user))
291             {
292             $c->render_message('ok', 200);
293             $c->app->emit(update_group => {
294             admin => $c->stash('user'),
295             group => $group,
296             users => $users,
297             });
298             }
299             else
300             {
301             $c->render_message('not ok', 404);
302             }
303             };
304              
305              
306             post '/grant/#group/:action1/(*resource)' => { resource => '/' } => sub {
307             my $c = shift;
308             my($group, $action, $resource) = map { $c->stash($_) } qw( group action1 resource );
309             $resource =~ s/\.(json|yml)$//;
310             if($c->authz->grant($group, $action, $resource))
311             {
312             $c->render_message('ok', 200);
313             $c->app->emit(grant => {
314             admin => $c->stash('user'),
315             group => $group,
316             action => $action,
317             resource => $resource,
318             });
319             }
320             else
321             {
322             $c->render_message('not ok', 404);
323             }
324             };
325              
326              
327             del '/grant/#group/:action1/(*resource)' => { resource => '/' } => sub {
328             my($c) = @_;
329             my($group, $action, $resource) = map { $c->stash($_) } qw( group action1 resource );
330             $resource =~ s/\.(json|yml)$//;
331             if($c->authz->revoke($group, $action, $resource))
332             {
333             $c->render_message('ok', 200);
334             $c->app->emit(revoke => {
335             admin => $c->stash('user'),
336             group => $group,
337             action => $action,
338             resource => $resource,
339             });
340             }
341             else
342             {
343             $c->render_message('not ok', 404);
344             }
345             };
346              
347              
348             get '/grant' => sub {
349             my($c) = @_;
350             $c->stash->{autodata} = $c->authz->granted;
351             };
352              
353              
354             authenticate;
355             authorize 'change_password';
356              
357             post '/user/#username' => sub {
358             my($c) = @_;
359             $c->parse_autodata;
360             my $user = $c->param('username');
361             my $password = eval { $c->stash->{autodata}->{password} } || '';
362             delete $c->stash->{autodata};
363             if($c->auth->change_password($user, $password))
364             {
365             $c->render_message('ok', 200);
366             $c->app->emit(change_password => { admin => $c->stash('user'), user => $user });
367             }
368             else
369             {
370             $c->render_message('not ok', 403);
371             }
372             };
373              
374             1;
375              
376             __END__
377              
378             =pod
379              
380             =encoding UTF-8
381              
382             =head1 NAME
383              
384             PlugAuth::Routes - routes for plugauth
385              
386             =head1 VERSION
387              
388             version 0.35
389              
390             =head1 DESCRIPTION
391              
392             This module defines the HTTP URL routes provided by L<PlugAuth>.
393             This document uses Mojolicious conventions to describe routes,
394             see L<Mojolicious::Guides::Routing> for details.
395              
396             =head1 ROUTES
397              
398             =head2 Public routes
399              
400             These routes work for unauthenticated and unauthorized users.
401              
402             =head3 GET /
403              
404             Returns the string "welcome to plug auth"
405              
406             =head3 GET /auth
407              
408             =over 4
409              
410             =item * if username and password provided using BASIC authentication and are correct
411              
412             Return 200 ok
413              
414             =item * if username and password provided using BASIC authentication but are not correct
415              
416             Return 403 not ok
417              
418             =item * if username and password are not provided using BASIC authentication
419              
420             Return 401 please authenticate
421              
422             =back
423              
424             =head3 GET /authz/user/#user/#action/(*resource)
425              
426             =over 4
427              
428             =item * if the given user (#user) is permitted to perform the given action (#action) on the given resource (*resource)
429              
430             Return 200 ok
431              
432             =item * otherwise
433              
434             Return 403 "unauthorized : $user cannot $action $resource"
435              
436             =back
437              
438             =head3 GET /authz/resources/#user/#action/(*resourceregex)
439              
440             Returns a list of resources that the given user (#user) is permitted to perform
441             action (#action) on. The regex is used to filter the results (*resourceregex).
442              
443             =head3 GET /actions
444              
445             Return a list of actions that PlugAuth knows about.
446              
447             =head3 GET /groups/#user
448              
449             Return a list of groups that the given user (#user) belongs to.
450              
451             Returns 404 not ok if the user does not exist.
452              
453             =head3 GET /host/#host/:tag
454              
455             =over 4
456              
457             =item * if the given host (#host) has the given tag (:tag)
458              
459             return 200 ok
460              
461             =item * otherwise
462              
463             return 403 not ok
464              
465             =back
466              
467             =head3 GET /user
468              
469             Returns a list of all users that PlugAuth knows about.
470              
471             =head3 GET /group
472              
473             Returns a list of all groups that PlugAuth knows about.
474              
475             =head3 GET /users/:group
476              
477             Returns the list of users that belong to the given group (:group)
478              
479             =head2 Accounts Routes
480              
481             These routes are available to users authenticates and authorized to perform
482             the 'accounts' action. They will return
483              
484             =over 4
485              
486             =item * 401
487              
488             If no credentials are provided
489              
490             =item * 403
491              
492             If the user is unauthorized.
493              
494             =item * 503
495              
496             If the PlugAuth server cannot reach itself or the delegated PlugAuth server.
497              
498             =back
499              
500             =head3 POST /user
501              
502             Create a user. The C<username> and C<password> are provided autodata arguments
503             (JSON, YAML, form data, etc).
504              
505             If supported by your authentication plugin (requires C<create_user_cb> to be
506             implemented see L<PlugAuth::Plugin::Auth> for details) You may also optionally
507             include C<groups> as an autodata argument, which specifies the list of groups
508             to which the new user should belong. C<groups> should be a comma separated
509             list stored as a string.
510              
511             Emits event 'create_user' on success
512              
513             $app->on(create_user => sub {
514             my($event, $hash) = @_;
515             my $admin = $hash->{admin}; # user who created the group
516             my $user = $hash->{user};
517             });
518              
519             =head3 DELETE /user/#user
520              
521             Delete the given user (#user). Returns 200 ok on success, 404 not ok on failure.
522              
523             Emits event 'delete_user' on success
524              
525             $app->on(delete_user => sub {
526             my($event, $hash) = @_;
527             my $admin = $hash->{admin}; # user who created the group
528             my $user = $hash->{user};
529             });
530              
531             =head3 POST /group
532              
533             Create a group. The C<group> name and list of C<users> are provided as autodata
534             arguments (JSON, YAML, form data etc). Returns 200 ok on success, 403 not ok
535             on failure.
536              
537             Emits event 'create_group' on success
538              
539             $app->on(create_group => sub {
540             my($event, $hash) = @_;
541             my $admin = $hash->{admin}; # user who created the group
542             my $group = $hash->{group};
543             my $users = $hash->{users};
544             });
545              
546             =head3 DELETE /group/:group
547              
548             Delete the given group (:group). Returns 200 ok on success, 403 not ok on failure.
549              
550             Emits event 'delete_group' on success
551              
552             $app->on(delete_group => sub {
553             my($event, $hash) = @_;
554             my $admin = $hash->{admin}; # user who deleted the group
555             my $group = $hash->{group};
556             });
557              
558             =head3 POST /group/:group
559              
560             Update the list of users belonging to the given group (:group). The list
561             of C<users> is provided as an autodata argument (JSON, YAML, form data etc.).
562             Returns 200 ok on success, 404 not ok on failure.
563              
564             Emits event 'update_group' on success
565              
566             $app->on(update_group => sub {
567             my($event, $hash) = @_;
568             my $admin = $hash->{admin}; # user who updated the group
569             my $group = $hash->{group};
570             my $users = $hash->{users};
571             });
572              
573             =head3 POST /group/:group/#username
574              
575             Add the given user (#username) to the given group (:group).
576             Returns 200 ok on success, 404 not ok on failure.
577              
578             Emits event 'update_group' (see route for POST /group/:group for
579             an example).
580              
581             =head3 DELETE /group/:group/#username
582              
583             Remove the given user (#username) from the given group (:group).
584             Returns 200 ok on success, 404 not ok on failure.
585              
586             Emits event 'update_group' (see route for POST /group/:group for
587             an example).
588              
589             =head3 POST /grant/#group/:action1/(*resource)
590              
591             Grant access to the given group (#group) so they can perform the given action (:action1)
592             on the given resource (*resource). Returns 200 ok on success, 404 not ok on failure.
593              
594             Emits event 'grant' on success
595              
596             $app->on(grant => sub {
597             my($event, $hash) = @_;
598             my $admin = $hash->{admin}; # user who did the granting
599             my $group = $hash->{group};
600             my $action = $hash->{action};
601             my $resource = $hash->{resource};
602             });
603              
604             =head3 DELETE /grant/#group/:action1/(*resource)
605              
606             Revoke permission to the given group (#group) to perform the given action (:action1) on
607             the given resource (*resource). Returns 200 ok on success, 404 not ok on failure.
608              
609             (the action is specified in the route as action1 because action is reserved by
610             L<Mojolicious>).
611              
612             Emits event 'revoke' on success
613              
614             $app->on(revoke => sub {
615             my($event, $hash) = @_;
616             my $admin = $hash->{admin}; # user who did the revoking
617             my $group = $hash->{group};
618             my $action = $hash->{action};
619             my $resource = $hash->{resource};
620             });
621              
622             =head3 GET /grant
623              
624             Get the list of granted permissions.
625              
626             =head2 Change Password routes
627              
628             These routes are available to users authenticates and authorized to perform
629             the 'change_password' action. They will return
630              
631             =over 4
632              
633             =item * 401
634              
635             If no credentials are provided
636              
637             =item * 403
638              
639             If the user is unauthorized.
640              
641             =item * 503
642              
643             If the PlugAuth server cannot reach itself or the delegated PlugAuth server.
644              
645             =back
646              
647             =head3 POST /user/#user
648              
649             Change the password of the given user (#user). The C<password> is provided as
650             an autodata argument (JSON, YAML, form data, etc.). Returns 200 ok on success,
651             403 not ok on failure.
652              
653             Emits event 'change_password' on success
654              
655             $app->on(change_password => sub {
656             my($event, $hash) = @_;
657             my $admin = $hash->{admin}; # user who changed the password
658             my $user = $hash->{user}; # user whos password is changed
659             });
660              
661             =head1 SEE ALSO
662              
663             L<PlugAuth>
664              
665             =head1 AUTHOR
666              
667             Graham Ollis <gollis@sesda3.com>
668              
669             =head1 COPYRIGHT AND LICENSE
670              
671             This software is copyright (c) 2012 by NASA GSFC.
672              
673             This is free software; you can redistribute it and/or modify it under
674             the same terms as the Perl 5 programming language system itself.
675              
676             =cut