diff --git a/src/gitolite.pm b/src/gitolite.pm index 5864466..f341ab1 100644 --- a/src/gitolite.pm +++ b/src/gitolite.pm @@ -81,7 +81,7 @@ sub where_is_rc # NOTE: this sub will change your cwd; caller beware! sub new_repo { - my ($repo, $hooks_dir) = @_; + my ($repo, $hooks_dir, $creater) = @_; umask($REPO_UMASK); @@ -89,19 +89,81 @@ sub new_repo # erm, note that's "and die" not "or die" as is normal in perl wrap_chdir("$repo.git"); system("git --bare init >&2"); + system("echo $creater > gl-creater") if $creater; # propagate our own, plus any local admin-defined, hooks system("cp $hooks_dir/* hooks/"); chmod 0755, "hooks/update"; } +# ---------------------------------------------------------------------------- +# metaphysics (like, "is there a god?", "who created me?", etc) +# ---------------------------------------------------------------------------- + +# "who created this repo", "am I on the R list", and "am I on the RW list"? +sub repo_rights +{ + my ($repo_base_abs, $repo, $user) = @_; + # creater + my $c = ''; + if ( -f "$repo_base_abs/$repo.git/gl-creater") { + my $fh = wrap_open("<", "$repo_base_abs/$repo.git/gl-creater"); + chomp($c = <$fh>); + } + # $user's R and W rights + my ($r, $w); $r = ''; $w = ''; + if ($user and -f "$repo_base_abs/$repo.git/gl-perms") { + my $fh = wrap_open("<", "$repo_base_abs/$repo.git/gl-perms"); + my $perms = join ("", <$fh>); + if ($perms) { + $r = $user if $perms =~ /^\s*R(?=\s).*\s$user(\s|$)/m; + $w = $user if $perms =~ /^\s*RW(?=\s).*\s$user(\s|$)/m; + } + } + + return ($c, $r, $w); +} + # ---------------------------------------------------------------------------- # parse the compiled acl # ---------------------------------------------------------------------------- sub parse_acl { - my $GL_CONF_COMPILED = shift; + # IMPLEMENTATION NOTE: a wee bit of this is duplicated in the update hook; + # please update that also if the interface or the env vars change + + my ($GL_CONF_COMPILED, $repo, $c, $r, $w) = @_; + + # void $r if same as $w (otherwise "readers" overrides "writers"; this is + # the same problem that needed a sort sub for the Dumper in the compile + # script, but localised to just $readers and $writers) + $r = "" if $r eq $w; + + # set up the variables for a parse to interpolate stuff from the dumped + # hash (remember the selective conversion of single to double quotes?). + + # if they're not passed in, then we look for an env var of that name, else + # we default to "NOBODY" (we hope there isn't a real user called NOBODY!) + # And in any case, we set those env vars so level 2 can redo the last + # parse without any special code + + our $creater = $ENV{GL_CREATER} = $c || $ENV{GL_CREATER} || "NOBODY"; + our $readers = $ENV{GL_READERS} = $r || $ENV{GL_READERS} || "NOBODY"; + our $writers = $ENV{GL_WRITERS} = $w || $ENV{GL_WRITERS} || "NOBODY"; + die "parse $GL_CONF_COMPILED failed: " . ($! or $@) unless do $GL_CONF_COMPILED; + + # access reporting doesn't send $repo, and doesn't need to + return unless $repo; + + return $ENV{GL_REPOPATT} = "" if $repos{$repo}; + my @matched = grep { $repo =~ /^$_$/ } sort keys %repos; + die "$repo has no matches\n" unless @matched; + die "$repo has multiple matches\n@matched\n" if @matched > 1; + # found exactly one pattern that matched, copy its ACL + $repos{$repo} = $repos{$matched[0]}; + # and return the pattern + return $ENV{GL_REPOPATT} = $matched[0]; } # ---------------------------------------------------------------------------- @@ -114,16 +176,18 @@ sub report_basic { my($GL_ADMINDIR, $GL_CONF_COMPILED, $user) = @_; - &parse_acl($GL_CONF_COMPILED); + &parse_acl($GL_CONF_COMPILED, "", "CREATER", "READERS", "WRITERS"); # send back some useful info if no command was given print "hello $user, the gitolite version here is "; system("cat", "$GL_ADMINDIR/src/VERSION"); print "\ryou have the following permissions:\n\r"; for my $r (sort keys %repos) { - my $perm .= ( $repos{$r}{R}{'@all'} ? ' @' : ( $repos{$r}{R}{$user} ? ' R' : '' ) ); - $perm .= ( $repos{$r}{W}{'@all'} ? ' @' : ( $repos{$r}{W}{$user} ? ' W' : '' ) ); - print "$perm\t$r\n\r" if $perm; + my $perm .= ( $repos{$r}{C}{'@all'} ? ' @' : ( $repos{$r}{C}{$user} ? ' C' : ' ' ) ); + $perm .= ( $repos{$r}{R}{'@all'} ? ' @' : ( $repos{$r}{R}{$user} ? ' R' : ' ' ) ); + $perm .= ( $repos{$r}{W}{'@all'} ? ' @' : ( $repos{$r}{W}{$user} ? ' W' : ' ' ) ); + print "$perm\t$r\n\r" if $perm =~ /\S/; } } 1; + diff --git a/src/gl-auth-command b/src/gl-auth-command index 86fec88..eff0d77 100755 --- a/src/gl-auth-command +++ b/src/gl-auth-command @@ -78,8 +78,28 @@ die "bad command: $cmd. Make sure the repo name is exactly as in your config\n" # first level permissions check # ---------------------------------------------------------------------------- -# parse the compiled acl; goes into %repos (global) -&parse_acl($GL_CONF_COMPILED); +my $repo_base_abs = ( $REPO_BASE =~ m(^/) ? $REPO_BASE : "$ENV{HOME}/$REPO_BASE" ); + +if ( -d "$repo_base_abs/$repo.git" ) { + # existing repo + my ($creater, $user_R, $user_W) = &repo_rights($repo_base_abs, $repo, $user); + my $patt = &parse_acl($GL_CONF_COMPILED, $repo, $creater, $user_R, $user_W); +} else { + my $patt = &parse_acl($GL_CONF_COMPILED, $repo, $user, $user, $user); + # parse_acl returns "" if the repo was non-wildcard, or the pattern + # that matched if it was a wildcard + + # auto-vivify new repo; 2 situations allow autoviv -- normal repos + # with W access (the old mode), and wildcard repos with C access + my $W_ok = $repos{$repo}{W}{$user} || $repos{$repo}{W}{'@all'}; + my $C_ok = $repos{$repo}{C}{$user} || $repos{$repo}{C}{'@all'}; + if ($W_ok and not $patt or $C_ok and $patt) { + wrap_chdir("$repo_base_abs"); + # for wildcard repos, we also want to set the "creater" + new_repo($repo, "$GL_ADMINDIR/src/hooks", ( $patt ? $user : "")); + wrap_chdir($ENV{HOME}); + } +} # we know the user and repo; we just need to know what perm he's trying my $perm = ($verb =~ $R_COMMANDS ? 'R' : 'W'); @@ -88,16 +108,6 @@ die "$perm access for $repo DENIED to $user\n" unless $repos{$repo}{$perm}{$user} or $repos{$repo}{$perm}{'@all'}; -# create the repo if it doesn't already exist and the user has "W" access -my $repo_base_abs = ( $REPO_BASE =~ m(^/) ? $REPO_BASE : "$ENV{HOME}/$REPO_BASE" ); -if ( not -d "$repo_base_abs/$repo.git" ) { - if ( $repos{$repo}{W}{$user} or $repos{$repo}{W}{'@all'} ) { - wrap_chdir("$repo_base_abs"); - new_repo($repo, "$GL_ADMINDIR/src/hooks"); - wrap_chdir($ENV{HOME}); - } -} - # ---------------------------------------------------------------------------- # logging, timestamp. also setup env vars for later # ---------------------------------------------------------------------------- @@ -113,7 +123,7 @@ for ($s, $min, $h, $d, $m) { } $ENV{GL_TS} = "$y-$m-$d.$h:$min:$s"; -# substitute template parameters and set the logfile name +# substitute template parameters and set the logfile name $GL_LOGT =~ s/%y/$y/g; $GL_LOGT =~ s/%m/$m/g; $GL_LOGT =~ s/%d/$d/g; diff --git a/src/gl-compile-conf b/src/gl-compile-conf index 54448f6..da1822a 100755 --- a/src/gl-compile-conf +++ b/src/gl-compile-conf @@ -5,6 +5,15 @@ use warnings; use Data::Dumper; $Data::Dumper::Indent = 1; $Data::Dumper::Sortkeys = 1; +$Data::Dumper::Sortkeys = sub { return [ reverse sort keys %{$_[0]} ]; }; + # this is to make sure that $creater etc go to the end of the dumped hash. + # Without this, a setup that has something like + # @team = u1 u2 u3 + # repo priv/CREATER/.+ + # RW+ = CREATER + # RW = @team + # has a problem. The RW overrides the RW+ when the dumped hash is read in + # (simply going by sequence), so creater's special privs are lost # === add-auth-keys === @@ -352,6 +361,7 @@ wrap_chdir("$repo_base_abs"); for my $repo (sort keys %repos) { next unless $repo =~ $REPONAME_PATT; + print STDERR "creating $repo...\n"; unless (-d "$repo.git") { new_repo($repo, "$GL_ADMINDIR/src/hooks"); # new_repo would have chdir'd us away; come back diff --git a/src/hooks/update b/src/hooks/update index 235fa40..0ee7ad0 100755 --- a/src/hooks/update +++ b/src/hooks/update @@ -30,8 +30,21 @@ our %repos; # we should already have the GL_RC env var set when we enter this hook die "parse $ENV{GL_RC} failed: " . ($! or $@) unless do $ENV{GL_RC}; -# then "do" the compiled config file, whose name we now know -die "parse $GL_CONF_COMPILED failed: " . ($! or $@) unless do $GL_CONF_COMPILED; +# then "do" the compiled config file, whose name we now know. Before doing +# that we setup the creater etc from environment variables so that the parse +# interpolates them. We've minimised the duplication but this *does* +# duplicate a bit of parse_acl from gitolite.pm; we don't want to include that +# file here just for that little bit +{ + our $creater = $ENV{GL_CREATER}; + our $readers = $ENV{GL_READERS}; + our $writers = $ENV{GL_WRITERS}; + + die "parse $GL_CONF_COMPILED failed: " . ($! or $@) unless do $GL_CONF_COMPILED; + + $repos{$ENV{GL_REPO}} = $repos{$ENV{GL_REPOPATT}} if ( $ENV{GL_REPOPATT} ); +} +my $reported_repo = $ENV{GL_REPO} . ( $ENV{GL_REPOPATT} ? " ($ENV{GL_REPOPATT})" : "" ); # ---------------------------------------------------------------------------- # start... @@ -101,12 +114,12 @@ sub check_ref { $refex = (keys %$ar)[0]; # refex? sure -- a regex to match a ref against :) next unless $ref =~ /$refex/; - die "$perm $ref $ENV{GL_USER} DENIED by $refex\n" if $ar->{$refex} eq '-'; + die "$perm $ref $reported_repo $ENV{GL_USER} DENIED by $refex\n" if $ar->{$refex} eq '-'; # as far as *this* ref is concerned we're ok return $refex if ($ar->{$refex} =~ /\Q$perm/); } - die "$perm $ref $ENV{GL_REPO} $ENV{GL_USER} DENIED by fallthru\n"; + die "$perm $ref $reported_repo $ENV{GL_USER} DENIED by fallthru\n"; } # and in this version, we have many "refs" to check. The one we print in the @@ -123,6 +136,6 @@ check_ref($_) for @refs; open my $log_fh, ">>", $ENV{GL_LOG} or die "open log failed: $!\n"; print $log_fh "$ENV{GL_TS} $perm\t" . substr($oldsha, 0, 14) . "\t" . substr($newsha, 0, 14) . - "\t$ENV{GL_REPO}\t$ref\t$ENV{GL_USER}\t$log_refex\n"; + "\t$reported_repo\t$ref\t$ENV{GL_USER}\t$log_refex\n"; close $log_fh or die "close log failed: $!\n"; exit 0;