File Coverage

blib/lib/SVN/Access.pm
Criterion Covered Total %
statement 150 195 76.9
branch 68 98 69.3
condition 14 21 66.6
subroutine 22 23 95.6
pod 16 17 94.1
total 270 354 76.2


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