aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSergey Poznyakoff <gray@gnu.org.ua>2014-07-31 00:16:34 +0300
committerSergey Poznyakoff <gray@gnu.org.ua>2014-07-31 00:16:34 +0300
commit6c604cdf5bb45a6a3d58f1f2eb985622ed39248a (patch)
tree54fd6a88949ba4b69bf866c41434e6291f01747f
parent99b83be1159294d7e05a9630b9b4babab52836e1 (diff)
downloadgitaclhook-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.schema9
-rwxr-xr-xgitaclhook31
-rw-r--r--lib/GitACL.pm61
-rw-r--r--lib/GitACL/File.pm2
-rw-r--r--lib/GitACL/LDAP.pm2
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
44attributetype ( 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
44objectclass ( 1.3.6.1.4.1.9163.2.3.2.0 NAME 'gitACL' 50objectclass ( 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
diff --git a/gitaclhook b/gitaclhook
index 0addbf5..756ebe5 100755
--- a/gitaclhook
+++ b/gitaclhook
@@ -19,7 +19,7 @@ use GitACL;
19use Pod::Man; 19use Pod::Man;
20use Pod::Usage; 20use Pod::Usage;
21use Getopt::Long qw(:config gnu_getopt no_ignore_case); 21use Getopt::Long qw(:config gnu_getopt no_ignore_case);
22 22
23=head1 NAME 23=head1 NAME
24 24
25gitaclhook - control access to git repositories 25gitaclhook - control access to git repositories
@@ -28,7 +28,7 @@ gitaclhook - control access to git repositories
28 28
29B<gitaclhook> I<refname> I<old-sha1> I<new-sha1> 29B<gitaclhook> I<refname> I<old-sha1> I<new-sha1>
30 30
31B<gitacthook> [B<--debug>] B<--test> I<REPO> I<USER> I<OP> I<REF> 31B<gitacthook> [B<--debug>] B<--test> I<REPO> I<USER> I<OP> I<REF> [I<FILE>...]
32 32
33B<gitaclhook --help> 33B<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
68I<VERB> I<PROJECT> I<USER> [I<OP> I<REF>] 68I<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,
142so, e.g., B<heads/baz/> matches B<refs/heads/baz> and anything below. 142so, e.g., B<heads/baz/> matches B<refs/heads/baz> and anything below.
143Otherwise, it must match exactly the affected ref. 143Otherwise, it must match exactly the affected ref.
144 144
145=item I<PATH>
146
147Pathname pattern. If present, the names of all files affected by the commit
148must match it in order for the rule to apply. Matching algorithm is the same
149as 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.
149The rule applies only if its I<PROJECT> and I<USER> parts match the project 155The rule applies only if its I<PROJECT> and I<USER> parts match the project
150which is being updated and the user who requests the update, its I<OP> 156which is being updated and the user who requests the update, its I<OP>
151contains the opcode of the requested operation and I<REF> matches the affected 157contains the opcode of the requested operation and I<REF> matches the affected
152ref. Missing I<REF> and/or I<OP> are treated as a match. 158ref. If I<PATH> is present each file changed by the commit is compared with
159it and removed from the list if it matches. The rule applies only if the
160list of files becomes empty.
161
162Missing I<REF>, I<OP> and I<PATH> are treated as a match.
153 163
154If no rule applies, the operation is denied. This can be changed by setting 164If no rule applies, the operation is denied. This can be changed by setting
155B<hooks.acl.default = allow> in Git configuration file. 165B<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
205Git ref. 215Git ref.
216
217=item B<gitAclPath> [optional]
218
219Pathname 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
289B<gitacthook> [B<--debug>] [B<-d>] B<--test> I<REPO> I<USER> I<OP> I<REF> 303B<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
293I<REPO> is a pathname of the repository to test, I<USER> is the username, 308I<REPO> is a pathname of the repository to test, I<USER> is the username,
294I<OP> is the operation code and I<REF> is the reference. 309I<OP> is the operation code and I<REF> is the reference. Optional I<FILE>
310arguments supply names of the files changed by the commit.
295 311
296Optional B<--debug> (B<-d>) options increment the debugging level. 312Optional 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 @@
17package GitACL; 17package GitACL;
18 18
19use strict; 19use strict;
20use feature "state";
20use File::Spec; 21use File::Spec;
21use Net::CIDR qw (cidrlookup); 22use Net::CIDR qw (cidrlookup);
22 23
@@ -99,6 +100,16 @@ sub get_project_name($) {
99 return $1; 100 return $1;
100} 101}
101 102
103sub 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
102sub git_value(@) { 113sub 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
167sub 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
156sub match_tuple($$) { 199sub 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') {