File Coverage

blib/lib/Catalyst/ActionRole/ACL.pm
Criterion Covered Total %
statement 1 3 33.3
branch n/a
condition n/a
subroutine 1 1 100.0
pod n/a
total 2 4 50.0


line stmt bran cond sub pod time code
1             package Catalyst::ActionRole::ACL;
2 1     1   1576 use Moose::Role;
  0            
  0            
3             use namespace::autoclean;
4              
5             use vars qw($VERSION);
6             $VERSION = '0.07'; # REMEMBER TO BUMP VERSION IN Action::Role::ACL ALSO!
7              
8             =head1 NAME
9              
10             Catalyst::ActionRole::ACL - User role-based authorization action class
11              
12             =head1 SYNOPSIS
13              
14             package MyApp::Controller::Foo;
15             use Moose;
16             use namespace::autoclean;
17              
18             BEGIN { extends 'Catalyst::Controller' }
19              
20             sub foo
21             :Local
22             :Does(ACL)
23             :RequiresRole(admin)
24             :ACLDetachTo(denied)
25             {
26             my ($self, $c) = @_;
27             ...
28             }
29              
30             sub denied :Private {
31             my ($self, $c) = @_;
32              
33             $c->res->status('403');
34             $c->res->body('Denied!');
35             }
36              
37             =head1 DESCRIPTION
38              
39             Provides a reusable action role
40             for user role-based authorization.
41             ACLs are applied via the assignment of attributes to
42             application action subroutines.
43              
44             =head1 REQUIRED ATTRIBUTES
45              
46             Failure to include the following required attributes will result in an exception
47             when the ACL::Role action's constructor is called.
48              
49             =head2 ACLDetachTo
50              
51             The name of an action to which the request should be detached if it is
52             determined that ACLs are not satisfied for this user and the resource he
53             is attempting to access.
54              
55             =head2 RequiresRole and AllowedRole
56              
57             The action must include at least one of these attributes, otherwise the Role::ACL
58             constructor will throw an exception.
59              
60             =head1 Processing of ACLs
61              
62             One or more roles may be associated with an action.
63              
64             User roles are fetched via the invocation of the context "user" object's "roles"
65             method.
66              
67             Roles specified with the RequiresRole attribute are checked before roles
68             specified with the AllowedRole attribute.
69              
70             The mandatory ACLDetachTo attribute specifies the name of the action to which
71             execution will detach on access violation.
72              
73             ACLs may be applied to chained actions so that different roles are required or
74             allowed for each link in the chain (or no roles at all).
75              
76             ACLDetachTo allows us to short-circuit traversal of an action chain as soon as
77             access is denied to one of the actions in the chain by its ACL.
78              
79             =head2 Examples
80              
81             # this is an invalid action
82             sub broken
83             :Local
84             :Does(ACL)
85             {
86             my ($self, $c) = @_;
87             ...
88             }
89              
90             This action will cause an exception because it's missing the ACLDetachTo attribute
91             and has neither a RequiresRole nor an AllowedRole attribute. A Role::ACL action
92             must include at least one RequiresRole or AllowedRole attribute.
93              
94             sub foo
95             :Local
96             :Does(ACL)
97             :RequiresRole(admin)
98             :ACLDetachTo(denied)
99             {
100             my ($self, $c) = @_;
101             ...
102             }
103              
104             This action may only be executed by users with the 'admin' role.
105              
106             sub bar :Local
107             :Does(ACL)
108             :RequiresRole(admin)
109             :AllowedRole(editor)
110             :AllowedRole(writer)
111             :ACLDetachTo(denied)
112             {
113             my ($self, $c) = @_;
114             ...
115             }
116              
117             This action requires that the user has the 'admin' role and
118             either the 'editor' or 'writer' role (or both).
119              
120             sub easy :Local
121             :Does(ACL)
122             :AllowedRole(admin)
123             :AllowedRole(user)
124             :ACLDetachTo(denied)
125             {
126             my ($self, $c) = @_;
127             ...
128             }
129              
130             Any user with either the 'admin' or 'user' role may execute this action.
131              
132             =head1 WRAPPED METHODS
133              
134             =cut
135              
136             =head2 C<BUILD( $args )>
137              
138             Throws an exception if parameters are missing or invalid.
139              
140             =cut
141              
142             sub BUILD { }
143              
144             after BUILD => sub {
145             my $class = shift;
146             my ($args) = @_;
147              
148             my $attr = $args->{attributes};
149              
150             unless (exists $attr->{RequiresRole} || exists $attr->{AllowedRole}) {
151             Catalyst::Exception->throw(
152             "Action '$args->{reverse}' requires at least one RequiresRole or AllowedRole attribute");
153             }
154             unless (exists $attr->{ACLDetachTo} && $attr->{ACLDetachTo}) {
155             Catalyst::Exception->throw(
156             "Action '$args->{reverse}' requires the ACLDetachTo(<action>) attribute");
157             }
158             };
159              
160             =head2 C<execute( $controller, $c )>
161              
162             Overrides &Catalyst::Action::execute.
163              
164             In order for delegation to occur, the context 'user' object must exist (authenticated user) and
165             the C<can_visit> method must return a true value.
166              
167             See L<Catalyst::Action|METHODS/action>
168              
169             =cut
170              
171             around execute => sub {
172             my $orig = shift;
173             my $self = shift;
174             my ($controller, $c) = @_;
175              
176             if ($c->user) {
177             if ($self->can_visit($c)) {
178             return $self->$orig(@_);
179             }
180             }
181              
182             my $denied = $self->attributes->{ACLDetachTo}[0];
183              
184             $c->detach($denied);
185             };
186              
187             =head2 C<can_visit( $c )>
188              
189             Return true if the authenticated user can visit this action.
190              
191             This method is useful for determining in advance if a user can execute
192             a given action.
193              
194             =cut
195              
196             sub can_visit {
197             my ($self, $c) = @_;
198              
199             my $user = $c->user;
200              
201             return unless $user;
202              
203             return unless
204             $user->supports('roles') && $user->can('roles');
205              
206             my %user_has = map {$_,1} $user->roles;
207              
208             my $required = $self->attributes->{RequiresRole};
209             my $allowed = $self->attributes->{AllowedRole};
210              
211             if ($required && $allowed) {
212             for my $role (@$required) {
213             return unless $user_has{$role};
214             }
215             for my $role (@$allowed) {
216             return 1 if $user_has{$role};
217             }
218             return;
219             }
220             elsif ($required) {
221             for my $role (@$required) {
222             return unless $user_has{$role};
223             }
224             return 1;
225             }
226             elsif ($allowed) {
227             for my $role (@$allowed) {
228             return 1 if $user_has{$role};
229             }
230             return;
231             }
232              
233             return;
234             }
235              
236             1;
237              
238             =head1 AUTHOR
239              
240             David P.C. Wollmann E<lt>converter42@gmail.comE<gt>
241              
242             =head1 CONTRIBUTORS
243              
244             Converted from an action class to an action role by Tomas Doran (t0m)
245              
246             =head1 BUGS
247              
248             This is new code. Find the bugs and report them, please.
249              
250             =head1 COPYRIGHT & LICENSE
251              
252             Copyright 2009 by David P.C. Wollmann
253              
254             This program is free software; you can redistribute it and/or modify it under
255             the same terms as Perl itself.
256