wildrepos: teach auth and update hook about wildcard repos

- new_repo now takes a "creater" parameter; if given, this user is
    recorded (in a file called "gl-creater") as the creater of the repo.
    Only applicable to wildcards

  - repo_rights reads "gl-creater" and "gl-perms" to tell you who
    created it, and whether you (the $user) are in the list of READERS
    or WRITERS

    **NOTE** that the mechanism to create/update gl-perms has not been
    written yet... (as of this commit)

  - parse_acl takes 4 more arguments, all optional.  The repo name we're
    interested in (set by all except the access reporting function), and
    the names to be interpolated as $creater, $readers, writers

  - report_basic now knows about the "C" permission and shows it

  - auth now autovivifies a repo if the user has "C" and it's a wildcard
    match, or (the old case) the user has "W" and it's not a wildcard.
    In the former case, the creater is also set

IMPLEMENTATION NOTES:

  - the Dumper code now uses a custom hash key sort to make sure
    $creater etc land up at the *end*

  - a wee bit of duplication exists in the update hook; it borrows a
    little code from parse_acl.  I dont (yet) want to include all of
    gitolite.pm for that little piece...
This commit is contained in:
Sitaram Chamarty 2009-12-05 22:39:56 +05:30
parent 77306567e9
commit f49eddd660
4 changed files with 121 additions and 24 deletions

View file

@ -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;

View file

@ -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;

View file

@ -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

View file

@ -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;