File Coverage

blib/lib/SVN/Access.pm
Criterion Covered Total %
statement 162 207 78.2
branch 80 112 71.4
condition 14 21 66.6
subroutine 23 24 95.8
pod 17 18 94.4
total 296 382 77.4


line stmt bran cond sub pod time code
1             package SVN::Access;
2              
3 1     1   23445 use SVN::Access::Group;
  1         3  
  1         39  
4 1     1   385 use SVN::Access::Resource;
  1         3  
  1         27  
5              
6 1     1   563 use open ':encoding(utf8)';
  1         1117  
  1         6  
7              
8 1     1   10934 use 5.006001;
  1         3  
  1         25  
9 1     1   3 use strict;
  1         2  
  1         29  
10 1     1   4 use warnings;
  1         1  
  1         2780  
11              
12             our $VERSION = '0.10';
13              
14             sub new {
15 9     9 1 2739 my ($class, %attr) = @_;
16 9         40 my $self = bless(\%attr, $class);
17            
18             # it's important that we have this.
19 9 50       37 die "No ACL file specified!" unless $attr{acl_file};
20              
21 9 100       144 if (-e $attr{acl_file}) {
22             # parse the file in.
23 8         39 $self->parse_acl;
24             } else {
25             # empty acl.
26 1         4 $attr{acl} = {};
27             }
28 9         37 return $self;
29             }
30              
31             sub parse_acl {
32 8     8 0 15 my ($self) = @_;
33 8 50       209 open(ACL, '<', $self->{acl_file}) or die "Can't open SVN Access file " . $self->{acl_file} . ": $!";
34 8         404 my $current_resource;
35             my $statement;
36 8         146 while (my $line = ) {
37             # ignore comments (properly defined)
38 129 100       1532 next if $line =~ /^#/;
39 125 100       361 $line =~ s/\s*#.*$// unless $self->{pedantic};
40              
41             # get rid of trailing whitespace.
42 125         919 $line =~ s/[\s\r\n]+$//;
43 125 100       410 next unless $line;
44              
45             # handle line continuations
46 94 100       333 if ($line =~ /^\s+(.+)$/) {
47 4         18 $statement .= " $1";
48             } else {
49 90         120 $statement = $line;
50             }
51              
52             # lookahead and see if the next line is a line continuation
53 94         548 my $pos = tell(ACL);
54 94         581 my $nextline = ;
55 94         1278 seek(ACL, $pos, 0); # rewind the filehandle to where we were.
56 94 100 100     518 if ($nextline && $nextline =~ /^[ \t]+\S/) {
57 4         175 next;
58             }
59              
60 90 50       147 next unless $statement;
61              
62 90 100       350 if ($statement =~ /^\[\s*(.+?)\s*\]$/) {
63             # this statement is defining a new resource.
64 31         90 $current_resource = $1;
65 31 100       108 unless ($current_resource =~ /^(?:groups|aliases)$/) {
66 20         61 $self->add_resource($current_resource);
67             }
68             } else {
69             # both groups and resources need this parsed.
70 59         398 my ($k, $v) = $statement =~ /^(.+?)\s*=\s*(.*?)$/;
71              
72             # if the previous split didn't work, there's a syntax error
73 59 100       129 unless ($k) {
74 2         19 warn "Unrecognized line $statement\n";
75 2         27 next;
76             }
77              
78 57 100       162 if ($current_resource eq "groups") {
    100          
79             # this is a group
80 17         222 $self->add_group($k, split(/\s*,\s*/, $v));
81             } elsif ($current_resource eq "aliases") {
82             # aliases are simple k=v, so let's just store them in ourselves.
83 4         14 $self->add_alias($k, $v);
84             } else {
85             # this is a generic resource
86 36 50       86 if (my $resource = $self->resource($current_resource)) {
87 36         85 $resource->authorize($k => $v);
88             } else {
89 0         0 $self->add_resource($current_resource, $k, $v);
90             }
91             }
92             }
93              
94 88         1883 $statement = undef;
95             }
96            
97             # make sure this isn't empty.
98 8 100       30 unless (ref($self->{acl}->{aliases}) eq "HASH") {
99 4         17 $self->{acl}->{aliases} = {};
100             }
101            
102 8         89 close (ACL);
103             }
104              
105             sub verify_acl {
106 6     6 1 8 my ($self) = @_;
107              
108             # Check for references to undefined groups (Thanks Jesse!)
109 6         8 my (%groups, @errors);
110 6 100       16 if ($self->groups) {
111 3         6 foreach my $group ($self->groups) {
112 3         10 $groups{$group->name}++;
113             }
114             }
115              
116 6         16 foreach my $resource ($self->resources) {
117 20 100 66     102 if (defined($resource) && $resource->authorized) {
118 19         18 foreach my $k (keys %{$resource->authorized}) {
  19         40  
119 35 100       384 if ( $k =~ /^@(.*)/ ) {
120 5 100       27 unless ( $groups{$1} ) {
121 2         8 push(@errors, "[error] An authz rule (" . $resource->name . ") refers to group '\@$1', which is undefined");
122             }
123             }
124             }
125             }
126             }
127              
128 6 100       50 return scalar(@errors) ? join("\n", @errors) : undef;
129             }
130              
131             sub write_acl {
132 6     6 1 13 my ($self, $out) = @_;
133              
134             # verify the ACL has no errors before writing it out
135 6 100       17 if (my $error = $self->verify_acl) {
136 1         9 die "Error found in ACL:\n$error\n";
137             }
138              
139 5 50 33     27 if (ref \$out eq "GLOB" or ref $out) {
140 0         0 *ACL = $out;
141             }
142             else {
143 5 50       16 $out = $self->{acl_file} unless $out;
144 5 50       442 open (ACL, '>', $out) or warn "Can't open ACL file " . $out . " for writing: $!\n";
145             }
146            
147             # aliases now supported!
148 5 100       278 if (scalar(keys %{$self->aliases})) {
  5         15  
149 2         12 print ACL "[aliases]\n";
150 2         4 foreach my $alias (keys %{$self->aliases}) {
  2         6  
151 2         9 print ACL $alias . " = " . $self->aliases->{$alias} . "\n";
152             }
153 2         6 print ACL "\n";
154             }
155            
156             # groups now second to aliases
157 5 100       12 if ($self->groups) {
158 3         19 print ACL "[groups]\n";
159 3         8 foreach my $group ($self->groups) {
160 3         10 print ACL $group->name . " = " . join(', ', $group->members) . "\n";
161             }
162 3         11 print ACL "\n";
163             }
164            
165 5         15 foreach my $resource ($self->resources) {
166 14 100 66     49 if (defined($resource) && $resource->authorized) {
167 13         33 print ACL "[" . $resource->name . "]\n";
168 13         18 while (my ($k, $v) = (each %{$resource->authorized})) {
  36         75  
169 23         378 print ACL "$k = $v\n";
170             }
171 13         112 print ACL "\n";
172             }
173             }
174            
175 5         247 close(ACL);
176             }
177              
178             sub write_pretty {
179 0     0 1 0 my ($self) = @_;
180              
181             # verify the ACL has no errors before writing it out
182 0 0       0 if (my $error = $self->verify_acl) {
183 0         0 die "Error found in ACL:\n$error\n";
184             }
185              
186 0         0 my $max_len = 0;
187              
188             # Compile a list of names that will appear on the left side
189 0         0 my @names;
190 0 0       0 if (scalar(keys %{$self->aliases})) {
  0         0  
191 0         0 foreach my $alias (keys %{$self->aliases}) {
  0         0  
192 0         0 push(@names, $alias);
193             }
194             }
195            
196 0 0       0 if ($self->groups) {
197 0         0 for ($self->groups) {
198 0         0 push(@names, $_->name);
199             }
200             }
201 0 0       0 if ($self->resources) {
202 0         0 for ($self->resources) {
203 0         0 push(@names, keys(%{$_->authorized}));
  0         0  
204             }
205             }
206              
207             # Go through that list looking for the longest name
208 0         0 for (@names) {
209 0 0       0 $max_len = length($_) >= $max_len ? length($_) : $max_len;
210             }
211              
212 0 0       0 open (ACL, '>', $self->{acl_file}) or warn "Can't open ACL file " . $self->{acl_file} . " for writing: $!\n";
213            
214             # aliases now fully supported!
215 0 0       0 if (scalar(keys %{$self->aliases})) {
  0         0  
216 0         0 print ACL "[aliases]\n";
217 0         0 foreach my $alias (keys %{$self->aliases}) {
  0         0  
218 0         0 print ACL $alias . " " x ($max_len - length($alias)) . " = " . $self->aliases->{$alias} . "\n";
219             }
220 0         0 print "\n";
221             }
222            
223             # groups now second?
224 0 0       0 if ($self->groups) {
225 0         0 print ACL "[groups]\n";
226 0         0 foreach my $group ($self->groups) {
227 0         0 print ACL $group->name . " " x ($max_len - length($group->name)) . " = " . join(', ', $group->members) . "\n";
228             }
229 0         0 print "\n";
230             }
231            
232 0         0 foreach my $resource ($self->resources) {
233 0 0 0     0 if (defined($resource) && $resource->authorized) {
234 0         0 print ACL "[" . $resource->name . "]\n";
235 0         0 while (my ($k, $v) = (each %{$resource->authorized})) {
  0         0  
236 0         0 print ACL "$k" . " " x ($max_len - length($k)) . " = $v\n";
237             }
238 0         0 print ACL "\n";
239             }
240             }
241 0         0 close(ACL);
242             }
243              
244             sub add_alias {
245 6     6 1 14 my ($self, $alias_name, $aliased) = @_;
246 6         30 $self->{acl}->{aliases}->{$alias_name} = $aliased;
247             }
248              
249             sub remove_alias {
250 2     2 1 14 my ($self, $alias_name) = @_;
251 2         10 delete $self->{acl}->{aliases}->{$alias_name};
252             }
253              
254             sub alias {
255 4     4 1 8 my ($self, $alias_name) = @_;
256 4 100       17 if (exists ($self->{acl}->{aliases}->{$alias_name})) {
257 3         21 return $self->{acl}->{aliases}->{$alias_name};
258             }
259 1         33 return undef;
260             }
261              
262             sub aliases {
263 10     10 1 22 my ($self) = @_;
264             # give em something if we got nothing!
265 10 100       36 unless (ref($self->{acl}->{aliases}) eq "HASH") {
266 1         3 $self->{acl}->{aliases} = {};
267             }
268 10         47 return $self->{acl}->{aliases};
269             }
270              
271             sub add_resource {
272 28     28 1 78 my ($self, $resource_name, @access) = @_;
273 28 100       90 if ($resource_name eq "name") {
274 1         3 $resource_name = shift(@access);
275             }
276            
277 28 50       61 if ($self->resource($resource_name)) {
    50          
278 0         0 die "Can't add new resource $resource_name: resource already exists!\n";
279             } elsif ($resource_name !~ /^(?:\S+\:)?\/.*$/) { # Thanks Matt
280 0         0 die "Invalid resource format in $resource_name! (format 'repo:/path')!\n";
281             } else {
282 28         134 my $resource = SVN::Access::Resource->new(
283             name => $resource_name,
284             authorized => \@access,
285             );
286 28         36 push(@{$self->{acl}->{resources}}, $resource);
  28         72  
287 28         78 return $resource;
288             }
289             }
290              
291             sub remove_resource {
292 8     8 1 383 my ($self, $resource_name) = @_;
293 8         9 my @resources;
294 8         18 foreach my $resource ($self->resources) {
295 33 100       78 push(@resources, $resource) unless $resource->name eq $resource_name;
296             }
297 8 100       38 $self->{acl}->{resources} = scalar(@resources) ? \@resources : undef;
298             }
299              
300             sub resources {
301 91     91 1 157 my ($self) = @_;
302 91 100       266 if (ref($self->{acl}->{resources}) eq "ARRAY") {
303 79         71 return (@{$self->{acl}->{resources}});
  79         256  
304             } else {
305 12         37 return (undef);
306             }
307             }
308              
309             sub resource {
310 71     71 1 133 my ($self, $resource_name) = @_;
311 71         146 foreach my $resource ($self->resources) {
312 197 100 100     642 return $resource if defined($resource) && $resource->name eq $resource_name;
313             }
314 28         269 return undef;
315             }
316              
317             sub add_group {
318 18     18 1 68 my ($self, $group_name, @initial_members) = @_;
319              
320             # get rid of the @ symbol.
321 18 100       84 $group_name =~ s/\@//g unless $self->{pedantic};
322              
323 18 50       51 if ($self->group($group_name)) {
324 0         0 die "Can't add new group $group_name: group already exists!\n";
325             } else {
326 18         87 my $group = SVN::Access::Group->new(
327             name => $group_name,
328             members => \@initial_members,
329             );
330 18         24 push(@{$self->{acl}->{groups}}, $group);
  18         94  
331 18         44 return $group;
332             }
333             }
334              
335             sub remove_group {
336 1     1 1 3 my ($self, $group_name) = @_;
337 1         2 my @groups;
338              
339             # get rid of the @ symbol.
340 1         3 $group_name =~ s/\@//g;
341 1         4 foreach my $group ($self->groups) {
342 1 50       4 push(@groups, $group) unless $group->name eq $group_name;
343             }
344              
345 1 50       8 $self->{acl}->{groups} = scalar(@groups) ? \@groups : undef;
346             }
347              
348             sub groups {
349 55     55 1 62 my ($self) = @_;
350 55 100       177 if (ref($self->{acl}->{groups}) eq "ARRAY") {
351 41         43 return (@{$self->{acl}->{groups}});
  41         133  
352             } else {
353 14         47 return (undef);
354             }
355             }
356              
357             sub group {
358 36     36 1 4864 my ($self, $group_name) = @_;
359 36         78 foreach my $group ($self->groups) {
360 71 100 100     270 return $group if defined($group) && $group->name eq $group_name;
361             }
362 18         53 return undef;
363             }
364              
365             sub resolve {
366 8     8 1 472 my $self = shift;
367 8         16 my @res;
368              
369 8         18 foreach my $e (@_) {
370 8 100       45 if ($e =~ /^\@(.+)/) {
    100          
371 4 50       10 push @res, map $self->resolve($_), $self->group($1)->members()
372             if $self->group($1);
373             } elsif ($e =~ /^\&(.+)/) {
374 1 50       22 push @res, map $self->resolve($_), $self->alias($1)
375             if $self->alias($1);
376             } else {
377 3         8 push @res, $e;
378             }
379             }
380              
381 8         32 return @res;
382             }
383              
384             1;
385             __END__