File Coverage

blib/lib/Authorize/Rule.pm
Criterion Covered Total %
statement 93 100 93.0
branch 51 64 79.6
condition 10 11 90.9
subroutine 9 11 81.8
pod 6 8 75.0
total 169 194 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.007';
4 18     18   379871 use strict;
  18         50  
  18         810  
5 18     18   88 use warnings;
  18         27  
  18         554  
6 18     18   109 use Carp 'croak';
  18         25  
  18         25565  
7              
8             sub new {
9 19     19 0 1083 my $class = shift;
10 19         81 my %opts = @_;
11              
12 19 100       261 defined $opts{'rules'}
13             or croak 'You must provide rules';
14              
15 18         46 my $rules = $opts{'rules'};
16 18 50       82 ref($rules) eq 'HASH'
17             or croak 'attribute rules must be a hashref';
18              
19             # expand entity groups to their entities
20 18 100       104 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         3  
25 1 50       5 my $group_rules = delete $rules->{$group}
26             or next;
27              
28 1         8 $rules->{$_} = $group_rules
29 1         1 for @{ $opts{'entity_groups'}{$group} };
30             }
31             }
32              
33             # expand resource groups to their entities
34 18 100       73 if ( $opts{'resource_groups'} ) {
35 1 50       5 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         4  
40 1         1 foreach my $resource ( keys %{ $rules->{$entity} } ) {
  1         5  
41 2 100       8 my $in_rsrc = $opts{'resource_groups'}{$resource}
42             or next;
43              
44 1         8 $rules->{$entity}{$_} = $rules->{$entity}{$resource}
45 1         2 for @{$in_rsrc};
46             }
47             }
48              
49             # delete
50 1         3 foreach my $entity ( keys %{$rules} ) {
  1         4  
51 1         8 delete $rules->{$entity}{$_}
52 1         1 for keys %{ $opts{'resource_groups'} };
53             }
54             }
55              
56 18         143 return bless {
57             default => 0, # deny by default
58             %opts,
59             }, $class;
60             }
61              
62             sub default {
63 110     110 1 110 my $self = shift;
64 110 50       225 @_ and croak 'default() is a ro attribute';
65 110         252 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 110     110 1 126 my $self = shift;
82 110 50       241 @_ and croak 'rules() is a ro attribute';
83 110         196 return $self->{'rules'};
84             }
85              
86             sub is_allowed {
87 105     105 1 50400 my $self = shift;
88 105         234 return $self->allowed(@_)->{'action'};
89             }
90              
91             sub allowed {
92 110     110 1 3346 my $self = shift;
93 110         133 my $entity = shift;
94 110         120 my $req_resource = shift;
95 110   100     397 my $req_params = shift || {};
96 110         246 my $default = $self->default;
97 110         629 my $rules = $self->rules;
98 110   50     498 my %result = (
99             entity => $entity,
100             resource => ($req_resource || ''),
101             params => $req_params,
102             );
103              
104             # deny entities that aren't in the rules
105 110 100       313 my $perms = $rules->{$entity}
106             or return { %result, action => $default };
107              
108             # the requested and default
109 109   100     365 my $main_ruleset = $perms->{$req_resource} || [];
110 109   100     294 my $def_ruleset = $perms->{''} || [];
111              
112             # if neither, return default action
113 109 100 100     526 @{ $main_ruleset } || @{ $def_ruleset }
  109         717  
  49         177  
114             or return { %result, action => $default };
115              
116 108         184 foreach my $rulesets ( $main_ruleset, $def_ruleset ) {
117 163         148 my $ruleset_idx = 0;
118 163         125 my $label;
119              
120 163         134 R_SET: foreach my $ruleset ( @{$rulesets} ) {
  163         242  
121 143 100       276 if ( ! ref $ruleset ) {
122 11         10 $label = $ruleset;
123 11         15 next R_SET;
124             }
125              
126 132         130 $ruleset_idx++;
127              
128 132         263 my $action = $self->match_ruleset( $ruleset, $req_params );
129              
130 131 100       1585 if ( defined $action ) {
131 102         901 my %full_result = (
132             %result,
133             ruleset_idx => $ruleset_idx,
134             ( label => $label )x!! $label,
135             );
136              
137 102 100       264 $full_result{'action'} = ref $action eq 'CODE' ?
138             $action->( \%full_result ) :
139             $action;
140              
141 102         2707 return \%full_result;
142             }
143              
144 29         61 undef $label;
145             }
146             }
147              
148 5         40 return { %result, action => $default };
149             }
150              
151             sub match_ruleset {
152 132     132 0 128 my $self = shift;
153 132         122 my $ruleset = shift;
154 132         124 my $req_params = shift;
155              
156 132 50       122 my ( $action, @rules ) = @{$ruleset}
  132         358  
157             or return;
158              
159             # an empty return() is a failure to match
160             # if matching of a rule succeeds, we just move to the next rule
161 132         191 foreach my $rule (@rules) {
162 64 100       213 if ( ref $rule eq 'HASH' ) {
    100          
    50          
163             # check defined params by rule against requested params
164 49         42 KEY: foreach my $key ( keys %{$rule} ) {
  49         119  
165 50 100       103 if ( defined $rule->{$key} ) {
166             # check if key is missing
167 49 100       147 defined $req_params->{$key}
168             or return;
169             } else {
170             # check the key exists and value is defined
171 1 50       4 exists $req_params->{$key}
172             and return;
173              
174             # don't continue checking the value in this case
175             # because it's undefined
176 1         6 next KEY;
177             }
178              
179             # check matching against a code reference
180 38 100       738 if ( ref $rule->{$key} eq 'CODE' ) {
    100          
    100          
181 2 100       6 $req_params->{$key} eq $rule->{$key}->($req_params)
182             or return;
183             } elsif ( ref $rule->{$key} eq 'Regexp' ) {
184 3 100       36 $req_params->{$key} =~ $rule->{$key}
185             or return;
186             } elsif ( ref $rule->{$key} ) {
187 1         160 croak 'Rule keys can only be strings, regexps, or code';
188             } else {
189             # check matching against a simple string
190 32 100       137 $req_params->{$key} eq $rule->{$key}
191             or return; # no match
192             }
193             }
194             } elsif ( ref $rule eq 'CODE' ) {
195 2 100       8 $rule->($req_params)
196             or return;
197             } elsif ( ! ref $rule ) {
198 13 100       32 defined $req_params->{$rule}
199             or return; # no match
200             } else {
201 0         0 croak 'Unknown rule type';
202             }
203             }
204              
205 102         1510 return $action;
206             }
207              
208             1;
209              
210             __END__