#!/usr/bin/perl -s use strict; use warnings; use lib $ENV{GL_LIBDIR}; use Gitolite::Rc; use Gitolite::Common; use Gitolite::Conf::Load; our ( $q, $s, $h ); # quiet, show, help =for usage Usage: gitolite access [-q|-s] Print access rights for arguments given. The string printed has the word DENIED in it if access was denied. With '-q', returns only an exit code (shell truth, not perl truth -- 0 is success). For '-s', see below. - repo: mandatory - user: mandatory - perm: defauts to '+'. Valid values: R, W, +, C, D, M - ref: defauts to 'any'. See notes below Notes: - ref: something like 'master', or 'refs/tags/v1.0', or even a VREF if you know what they look like. The 'any' ref is special -- it ignores deny rules, thus simulating gitolite's behaviour during the pre-git access check (see 'deny-rules' section in rules.html for details). - batch mode: see src/triggers/post-compile/update-git-daemon-access-list for a good example that shows how to test several repos in one invocation. This is orders of magnitude faster than running the command multiple times; you'll notice if you have more than a hundred or so repos. - '-s' shows the rules (conf file name, line number, and rule) that were considered and how they fared. - you can also test the ability to create wild repos if you set GL_USER to the username and use ^C as the permission to check for. =cut usage() if not @ARGV >= 2 or $h; my ( $repo, $user, $aa, $ref ) = @ARGV; # default access is '+' $aa ||= '+'; # default ref is 'any' $ref ||= 'any'; # fq the ref if needed $ref =~ s(^)(refs/heads/) if $ref and $ref ne 'any' and $ref !~ m(^(refs|VREF)/); _die "invalid perm" if not( $aa and $aa =~ /^(R|W|\+|C|D|M|\^C)$/ ); _die "invalid ref name" if not( $ref and $ref =~ $REF_OR_FILENAME_PATT ); my $ret = ''; if ( $repo ne '%' and $user ne '%' ) { # single repo, single user; no STDIN $ret = access( $repo, $user, adjust_aa($repo, $aa), $ref ); show($ret) if $s; # adjust for fallthru in VREFs $ret =~ s/DENIED by fallthru/allowed by fallthru/ if $ref =~ m(^VREF/); if ( $ret =~ /DENIED/ ) { print "$ret\n" unless $q; exit 1; } print "$ret\n" unless $q; exit 0; } $repo = '' if $repo eq '%'; $user = '' if $user eq '%'; _die "'-q' and '-s' meaningless in pipe mode" if $q or $s; @ARGV = (); while (<>) { my @in = split; my $r = $repo || shift @in; my $u = $user || shift @in; $ret = access( $r, $u, adjust_aa($r, $aa), $ref ); print "$r\t$u\t$ret\n"; } sub adjust_aa { my ($repo, $aa) = @_; $aa = 'W' if $aa eq 'C' and not option($repo, 'CREATE_IS_C'); $aa = '+' if $aa eq 'D' and not option($repo, 'DELETE_IS_D'); $aa = 'W' if $aa eq 'M' and not option($repo, 'MERGE_CHECK'); return $aa; } sub show { my $ret = shift; die "repo already exists; ^C won't work\n" if $ret =~ /DENIED by existence/; my $in = $rc{RULE_TRACE} or die "this should not happen! $ret"; print STDERR "legend:"; print STDERR " d => skipped deny rule due to ref unknown or 'any', r => skipped due to refex not matching, p => skipped due to perm (W, +, etc) not matching, D => explicitly denied, A => explicitly allowed, F => fallthru; access denied for normal refs, allowed for VREFs "; my %rule_info = read_ri($in); # get rule info data for all traced rules # this means conf filename, line number, and content of the line # the rule-trace info is a set of pairs of a number plus a string. Only # the last character in a string is valid (and has meanings shown above). # At the end there may be a final 'f' my @in = split ' ', $in; while (@in) { $in = shift @in; if ( $in =~ /^\d+$/ ) { my $res = shift @in or die "this should not happen either!"; my $m = chop($res); printf " %s %20s:%-6s %s\n", $m, $rule_info{$in}{fn}, $rule_info{$in}{ln}, $rule_info{$in}{cl}; } elsif ( $in eq 'F' ) { printf " %s %20s\n", $in, "(fallthru)"; } else { die "and finally, this also should not happen!"; } } print "\n"; } sub read_ri { my %rules = map { $_ => 1 } $_[0] =~ /(\d+)/g; # contains a series of rule numbers, each of which we must search in # $GL_ADMIN_BASE/.gitolite/conf/rule_info my %rule_info; for ( slurp( $ENV{GL_ADMIN_BASE} . "/conf/rule_info" ) ) { my ( $r, $f, $l ) = split ' ', $_; next unless $rules{$r}; $rule_info{$r}{fn} = $f; $rule_info{$r}{ln} = $l; $rule_info{$r}{cl} = conf_lines( $f, $l ); # a wee bit of optimisation, in case the rule_info file is huge and # what we want is up near the beginning delete $rules{$r}; last unless %rules; } return %rule_info; } { my %conf_lines; sub conf_lines { my ( $file, $line ) = @_; $line--; unless ( $conf_lines{$file} ) { $conf_lines{$file} = [ slurp( $ENV{GL_ADMIN_BASE} . "/conf/$file" ) ]; chomp( @{ $conf_lines{$file} } ); } return $conf_lines{$file}[$line]; } }