File Coverage

blib/lib/WE/Util/Permissions.pm
Criterion Covered Total %
statement 113 133 84.9
branch 42 60 70.0
condition 2 3 66.6
subroutine 16 17 94.1
pod 7 7 100.0
total 180 220 81.8


line stmt bran cond sub pod time code
1             # -*- perl -*-
2              
3             #
4             # $Id: Permissions.pm,v 1.10 2004/10/11 22:08:40 eserte Exp $
5             # Author: Slaven Rezic
6             #
7             # Copyright (C) 2001 Online Office Berlin. All rights reserved.
8             # Copyright (C) 2002,2004 Slaven Rezic.
9             # This is free software; you can redistribute it and/or modify it under the
10             # terms of the GNU General Public License, see the file COPYING.
11              
12             #
13             # Mail: slaven@rezic.de
14             # WWW: http://we-framework.sourceforge.net
15             #
16              
17             package WE::Util::Permissions;
18 2     2   6722 use WE::Util::GenericTree::FromString;
  2         6  
  2         57  
19              
20 2     2   12 use strict;
  2         3  
  2         61  
21 2     2   10 use vars qw($VERSION);
  2         3  
  2         141  
22             $VERSION = sprintf("%d.%02d", q$Revision: 1.10 $ =~ /(\d+)\.(\d+)/);
23              
24 2     2   12 use constant DEBUG => 0; # possible values 0 .. 2
  2         2  
  2         3944  
