118
120
print STDERR "-Info- $_[0]\n" if $debug;
121
sub parse_config ($$) {
122
my ($data, $fn) = @_;
124
open(I,'-|','git',"--git-dir=$acl_git",'cat-file','blob',$fn);
124
open(T,'-|','git',@_); local $_ = <T>; chop; close T; $_;
127
sub match_string ($$) {
128
my ($acl_n, $ref) = @_;
130
|| ($acl_n =~ m,/$, && substr($ref,0,length $acl_n) eq $acl_n)
131
|| ($acl_n =~ m,^\^, && $ref =~ m:$acl_n:);
134
sub parse_config ($$$$) {
136
local $ENV{GIT_DIR} = shift;
139
info "Loading $br:$fn";
140
open(I,'-|','git','cat-file','blob',"$br:$fn");
125
141
my $section = '';
128
144
if (/^\s*$/ || /^\s*#/) {
129
145
} elsif (/^\[([a-z]+)\]$/i) {
131
147
} elsif (/^\[([a-z]+)\s+"(.*)"\]$/i) {
148
$section = join('.',lc $1,$2);
133
149
} elsif (/^\s*([a-z][a-z0-9]+)\s*=\s*(.*?)\s*$/i) {
134
push @{$data->{"$section.$1"}}, $2;
150
push @{$data->{join('.',$section,lc $1)}}, $2;
136
deny "bad config file line $. in $fn";
152
deny "bad config file line $. in $br:$fn";
206
open(T,'-|','git',@_); local $_ = <T>; chop; close T;
223
my $d = $diff_cache{$base};
227
if ($base =~ /^0{40}$/) {
228
open(T,'-|','git','ls-tree',
229
'-r','--name-only','-z',
230
$new) or return undef;
233
$this_diff{$_} = 'A';
235
close T or return undef;
237
open(T,'-|','git','diff-tree',
238
'-r','--name-status','-z',
239
$base,$new) or return undef;
247
$this_diff{$path} = $op;
249
close T or return undef;
252
$diff_cache{$base} = $d;
210
257
deny "No GIT_DIR inherited from caller" unless $git_dir;
231
278
&& $ref =~ m,^heads/,
232
279
&& $old eq git_value('merge-base',$old,$new));
234
# Load the user's ACL file.
281
# Load the user's ACL file. Expand groups (user.memberof) one level.
236
283
my %data = ('user.committer' => []);
237
parse_config(\%data, "$acl_branch:users/$this_user.acl");
284
parse_config(\%data,$acl_git,$acl_branch,"external/$repository_name.acl");
287
'user.committer' => $data{'user.committer'},
288
'user.memberof' => [],
290
parse_config(\%data,$acl_git,$acl_branch,"users/$this_user.acl");
238
292
%user_committer = map {$_ => $_} @{$data{'user.committer'}};
239
my $rules = $data{"repository.$repository_name.allow"} || [];
293
my $rule_key = "repository.$repository_name.allow";
294
my $rules = $data{$rule_key} || [];
296
foreach my $group (@{$data{'user.memberof'}}) {
298
parse_config(\%g,$acl_git,$acl_branch,"groups/$group.acl");
299
my $group_rules = $g{$rule_key};
300
push @$rules, @$group_rules if $group_rules;
240
304
foreach (@$rules) {
241
if (/^([CDRU ]+)\s+for\s+([^\s]+)$/) {
305
while (/\${user\.([a-z][a-zA-Z0-9]+)}/) {
307
my $v = $data{"user.$k"};
308
next RULE unless defined $v;
309
next RULE if @$v != 1;
310
next RULE unless defined $v->[0];
311
s/\${user\.$k}/$v->[0]/g;
314
if (/^([AMD ]+)\s+of\s+([^\s]+)\s+for\s+([^\s]+)\s+diff\s+([^\s]+)$/) {
315
my ($ops, $pth, $ref, $bst) = ($1, $2, $3, $4);
319
push @path_rules, [$ops, $pth, $ref, $bst];
320
} elsif (/^([AMD ]+)\s+of\s+([^\s]+)\s+for\s+([^\s]+)$/) {
321
my ($ops, $pth, $ref) = ($1, $2, $3);
325
push @path_rules, [$ops, $pth, $ref, $old];
326
} elsif (/^([CDRU ]+)\s+for\s+([^\s]+)$/) {
272
357
next unless $acl_ops =~ /^[CDRU]+$/; # Uhh.... shouldn't happen.
273
358
next unless $acl_n;
274
359
next unless $op =~ /^[$acl_ops]$/;
276
grant "Allowed by: $acl_ops for $acl_n"
279
|| ($acl_n =~ m,/$, && substr($ref,0,length $acl_n) eq $acl_n)
280
|| ($acl_n =~ m,^\^, && $ref =~ m:$acl_n:)
360
next unless match_string $acl_n, $ref;
362
# Don't test path rules on branch deletes.
364
grant "Allowed by: $acl_ops for $acl_n" if $op eq 'D';
366
# Aggregate matching path rules; allow if there aren't
367
# any matching this ref.
370
foreach my $p_entry (@path_rules) {
371
my ($p_ops, $p_n, $p_ref, $p_bst) = @$p_entry;
373
push @{$pr{$p_bst}}, $p_entry if match_string $p_ref, $ref;
375
grant "Allowed by: $acl_ops for $acl_n" unless %pr;
377
# Allow only if all changes against a single base are
378
# allowed by file path rules.
381
foreach my $p_bst (keys %pr) {
382
my $diff_ref = load_diff $p_bst;
383
deny "Cannot difference trees." unless ref $diff_ref;
386
foreach my $p_entry (@{$pr{$p_bst}}) {
387
my ($p_ops, $p_n, $p_ref, $p_bst) = @$p_entry;
388
next unless $p_ops =~ /^[AMD]+$/;
391
foreach my $f_n (keys %fd) {
392
my $f_op = $fd{$f_n};
394
next unless $f_op =~ /^[$p_ops]$/;
395
delete $fd{$f_n} if match_string $p_n, $f_n;
401
push @bad, [$p_bst, \%fd];
403
# All changes relative to $p_bst were allowed.
405
grant "Allowed by: $acl_ops for $acl_n diff $p_bst";
409
foreach my $bad_ref (@bad) {
410
my ($p_bst, $fd) = @$bad_ref;
412
print STDERR "Not allowed to make the following changes:\n";
413
print STDERR "(base: $p_bst)\n";
414
foreach my $f_n (sort keys %$fd) {
415
print STDERR " $fd->{$f_n} $f_n\n";
418
deny "You are not permitted to $op $ref";
284
421
deny "You are not permitted to $op $ref";