diff options
author | Sergey Poznyakoff <gray@gnu.org.ua> | 2014-07-31 00:16:34 +0300 |
---|---|---|
committer | Sergey Poznyakoff <gray@gnu.org.ua> | 2014-07-31 00:16:34 +0300 |
commit | 6c604cdf5bb45a6a3d58f1f2eb985622ed39248a (patch) | |
tree | 54fd6a88949ba4b69bf866c41434e6291f01747f | |
parent | 99b83be1159294d7e05a9630b9b4babab52836e1 (diff) | |
download | gitaclhook-6c604cdf5bb45a6a3d58f1f2eb985622ed39248a.tar.gz gitaclhook-6c604cdf5bb45a6a3d58f1f2eb985622ed39248a.tar.bz2 |
Optionally test filenames of the files changed by the commit.
* gitacl.schema (gitAclTree): New attribute.
* gitaclhook: Document changes.
Accept four or more arguments in test mode.
* lib/GitACL.pm (git_values): New function.
(match_tree): New function.
(match_tuple): Match path pattern, if supplied.
(new): Fix initialization of the debug member.
Get remote IP address from the socket, if no envar
is defined. Default to 127.0.0.1.
* lib/GitACL/File.pm (check_acl): Allow up to 6 fields in
an ACL line.
* lib/GitACL/LDAP.pm: Get path pattern from the gitAclTree
attribute.
-rw-r--r-- | gitacl.schema | 9 | ||||
-rwxr-xr-x | gitaclhook | 31 | ||||
-rw-r--r-- | lib/GitACL.pm | 61 | ||||
-rw-r--r-- | lib/GitACL/File.pm | 2 | ||||
-rw-r--r-- | lib/GitACL/LDAP.pm | 2 |
5 files changed, 93 insertions, 12 deletions
diff --git a/gitacl.schema b/gitacl.schema index d8083e5..a9098d4 100644 --- a/gitacl.schema +++ b/gitacl.schema | |||
@@ -41,9 +41,16 @@ attributetype ( 1.3.6.1.4.1.9163.2.3.1.5 NAME 'gitAclUser' | |||
41 | SUBSTR caseExactSubstringsMatch | 41 | SUBSTR caseExactSubstringsMatch |
42 | SYNTAX 1.3.6.1.4.1.1466.115.121.1.15{256} ) | 42 | SYNTAX 1.3.6.1.4.1.1466.115.121.1.15{256} ) |
43 | 43 | ||
44 | attributetype ( 1.3.6.1.4.1.9163.2.3.1.6 NAME 'gitAclTree' | ||
45 | DESC 'Git subtree' | ||
46 | EQUALITY caseExactMatch | ||
47 | SUBSTR caseExactSubstringsMatch | ||
48 | SYNTAX 1.3.6.1.4.1.1466.115.121.1.15{256} ) | ||
49 | |||
44 | objectclass ( 1.3.6.1.4.1.9163.2.3.2.0 NAME 'gitACL' | 50 | objectclass ( 1.3.6.1.4.1.9163.2.3.2.0 NAME 'gitACL' |
45 | DESC 'Git Access Control List Entry' | 51 | DESC 'Git Access Control List Entry' |
46 | SUP top STRUCTURAL | 52 | SUP top STRUCTURAL |
47 | MUST ( cn $ gitAclProject $ gitAclVerb ) | 53 | MUST ( cn $ gitAclProject $ gitAclVerb ) |
48 | MAY ( gitAclUser $ gitAclOp $ gitAclRef $ gitAclOrder $ description ) ) | 54 | MAY ( gitAclUser $ gitAclOp $ gitAclRef $ gitAclOrder $ |
55 | gitAclTree $ description ) ) | ||
49 | 56 | ||
@@ -19,7 +19,7 @@ use GitACL; | |||
19 | use Pod::Man; | 19 | use Pod::Man; |
20 | use Pod::Usage; | 20 | use Pod::Usage; |
21 | use Getopt::Long qw(:config gnu_getopt no_ignore_case); | 21 | use Getopt::Long qw(:config gnu_getopt no_ignore_case); |
22 | 22 | ||
23 | =head1 NAME | 23 | =head1 NAME |
24 | 24 | ||
25 | gitaclhook - control access to git repositories | 25 | gitaclhook - control access to git repositories |
@@ -28,7 +28,7 @@ gitaclhook - control access to git repositories | |||
28 | 28 | ||
29 | B<gitaclhook> I<refname> I<old-sha1> I<new-sha1> | 29 | B<gitaclhook> I<refname> I<old-sha1> I<new-sha1> |
30 | 30 | ||
31 | B<gitacthook> [B<--debug>] B<--test> I<REPO> I<USER> I<OP> I<REF> | 31 | B<gitacthook> [B<--debug>] B<--test> I<REPO> I<USER> I<OP> I<REF> [I<FILE>...] |
32 | 32 | ||
33 | B<gitaclhook --help> | 33 | B<gitaclhook --help> |
34 | 34 | ||
@@ -65,7 +65,7 @@ Non-empty lines introduce ACL rules. The syntax is: | |||
65 | 65 | ||
66 | =over 4 | 66 | =over 4 |
67 | 67 | ||
68 | I<VERB> I<PROJECT> I<USER> [I<OP> I<REF>] | 68 | I<VERB> I<PROJECT> I<USER> [I<OP> I<REF> [I<PATH>]] |
69 | 69 | ||
70 | =back | 70 | =back |
71 | 71 | ||
@@ -142,6 +142,12 @@ being its part). If it ends with a B</>, it is treated as a prefix match, | |||
142 | so, e.g., B<heads/baz/> matches B<refs/heads/baz> and anything below. | 142 | so, e.g., B<heads/baz/> matches B<refs/heads/baz> and anything below. |
143 | Otherwise, it must match exactly the affected ref. | 143 | Otherwise, it must match exactly the affected ref. |
144 | 144 | ||
145 | =item I<PATH> | ||
146 | |||
147 | Pathname pattern. If present, the names of all files affected by the commit | ||
148 | must match it in order for the rule to apply. Matching algorithm is the same | ||
149 | as for I<REF>. | ||
150 | |||
145 | =back | 151 | =back |
146 | 152 | ||
147 | =head1 RULE MATCHING | 153 | =head1 RULE MATCHING |
@@ -149,7 +155,11 @@ Otherwise, it must match exactly the affected ref. | |||
149 | The rule applies only if its I<PROJECT> and I<USER> parts match the project | 155 | The rule applies only if its I<PROJECT> and I<USER> parts match the project |
150 | which is being updated and the user who requests the update, its I<OP> | 156 | which is being updated and the user who requests the update, its I<OP> |
151 | contains the opcode of the requested operation and I<REF> matches the affected | 157 | contains the opcode of the requested operation and I<REF> matches the affected |
152 | ref. Missing I<REF> and/or I<OP> are treated as a match. | 158 | ref. If I<PATH> is present each file changed by the commit is compared with |
159 | it and removed from the list if it matches. The rule applies only if the | ||
160 | list of files becomes empty. | ||
161 | |||
162 | Missing I<REF>, I<OP> and I<PATH> are treated as a match. | ||
153 | 163 | ||
154 | If no rule applies, the operation is denied. This can be changed by setting | 164 | If no rule applies, the operation is denied. This can be changed by setting |
155 | B<hooks.acl.default = allow> in Git configuration file. | 165 | B<hooks.acl.default = allow> in Git configuration file. |
@@ -203,6 +213,10 @@ The list of operation codes. | |||
203 | =item B<gitAclRef> [optional] | 213 | =item B<gitAclRef> [optional] |
204 | 214 | ||
205 | Git ref. | 215 | Git ref. |
216 | |||
217 | =item B<gitAclPath> [optional] | ||
218 | |||
219 | Pathname pattern. | ||
206 | 220 | ||
207 | =item B<gitAclOrder> [optional] | 221 | =item B<gitAclOrder> [optional] |
208 | 222 | ||
@@ -287,11 +301,13 @@ lists from the command line. The syntax is: | |||
287 | =over 4 | 301 | =over 4 |
288 | 302 | ||
289 | B<gitacthook> [B<--debug>] [B<-d>] B<--test> I<REPO> I<USER> I<OP> I<REF> | 303 | B<gitacthook> [B<--debug>] [B<-d>] B<--test> I<REPO> I<USER> I<OP> I<REF> |
290 | 304 | [I<FILE>...] | |
305 | |||
291 | =back | 306 | =back |
292 | 307 | ||
293 | I<REPO> is a pathname of the repository to test, I<USER> is the username, | 308 | I<REPO> is a pathname of the repository to test, I<USER> is the username, |
294 | I<OP> is the operation code and I<REF> is the reference. | 309 | I<OP> is the operation code and I<REF> is the reference. Optional I<FILE> |
310 | arguments supply names of the files changed by the commit. | ||
295 | 311 | ||
296 | Optional B<--debug> (B<-d>) options increment the debugging level. | 312 | Optional B<--debug> (B<-d>) options increment the debugging level. |
297 | 313 | ||
@@ -358,11 +374,12 @@ unless ($ENV{GIT_DIR}) { | |||
358 | "test|t" => \$test) | 374 | "test|t" => \$test) |
359 | or exit (3); | 375 | or exit (3); |
360 | if ($test) { | 376 | if ($test) { |
361 | abend("--test requires four arguments") unless ($#ARGV == 3); | 377 | abend("--test requires four or more arguments") unless ($#ARGV >= 3); |
362 | $args{git_dir} = $ENV{GIT_DIR} = $ARGV[0]; | 378 | $args{git_dir} = $ENV{GIT_DIR} = $ARGV[0]; |
363 | $args{user} = $ARGV[1]; | 379 | $args{user} = $ARGV[1]; |
364 | $args{op} = $ARGV[2]; | 380 | $args{op} = $ARGV[2]; |
365 | $args{ref} = $ARGV[3]; | 381 | $args{ref} = $ARGV[3]; |
382 | $args{files} = [@ARGV[4..$#ARGV]] if ($#ARGV > 3); | ||
366 | $args{old} = '0000000000000000000000000000000000000000'; | 383 | $args{old} = '0000000000000000000000000000000000000000'; |
367 | $args{new} = '0000000000000000000000000000000000000001'; | 384 | $args{new} = '0000000000000000000000000000000000000001'; |
368 | $args{debug} = $debug; | 385 | $args{debug} = $debug; |
diff --git a/lib/GitACL.pm b/lib/GitACL.pm index d3f6b35..f7743d5 100644 --- a/lib/GitACL.pm +++ b/lib/GitACL.pm | |||
@@ -17,6 +17,7 @@ | |||
17 | package GitACL; | 17 | package GitACL; |
18 | 18 | ||
19 | use strict; | 19 | use strict; |
20 | use feature "state"; | ||
20 | use File::Spec; | 21 | use File::Spec; |
21 | use Net::CIDR qw (cidrlookup); | 22 | use Net::CIDR qw (cidrlookup); |
22 | 23 | ||
@@ -99,6 +100,16 @@ sub get_project_name($) { | |||
99 | return $1; | 100 | return $1; |
100 | } | 101 | } |
101 | 102 | ||
103 | sub git_values(@) { | ||
104 | my $fd; | ||
105 | |||
106 | open($fd,'-|','git',@_); | ||
107 | local $_; | ||
108 | my @ret = map { chomp; $_; } <$fd>; | ||
109 | close($fd); | ||
110 | return @ret; | ||
111 | } | ||
112 | |||
102 | sub git_value(@) { | 113 | sub git_value(@) { |
103 | my $fd; | 114 | my $fd; |
104 | 115 | ||
@@ -153,11 +164,43 @@ sub match_ref($$) { | |||
153 | return $self->{ref} eq $expr; | 164 | return $self->{ref} eq $expr; |
154 | } | 165 | } |
155 | 166 | ||
167 | sub match_tree($$) { | ||
168 | my ($self, $expr) = @_; | ||
169 | state @tree; | ||
170 | state $init; | ||
171 | |||
172 | unless (defined($init)) { | ||
173 | if (defined($self->{files})) { | ||
174 | @tree = @{$self->{files}}; | ||
175 | } else { | ||
176 | @tree = git_values("diff-tree", | ||
177 | "--no-commit-id", "--name-only", "-r", | ||
178 | $self->{new}); | ||
179 | } | ||
180 | $init = 1; | ||
181 | } | ||
182 | |||
183 | for (my $i = 0; $i <= $#tree; ) { | ||
184 | my $dir = $tree[$i]; | ||
185 | |||
186 | if (($expr =~ /^\^/ and $dir =~ /$expr/) | ||
187 | or ($expr =~ /\/$/ | ||
188 | and ("$dir/" eq $expr or index($dir, $expr) == 0)) | ||
189 | or $dir eq $expr) { | ||
190 | splice(@tree, $i, 1); | ||
191 | } else { | ||
192 | ++$i; | ||
193 | } | ||
194 | } | ||
195 | |||
196 | return $#tree == -1; | ||
197 | } | ||
198 | |||
156 | sub match_tuple($$) { | 199 | sub match_tuple($$) { |
157 | my ($self, $tuple) = @_; | 200 | my ($self, $tuple) = @_; |
158 | my @x = @{$tuple}; | 201 | my @x = @{$tuple}; |
159 | 202 | ||
160 | return ( \&deny, "malformed line" ) unless $#x >= 2; | 203 | return ( \&deny, "malformed line: " . join(' ', @x) ) unless $#x >= 2; |
161 | return ( \&deny, "unknown keyword" ) | 204 | return ( \&deny, "unknown keyword" ) |
162 | unless ($x[0] eq 'allow' || $x[0] eq 'deny'); | 205 | unless ($x[0] eq 'allow' || $x[0] eq 'deny'); |
163 | 206 | ||
@@ -168,7 +211,9 @@ sub match_tuple($$) { | |||
168 | return ( 0, "op mismatch" ) | 211 | return ( 0, "op mismatch" ) |
169 | if ($#x >= 3 && index(uc $x[3], $self->{op}) == -1); | 212 | if ($#x >= 3 && index(uc $x[3], $self->{op}) == -1); |
170 | return ( 0, "ref mismatch" ) | 213 | return ( 0, "ref mismatch" ) |
171 | if ($#x == 4 && !$self->match_ref($x[4])); | 214 | if ($#x >= 4 && !$self->match_ref($x[4])); |
215 | return ( 0, "tree mismatch" ) | ||
216 | if ($#x == 5 && !$self->match_tree($x[5])); | ||
172 | if ($x[0] eq 'allow') { | 217 | if ($x[0] eq 'allow') { |