25              
26             =head1 NAME
27              
28             WE::Util::Permissions - rule-based permission model
29              
30             =head1 SYNOPSIS
31              
32             use WE::Util::Permissions;
33             $p = WE::Util::Permissions->new(-file => $permissionsfile);
34             $p->is_allowed(-user => "user", -group => \@groups, -process => "delete");
35              
36             =head1 DESCRIPTION
37              
38             This is a rule-based permission system. All permissions are stored in
39             a single file, so it is easy to see all the permissions on one look.
40              
41             See L for a description of this file's
42             syntax.
43              
44             =head2 METHODS
45              
46             =over
47              
48             =item new($class, %args)
49              
50             Create a C object. At least one of the following named parameters should be given:
51              
52             =over
53              
54             =item -string => $string
55              
56             A string with the permission data.
57              
58             =item -file => $file
59              
60             A file with the permission data.
61              
62             =item -objectfromfile => $file
63              
64             A file containg a dump of a Permission object.
65              
66             =back
67              
68             =cut
69              
70             sub new {
71 13     13 1 10913 my($class, %args) = @_;
72 13         32 my $self = {};
73 13         49 $self->{Directives} = {};
74 13         39 bless $self, $class;
75 13 50       47 if ($args{'-string'}) {
    0          
    0          
76 13         43 $self->parse($args{'-string'});
77             } elsif ($args{'-file'}) {
78 0         0 local $/ = undef;
79 0 0       0 open(F, $args{'-file'}) or die "Can't open $args{'-file'}: $!";
80 0         0 my($s) = ;
81 0         0 close F;
82 0         0 $self->parse($s);
83             } elsif ($args{'-objectfromfile'}) {
84 0         0 local $/ = undef;
85 0 0       0 open(F, $args{'-objectfromfile'}) or die "Can't open $args{'-objectfromfile'}: $!";
86 0         0 my($s) = ;
87 0         0 close F;
88 0         0 $self->_eval_obj($s);
89             } else {
90 0         0 die "Either -string or -file should be given as argument!";
91             }
92 12         53 $self;
93             }
94              
95             =item parse($string)
96              
97             Internal function. Parse the C<$string> into the internal
98             representation. Normally, this is called automatically on construction
99             time.
100              
101             =cut
102              
103             sub parse {
104 13     13 1 25 my($self, $string) = @_;
105 13         23 my $new_s = "";
106 13         19 my $directives = {};
107 13         79 foreach my $line (split/\n/, $string) {
108 91         166 $line =~ s/\#.*$//; # delete comments
109 91         236 $line =~ s/\s+$//; # delete whitespace on end
110 91 100       197 next if $line eq ''; # ignore empty lines
111 84 100       271 if (my($key,$val) = $line =~ /^!\s*(\S+)\s*:\s*(.+)$/) { # directive
112 10 100       33 if (exists $directives->{$key}) {
113 1         15 die "Can't set multiple $key directives in permissions file";
114             }
115 9         26 $directives->{$key} = $val;
116             }
117 83         178 $new_s .= $line . "\n";
118             }
119 12         92 my $tree = WE::Util::GenericTree::FromString->new($new_s);
120 12         35 $self->{Def} = $tree;
121 12         36 $self->{Directives} = $directives;
122             }
123              
124             sub _eval_obj {
125 0     0   0 my($self, $s) = @_;
126 0         0 eval 'package Permissions::_eval_; ' . $s;
127 0 0       0 die "Can't eval $s: $@" if $@;
128 0 0       0 die "No Def object in file" unless defined $Permissions::_eval_::Def;
129 0         0 $self->{Def} = $Permissions::_eval_::Def;
130 0         0 undef $Permissions::_eval_::Def;
131             }
132              
133             =item save($file)
134              
135             Save the Permission object to file C<$file>. The file may be reread
136             using the C<-objectfromfile> argument in C.
137              
138             =cut
139              
140             sub save {
141 1     1 1 781 my($self, $file) = @_;
142 1 50       176 open(F, ">$file") or die "Can't write to $file: $!";
143 1         1708 require Data::Dumper;
144 1         8205 print F Data::Dumper->Dump([$self->{Def}], ['Def']);
145 1         775 close F;
146             }
147              
148             =item is_allowed(%args)
149              
150             Return true, if the process for the specified user/group and specified
151             page is allowed. The keys of C<%args> may be: C<-user>, C<-group> (an
152             array reference to a group list), C<-process>, and C<-page>.
153              
154             =cut
155              
156             sub is_allowed {
157 124     124 1 11312 my($self, %args) = @_;
158 124         145 my %new_args;
159 124         388 while(my($k,$v) = each %args) {
160 420         1713 $new_args{substr($k,1)} = $v; # strip dash
161             }
162 124         414 $self->_is_allowed($self->{Def}, \%new_args);
163             }
164              
165             =item get_all_users($usersref, $process, $page)
166              
167             Return a list of all users which are allowed to do C<$process> in
168             C<$page>. The C<$usersref> should contain all users in the system and
169             may be a reference to an array or a reference to an hash. In the
170             latter case, the keys are the user names and the values an array
171             reference to the groups of the user. For example:
172              
173             $p->get_all_users([qw(eserte ole veit)], "publish", "home");
174              
175             $p->get_all_users({eserte => ['admin','editor'],
176             ole => ['admin'],
177             veit => ['editor']}, "publish", "home");
178              
179             =cut
180              
181             sub get_all_users {
182 9     9 1 21 my($self, $usersref, $process, $page) = @_;
183              
184 9         12 my @res;
185             my @all_users;
186 0         0 my %groups;
187 9 50       27 if (ref $usersref eq 'HASH') {
188 9         39 @all_users = keys %$usersref;
189 9         50 %groups = %$usersref;
190             } else {
191 0         0 @all_users = @$usersref;
192             }
193              
194 9         16 my @args;
195 9 50       26 if (defined $process) { push @args, -process => $process }
  9         22  
196 9 100       21 if (defined $page) { push @args, -page => $page }
  6         13  
197 9         14 foreach my $user (@all_users) {
198 63 50       224 my @group_arg = (exists $groups{$user}
199             ? (-group => $groups{$user})
200             : ()
201             );
202 63 100       174 push @res, $user if $self->is_allowed(-user => $user,
203             @group_arg,
204             @args);
205             }
206 9         100 @res;
207             }
208              
209             =item get_all_page_permissions($usersref, $processref, $page)
210              
211             Return permissions for all users for the specified C<$page>. Arguments
212             are similar to that of C, except that C<$processref>
213             takes an array reference with all allowed processes. The returned
214             object is a hash reference with the following format:
215              
216             { process1 => [user1, user2, ...],
217             process2 => [user3, user4, ...],
218             ...
219             }
220              
221             =cut
222              
223             sub get_all_page_permissions {
224 1     1 1 4 my($self, $usersref, $processref, $page, %args) = @_;
225 1         2 my $info = {};
226 1         3 foreach my $process (@$processref) {
227 4         11 $info->{$process} = [$self->get_all_users($usersref, $process, $page)];
228             }
229 1         7 $info;
230             }
231              
232             sub _is_allowed {
233 264     264   400 my($self, $tree, $args_ref) = @_;
234 264         656 foreach my $subtree ($tree->subtree) {
235 532 100       1452 if ($self->_match($subtree->data, $args_ref)) {
236 199 100       227 if (@{$subtree->subtree}) {
  199         568  
237 140         297 my $r = $self->_is_allowed($subtree, $args_ref);
238 140 100       781 return 1 if $r;
239             } else {
240 59         130 return 1;
241             }
242             }
243             }
244 128         843 0;
245             }
246              
247             sub _match {
248 532     532   852 my($self, $perm, $args_ref) = @_;
249 532 100 66     3418 my $matchtype = ($self->{Directives} && $self->{Directives}{match}
250             ? $self->{Directives}{match} : 'glob');
251 532         1796 my(@big_or) = split /\s*;\s*/, $perm;
252 532         924 foreach my $term (@big_or) {
253 605         2844 my(@args) = split /[\s,]+/, $term;
254 605         996 my $permtype = shift @args;
255 605         942 my $args_permtype = $args_ref->{$permtype};
256 605         1153 warn "term @args against " .
257             (defined $args_permtype
258             ? (ref $args_permtype eq 'ARRAY'
259             ? "@$args_permtype"
260             : $args_permtype
261             )
262             : ""
263             ) . " ...\n" if DEBUG;
264 605 100       1349 if ($args_permtype) {
265 132         274 my @terms = (ref $args_permtype eq 'ARRAY'
266 548 100       1278 ? @{ $args_permtype }
267             : $args_permtype
268             );
269 548         730 foreach my $arg_ (@args) {
270 686         884 my $arg = $arg_;
271 686         725 my $no = 0;
272 686 100       1616 if ($arg =~ /^!(.*)/) {
273 41         91 $arg = $1;
274 41         57 $no = 1;
275             }
276 686         627 my $check_sub;
277 686 100       1630 if ($matchtype eq 'glob') {
    100          
278 611         2619 my $repl = { '*' => '.*',
279             '?' => '.',
280             };
281 611 100       1733 if ($arg =~ /[\*\?]/) {
282 57         304 $arg =~ s/(.*?)([\*\?])([^\*\?]*)/"\Q$1\E" . $repl->{$2} . "\Q$3\E"/ge;
  58         304  
283 57         666 $arg = qr/^$arg$/;
284 57         73 warn "Glob -> regexp: $arg\n" if DEBUG >= 2;
285 57     59   245 $check_sub = sub { /$arg/ };
  59         732  
286             } else {
287 554     548   2255 $check_sub = sub { $_ eq $arg };
  548         3634  
288             }
289             } elsif ($matchtype =~ /^(rx|regexp?)$/) {
290 74     74   240 $check_sub = sub { /^$arg$/ };
  74         1626  
291             } else {
292 1         14 die "Invalid match type: $matchtype";
293             }
294              
295 685 100       1230 if ($no) {
296 41 100       59 return 0 if grep { $check_sub->() } @terms;
  44         76  
297             } else {
298 644 100       995 return 1 if grep { $check_sub->() } @terms;
  637         1019  
299             }
300             }
301             }
302             }
303 310         1207 0;
304             }
305              
306             =item get_directive($directive)
307              
308             Return the value of the global directive C<$directive>, or undef.
309              
310             =cut
311              
312             sub get_directive {
313 1     1 1 9 my($self, $directive) = @_;
314 1 50       8 if (exists $self->{Directives}{$directive}) {
315 1         8 $self->{Directives}{$directive};
316             } else {
317 0           undef;
318             }
319             }
320              
321             1;
322              
323             __END__