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:
parent
77306567e9
commit
f49eddd660
4 changed files with 121 additions and 24 deletions
|
@ -81,7 +81,7 @@ sub where_is_rc
|
||||||
# NOTE: this sub will change your cwd; caller beware!
|
# NOTE: this sub will change your cwd; caller beware!
|
||||||
sub new_repo
|
sub new_repo
|
||||||
{
|
{
|
||||||
my ($repo, $hooks_dir) = @_;
|
my ($repo, $hooks_dir, $creater) = @_;
|
||||||
|
|
||||||
umask($REPO_UMASK);
|
umask($REPO_UMASK);
|
||||||
|
|
||||||
|
@ -89,19 +89,81 @@ sub new_repo
|
||||||
# erm, note that's "and die" not "or die" as is normal in perl
|
# erm, note that's "and die" not "or die" as is normal in perl
|
||||||
wrap_chdir("$repo.git");
|
wrap_chdir("$repo.git");
|
||||||
system("git --bare init >&2");
|
system("git --bare init >&2");
|
||||||
|
system("echo $creater > gl-creater") if $creater;
|
||||||
# propagate our own, plus any local admin-defined, hooks
|
# propagate our own, plus any local admin-defined, hooks
|
||||||
system("cp $hooks_dir/* hooks/");
|
system("cp $hooks_dir/* hooks/");
|
||||||
chmod 0755, "hooks/update";
|
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
|
# parse the compiled acl
|
||||||
# ----------------------------------------------------------------------------
|
# ----------------------------------------------------------------------------
|
||||||
|
|
||||||
sub parse_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;
|
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) = @_;
|
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
|
# send back some useful info if no command was given
|
||||||
print "hello $user, the gitolite version here is ";
|
print "hello $user, the gitolite version here is ";
|
||||||
system("cat", "$GL_ADMINDIR/src/VERSION");
|
system("cat", "$GL_ADMINDIR/src/VERSION");
|
||||||
print "\ryou have the following permissions:\n\r";
|
print "\ryou have the following permissions:\n\r";
|
||||||
for my $r (sort keys %repos) {
|
for my $r (sort keys %repos) {
|
||||||
my $perm .= ( $repos{$r}{R}{'@all'} ? ' @' : ( $repos{$r}{R}{$user} ? ' R' : '' ) );
|
my $perm .= ( $repos{$r}{C}{'@all'} ? ' @' : ( $repos{$r}{C}{$user} ? ' C' : ' ' ) );
|
||||||
$perm .= ( $repos{$r}{W}{'@all'} ? ' @' : ( $repos{$r}{W}{$user} ? ' W' : '' ) );
|
$perm .= ( $repos{$r}{R}{'@all'} ? ' @' : ( $repos{$r}{R}{$user} ? ' R' : ' ' ) );
|
||||||
print "$perm\t$r\n\r" if $perm;
|
$perm .= ( $repos{$r}{W}{'@all'} ? ' @' : ( $repos{$r}{W}{$user} ? ' W' : ' ' ) );
|
||||||
|
print "$perm\t$r\n\r" if $perm =~ /\S/;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
1;
|
1;
|
||||||
|
|
||||||
|
|
|
@ -78,8 +78,28 @@ die "bad command: $cmd. Make sure the repo name is exactly as in your config\n"
|
||||||
# first level permissions check
|
# first level permissions check
|
||||||
# ----------------------------------------------------------------------------
|
# ----------------------------------------------------------------------------
|
||||||
|
|
||||||
# parse the compiled acl; goes into %repos (global)
|
my $repo_base_abs = ( $REPO_BASE =~ m(^/) ? $REPO_BASE : "$ENV{HOME}/$REPO_BASE" );
|
||||||
&parse_acl($GL_CONF_COMPILED);
|
|
||||||
|
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
|
# we know the user and repo; we just need to know what perm he's trying
|
||||||
my $perm = ($verb =~ $R_COMMANDS ? 'R' : 'W');
|
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}
|
unless $repos{$repo}{$perm}{$user}
|
||||||
or $repos{$repo}{$perm}{'@all'};
|
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
|
# logging, timestamp. also setup env vars for later
|
||||||
# ----------------------------------------------------------------------------
|
# ----------------------------------------------------------------------------
|
||||||
|
|
|
@ -5,6 +5,15 @@ use warnings;
|
||||||
use Data::Dumper;
|
use Data::Dumper;
|
||||||
$Data::Dumper::Indent = 1;
|
$Data::Dumper::Indent = 1;
|
||||||
$Data::Dumper::Sortkeys = 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 ===
|
# === add-auth-keys ===
|
||||||
|
|
||||||
|
@ -352,6 +361,7 @@ wrap_chdir("$repo_base_abs");
|
||||||
|
|
||||||
for my $repo (sort keys %repos) {
|
for my $repo (sort keys %repos) {
|
||||||
next unless $repo =~ $REPONAME_PATT;
|
next unless $repo =~ $REPONAME_PATT;
|
||||||
|
print STDERR "creating $repo...\n";
|
||||||
unless (-d "$repo.git") {
|
unless (-d "$repo.git") {
|
||||||
new_repo($repo, "$GL_ADMINDIR/src/hooks");
|
new_repo($repo, "$GL_ADMINDIR/src/hooks");
|
||||||
# new_repo would have chdir'd us away; come back
|
# new_repo would have chdir'd us away; come back
|
||||||
|
|
|
@ -30,8 +30,21 @@ our %repos;
|
||||||
|
|
||||||
# we should already have the GL_RC env var set when we enter this hook
|
# 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};
|
die "parse $ENV{GL_RC} failed: " . ($! or $@) unless do $ENV{GL_RC};
|
||||||
# then "do" the compiled config file, whose name we now know
|
# then "do" the compiled config file, whose name we now know. Before doing
|
||||||
die "parse $GL_CONF_COMPILED failed: " . ($! or $@) unless do $GL_CONF_COMPILED;
|
# 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...
|
# start...
|
||||||
|
@ -101,12 +114,12 @@ sub check_ref {
|
||||||
$refex = (keys %$ar)[0];
|
$refex = (keys %$ar)[0];
|
||||||
# refex? sure -- a regex to match a ref against :)
|
# refex? sure -- a regex to match a ref against :)
|
||||||
next unless $ref =~ /$refex/;
|
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
|
# as far as *this* ref is concerned we're ok
|
||||||
return $refex if ($ar->{$refex} =~ /\Q$perm/);
|
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
|
# 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";
|
open my $log_fh, ">>", $ENV{GL_LOG} or die "open log failed: $!\n";
|
||||||
print $log_fh "$ENV{GL_TS} $perm\t" .
|
print $log_fh "$ENV{GL_TS} $perm\t" .
|
||||||
substr($oldsha, 0, 14) . "\t" . substr($newsha, 0, 14) .
|
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";
|
close $log_fh or die "close log failed: $!\n";
|
||||||
exit 0;
|
exit 0;
|
||||||
|
|
Loading…
Reference in a new issue