File Coverage

blib/lib/Authorize/Rule.pm
Criterion Covered Total %
statement 97 104 93.2
branch 51 64 79.6
condition 20 23 86.9
subroutine 9 11 81.8
pod 6 8 75.0
total 183 210 87.1


line stmt bran cond sub pod time code
1             package Authorize::Rule;
2             # ABSTRACT: Rule-based authorization mechanism
3             $Authorize::Rule::VERSION = '0.008';
4 19     19   317819 use strict;
  19         40  
  19         711  
5 19     19   82 use warnings;
  19         26  
  19         516  
6 19     19   77 use Carp 'croak';
  19         25  
  19         17709  
7              
8             sub new {
9 20     20 0 1350 my $class = shift;
10 20         73 my %opts = @_;
11              
12 20 100       250 defined $opts{'rules'}
13             or croak 'You must provide rules';
14              
15 19         41 my $rules = $opts{'rules'};
16 19 50       75 ref($rules) eq 'HASH'
17             or croak 'attribute rules must be a hashref';
18              
19             # expand entity groups to their entities
20 19 100       93 if ( $opts{'entity_groups'} ) {
21 1 50       4 ref( $opts{'entity_groups'} ) eq 'HASH'
22             or croak 'attribute entity_groups must be a hashref';
23              
24 1         2 foreach my $group ( keys %{ $opts{'entity_groups'} } ) {
  1         5  
25 1 50       4 my $group_rules = delete $rules->{$group}
26             or next;
27              
28 1         6 $rules->{$_} = $group_rules
29 1         2 for @{ $opts{'entity_groups'}{$group} };
30             }
31             }
32              
33             # expand resource groups to their entities
34 19 100       68 if ( $opts{'resource_groups'} ) {
35 1 50       4 ref( $opts{'resource_groups'} ) eq 'HASH'
36             or croak 'attribute resource_groups must be a hashref';
37              
38             # populate
39 1         2 foreach my $entity ( keys %{$rules} ) {
  1         2  
40 1         1 foreach my $resource ( keys %{ $rules->{$entity} } ) {
  1         3  
41 2 100       6 my $in_rsrc = $opts{'resource_groups'}{$resource}
42             or next;
43              
44 1         7 $rules->{$entity}{$_} = $rules->{$entity}{$resource}
45 1         2 for @{$in_rsrc};
46             }
47             }
48              
49             # delete
50 1         2 foreach my $entity ( keys %{$rules} ) {
  1         2  
51 1         5 delete $rules->{$entity}{$_}
52 1         2 for keys %{ $opts{'resource_groups'} };
53             }
54             }
55              
56 19         124 return bless {
57             default => 0, # deny by default
58             %opts,
59             }, $class;
60             }
61              
62             sub default {
63 115     115 1 122 my $self = shift;
64 115 50       226 @_ and croak 'default() is a ro attribute';
65 115         240 return $self->{'default'};
66             }
67              
68             sub entity_groups {
69 0     0 1 0 my $self = shift;
70 0 0       0 @_ and croak 'entity_groups() is a ro attribute';
71 0         0 return $self->{'entity_groups'};
72             }
73              
74             sub resource_groups {
75 0     0 1 0 my $self = shift;
76 0 0       0 @_ and croak 'resource_groups() is a ro attribute';
77 0         0 return $self->{'resource_groups'};
78             }
79              
80             sub rules {
81 115     115 1 130 my $self = shift;
82 115 50       226 @_ and croak 'rules() is a ro attribute';
83 115         162 return $self->{'rules'};
84             }
85              
86             sub is_allowed {
87 110     110 1 49035 my $self = shift;
88 110         269 return $self->allowed(@_)->{'action'};
89             }
90              
91             sub allowed {
92 115     115 1 3508 my $self = shift;
93 115         121 my $entity = shift;
94 115         107 my $req_resource = shift;
95 115   100     394 my $req_params = shift || {};
96 115         216 my $default = $self->default;
97 115         588 my $rules = $self->rules;
98 115   50     446 my %result = (
99             entity => $entity,
100             resource => ($req_resource || ''),
101             params => $req_params,
102             );
103              
104 115   100     260 my $perms = $rules->{$entity} || {};
105 115   100     379 my $all_entities_perms = $rules->{''} || {};
106              
107             # deny entities that aren't in the rules
108 115 50 33     1068 $perms || $all_entities_perms
109             or return { %result, action => $default };
110              
111             # the requested and default
112 115   100     295 my $main_ruleset = $perms->{$req_resource} || [];
113 115   100     251 my $def_ruleset = $perms->{''} || [];
114              
115             # perm for all the entities. Lower priority than main&def ruleset
116             # we don't need to check $all_entities_perms->{''} because we have $default
117 115   100     327 my $all_entities_ruleset = $all_entities_perms->{$req_resource} || [];
118              
119             # if neither, return default action
120 115 100 100     106 @{ $main_ruleset } || @{ $def_ruleset } || @{ $all_entities_ruleset }
  115   100     317  
  54         166  
  6         44  
121             or return { %result, action => $default };
122              
123 111         193 foreach my $rulesets ( $main_ruleset, $def_ruleset, $all_entities_ruleset) {
124 175         148 my $ruleset_idx = 0;
125 175         132 my $label;
126              
127 175         140 R_SET: foreach my $ruleset ( @{$rulesets} ) {
  175         250  
128 146 100       289 if ( ! ref $ruleset ) {
129 11         8 $label = $ruleset;
130 11         15 next R_SET;
131             }
132              
133 135         104 $ruleset_idx++;
134              
135 135         224 my $action = $self->match_ruleset( $ruleset, $req_params );
136              
137 134 100       903 if ( defined $action ) {
138 105         895 my %full_result = (
139             %result,
140             ruleset_idx => $ruleset_idx,
141             ( label => $label )x!! $label,
142             );
143              
144 105 100       297 $full_result{'action'} = ref $action eq 'CODE' ?
145             $action->( \%full_result ) :
146             $action;
147              
148 105         1847 return \%full_result;
149             }
150              
151 29         43 undef $label;
152             }
153             }
154              
155 5         479 return { %result, action => $default };
156             }
157              
158             sub match_ruleset {
159 135     135 0 126 my $self = shift;
160 135         105 my $ruleset = shift;
161 135         125 my $req_params = shift;
162              
163 135 50       116 my ( $action, @rules ) = @{$ruleset}
  135         336  
164             or return;
165              
166             # an empty return() is a failure to match
167             # if matching of a rule succeeds, we just move to the next rule
168 135         165 foreach my $rule (@rules) {
169 64 100       562 if ( ref $rule eq 'HASH' ) {
    100          
    50          
170             # check defined params by rule against requested params
171 49         79 KEY: foreach my $key ( keys %{$rule} ) {
  49         148  
172 50 100       82 if ( defined $rule->{$key} ) {
173             # check if key is missing
174 48 100       128 defined $req_params->{$key}
175             or return;
176             } else {
177             # check the key exists and value is defined
178 2 100       5 exists $req_params->{$key}
179             and return;
180              
181             # don't continue checking the value in this case
182             # because it's undefined
183 1         2 next KEY;
184             }
185              
186             # check matching against a code reference
187 37 100       546 if ( ref $rule->{$key} eq 'CODE' ) {
    100          
    100          
188 2 100       5 $req_params->{$key} eq $rule->{$key}->($req_params)
189             or return;
190             } elsif ( ref $rule->{$key} eq 'Regexp' ) {
191 3 100       28 $req_params->{$key} =~ $rule->{$key}
192             or return;
193             } elsif ( ref $rule->{$key} ) {
194 1         100 croak 'Rule keys can only be strings, regexps, or code';
195             } else {
196             # check matching against a simple string
197 31 100       568 $req_params->{$key} eq $rule->{$key}
198             or return; # no match
199             }
200             }
201             } elsif ( ref $rule eq 'CODE' ) {
202 2 100       18 $rule->($req_params)
203             or return;
204             } elsif ( ! ref $rule ) {
205 13 100       30 defined $req_params->{$rule}
206             or return; # no match
207             } else {
208 0         0 croak 'Unknown rule type';
209             }
210             }
211              
212 105         792 return $action;
213             }
214              
215             1;
216              
217             __END__