Merge branch 'pu-big-config' into pu
This commit is contained in:
commit
c3d23f8734
6 changed files with 488 additions and 138 deletions
|
@ -82,7 +82,13 @@ $GIT_PATH="";
|
|||
|
||||
# --------------------------------------
|
||||
|
||||
# ----------------------------------------------------------------------
|
||||
# BIG CONFIG SETTINGS
|
||||
|
||||
# Please read doc/big-config.mkd for details
|
||||
|
||||
$GL_BIG_CONFIG = 0;
|
||||
$GL_NO_DAEMON_NO_GITWEB = 0;
|
||||
|
||||
# ----------------------------------------------------------------------
|
||||
# SECURITY SENSITIVE SETTINGS
|
||||
|
|
219
doc/big-config.mkd
Normal file
219
doc/big-config.mkd
Normal file
|
@ -0,0 +1,219 @@
|
|||
# what is a "big-config"
|
||||
|
||||
In this document:
|
||||
|
||||
* when/why do we need it?
|
||||
* how do we use it?
|
||||
* summary of settings in RC file
|
||||
* what are the downsides?
|
||||
* (extra coolness) usergroups and LDAP/similar tools
|
||||
|
||||
### when/why do we need it?
|
||||
|
||||
A "big config" is anything that has a few thousand users and a few thousand
|
||||
repos, organised into groups that are much smaller in number (like maybe a few
|
||||
hundreds of repogroups and a few dozens of usergroups).
|
||||
|
||||
So let's say you have
|
||||
|
||||
@wbr = lynx firefox
|
||||
@devs = alice bob
|
||||
|
||||
repo @wbr
|
||||
RW+ next = @devs
|
||||
RW master = @devs
|
||||
|
||||
Gitolite internally translates this to
|
||||
|
||||
repo lynx firefox
|
||||
RW+ next = alice bob
|
||||
RW master = alice bob
|
||||
|
||||
Not just that -- it now generates the actual config rules once for each
|
||||
user-repo-ref combination (there are 8 combinations above; the compiled config
|
||||
file looks partly like this:
|
||||
|
||||
%repos = (
|
||||
'firefox' => {
|
||||
'R' => {
|
||||
'alice' => 1,
|
||||
'bob' => 1
|
||||
},
|
||||
'W' => {
|
||||
'alice' => 1,
|
||||
'bob' => 1
|
||||
},
|
||||
'alice' => [
|
||||
{
|
||||
'refs/heads/next' => 'RW+'
|
||||
},
|
||||
{
|
||||
'refs/heads/master' => 'RW'
|
||||
}
|
||||
],
|
||||
'bob' => [
|
||||
{
|
||||
'refs/heads/next' => 'RW+'
|
||||
},
|
||||
{
|
||||
'refs/heads/master' => 'RW'
|
||||
}
|
||||
]
|
||||
},
|
||||
'lynx' => {
|
||||
'R' => {
|
||||
'alice' => 1,
|
||||
'bob' => 1
|
||||
},
|
||||
'W' => {
|
||||
'alice' => 1,
|
||||
'bob' => 1
|
||||
},
|
||||
'alice' => [
|
||||
{
|
||||
'refs/heads/next' => 'RW+'
|
||||
},
|
||||
{
|
||||
'refs/heads/master' => 'RW'
|
||||
}
|
||||
],
|
||||
'bob' => [
|
||||
{
|
||||
'refs/heads/next' => 'RW+'
|
||||
},
|
||||
{
|
||||
'refs/heads/master' => 'RW'
|
||||
}
|
||||
]
|
||||
}
|
||||
);
|
||||
|
||||
Phew!
|
||||
|
||||
You can imagine what that does when you have 10,000 users and 10,000 repos.
|
||||
Let's just say it's not pretty :)
|
||||
|
||||
### how do we use it?
|
||||
|
||||
Now, if you had all those 10,000 users and repos explicitly listed (no
|
||||
groups), then there is no help. But if, like the above example, you had
|
||||
groups like we used above, there is hope.
|
||||
|
||||
Just set
|
||||
|
||||
$GL_BIG_CONFIG = 1;
|
||||
|
||||
in the `~/.gitolite.rc` file on the server. When you do that, and push this
|
||||
configuration, the compiled file looks like this:
|
||||
|
||||
%repos = (
|
||||
'@wbr' => {
|
||||
'@devs' => [
|
||||
{
|
||||
'refs/heads/next' => 'RW+'
|
||||
},
|
||||
{
|
||||
'refs/heads/master' => 'RW'
|
||||
}
|
||||
],
|
||||
'R' => {
|
||||
'@devs' => 1
|
||||
},
|
||||
'W' => {
|
||||
'@devs' => 1
|
||||
}
|
||||
},
|
||||
);
|
||||
%groups = (
|
||||
'@devs' => {
|
||||
'alice' => 'master',
|
||||
'bob' => 'master'
|
||||
},
|
||||
'@wbr' => {
|
||||
'firefox' => 'master',
|
||||
'lynx' => 'master'
|
||||
}
|
||||
);
|
||||
|
||||
That's a lot smaller, and allows orders of magintude more repos and groups to
|
||||
be supported.
|
||||
|
||||
### summary of settings in RC file
|
||||
|
||||
The default RC file contains the following lines:
|
||||
|
||||
$GL_BIG_CONFIG = 0;
|
||||
$GL_NO_DAEMON_NO_GITWEB = 0;
|
||||
|
||||
The first setting means that by default, big-config is off; you can change it
|
||||
to 1 to enable it.
|
||||
|
||||
The second is a very useful optimisation that you *must* enable if you *do*
|
||||
have a large number of repositories, and do *not* use gitolite's support for
|
||||
gitweb or git-daemon access (see "[easier to specify gitweb description and
|
||||
gitweb/daemon access][gw]" for details). This will save a lot of time when
|
||||
you push the gitolite-admin repo with changes.
|
||||
|
||||
[gw]: http://github.com/sitaramc/gitolite/blob/pu/doc/3-faq-tips-etc.mkd#gitweb
|
||||
|
||||
### what are the downsides?
|
||||
|
||||
There are a few minor issues.
|
||||
|
||||
First, "deny" rules (rules whose "permission" is a "-" sign), will not work
|
||||
exactly the same as before.
|
||||
|
||||
[TODO: add a nice example etc...]
|
||||
|
||||
Second, if you use the delegation feature, you can no longer define or extend
|
||||
@groups in a fragment, for security reasons. It will also not let you use any
|
||||
group other than the @fragname itself (specifically, groups which contained a
|
||||
subset of the allowed @fragname, which would work normally, do not work now).
|
||||
|
||||
(If you didn't understand all that, you're probably not using delegation, so
|
||||
feel free to ignore it!)
|
||||
|
||||
### (extra coolness) usergroups and LDAP/similar tools
|
||||
|
||||
[Please NOTE: this is all about *user* groups, not *repo* groups]
|
||||
|
||||
Gitolite now allows usergroup information to be passed in from outside. The
|
||||
`gl-auth-commmand` can now take an optional list of usergroup names after the
|
||||
first argument (which is the username).
|
||||
|
||||
To understand why this is useful, consider the following:
|
||||
|
||||
Some people have an LDAP-backed ssh daemon (or some other similar mechanism
|
||||
that can speak "ssh" to the client), with pubkeys stored in LDAP, etc., and
|
||||
some way (not using `~/.ssh/authorized_keys`) of invoking gitolite.
|
||||
|
||||
Such setups also have "usergroups", and a way to tell, for each user, what
|
||||
groups he/she is a member of. So let's say "alice" works on projects "foo"
|
||||
and "bar", while "bob" is works on project "bar" and is a member of the
|
||||
`L3_support` team.
|
||||
|
||||
You can use those group names in the gitolite config file for access control
|
||||
as "@foo", "@bar", `@L3_support`, etc.; please note the "@" prefix because
|
||||
gitolite requires it.
|
||||
|
||||
However, that still leaves a wee little inconvenience. You still have to add
|
||||
lines like this to the gitolite config file:
|
||||
|
||||
@foo = alice
|
||||
@bar = alice bob
|
||||
@L3_support = bob
|
||||
|
||||
You don't need to do that anymore now. Tell your authentication system that,
|
||||
after authenticating alice, instead of running:
|
||||
|
||||
/some/path/to/gl-auth-command alice
|
||||
|
||||
it should first find the groups that alice is a member of, and then run:
|
||||
|
||||
/some/path/to/gl-auth-command alice foo bar
|
||||
|
||||
That's it. Internally, gitolite will behave as if the config file had also
|
||||
specified:
|
||||
|
||||
@foo = alice
|
||||
@bar = alice
|
|
@ -34,27 +34,12 @@ exit 0 if exists $ENV{GL_BYPASS_UPDATE_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};
|
||||
# then "do" the compiled config file, whose name we now know. Before doing
|
||||
# that we setup the creator 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 $creator = $ENV{GL_CREATOR};
|
||||
our $readers = $ENV{GL_READERS};
|
||||
our $writers = $ENV{GL_WRITERS};
|
||||
our $gl_user = $ENV{GL_USER};
|
||||
|
||||
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})" : "" );
|
||||
|
||||
# we've started to need some common subs in what used to be a small, cute,
|
||||
# little script that barely spanned a few lines :(
|
||||
require "$ENV{GL_BINDIR}/gitolite.pm";
|
||||
|
||||
my ($perm, $creator, $wild) = &repo_rights($ENV{GL_REPO});
|
||||
my $reported_repo = $ENV{GL_REPO} . ( $ENV{GL_REPOPATT} ? " ($ENV{GL_REPOPATT})" : "" );
|
||||
|
||||
# ----------------------------------------------------------------------------
|
||||
# start...
|
||||
# ----------------------------------------------------------------------------
|
||||
|
@ -71,17 +56,17 @@ chomp($merge_base = `git merge-base $oldsha $newsha`)
|
|||
unless $oldsha eq '0' x 40
|
||||
or $newsha eq '0' x 40;
|
||||
|
||||
# what are you trying to do? (is it 'W' or '+'?)
|
||||
my $perm = 'W';
|
||||
# att_acc == attempted access -- what are you trying to do? (is it 'W' or '+'?)
|
||||
my $att_acc = 'W';
|
||||
# rewriting a tag is considered a rewind, in terms of permissions
|
||||
$perm = '+' if $ref =~ m(refs/tags/) and $oldsha ne ('0' x 40);
|
||||
$att_acc = '+' if $ref =~ m(refs/tags/) and $oldsha ne ('0' x 40);
|
||||
# non-ff push to ref
|
||||
# notice that ref delete looks like a rewind, as it should
|
||||
$perm = '+' if $oldsha ne $merge_base;
|
||||
$att_acc = '+' if $oldsha ne $merge_base;
|
||||
|
||||
# were any 'D' perms specified? If they were, it means we have to separate
|
||||
# deletes from rewinds, so if the new sha is all 0's, change the '+' to a 'D'
|
||||
$perm = 'D' if ( $repos{$ENV{GL_REPO}}{DELETE_IS_D} or $repos{'@all'}{DELETE_IS_D} ) and $newsha eq '0' x 40;
|
||||
$att_acc = 'D' if ( $repos{$ENV{GL_REPO}}{DELETE_IS_D} or $repos{'@all'}{DELETE_IS_D} ) and $newsha eq '0' x 40;
|
||||
|
||||
my @allowed_refs;
|
||||
# @all repos: see comments in similar code in check_access
|
||||
|
@ -113,12 +98,12 @@ if (exists $repos{$ENV{GL_REPO}}{NAME_LIMITS}) {
|
|||
# log is the *first* one (which is a *real* ref, like refs/heads/master),
|
||||
# while all the rest (if they exist) are like NAME/something. So we do the
|
||||
# first one separately to capture it, then run the rest (if any)
|
||||
my $log_refex = check_ref(\@allowed_refs, $ENV{GL_REPO}, (shift @refs), $perm);
|
||||
&check_ref (\@allowed_refs, $ENV{GL_REPO}, $_ , $perm) for @refs;
|
||||
my $log_refex = check_ref(\@allowed_refs, $ENV{GL_REPO}, (shift @refs), $att_acc);
|
||||
&check_ref (\@allowed_refs, $ENV{GL_REPO}, $_ , $att_acc) for @refs;
|
||||
|
||||
# if we returned at all, all the checks succeeded, so we log the action and exit 0
|
||||
|
||||
&log_it("$ENV{GL_TS} $perm\t" .
|
||||
&log_it("$ENV{GL_TS} $att_acc\t" .
|
||||
substr($oldsha, 0, 14) . "\t" . substr($newsha, 0, 14) .
|
||||
"\t$reported_repo\t$ref\t$ENV{GL_USER}\t$log_refex\n");
|
||||
|
||||
|
|
191
src/gitolite.pm
191
src/gitolite.pm
|
@ -1,4 +1,7 @@
|
|||
use strict;
|
||||
use Data::Dumper;
|
||||
$Data::Dumper::Deepcopy = 1;
|
||||
|
||||
# this file is commonly used using "require". It is not required to use "use"
|
||||
# (because it doesn't live in a different package)
|
||||
|
||||
|
@ -34,8 +37,9 @@ our $USERNAME_PATT=qr(^\@?[0-9a-zA-Z][0-9a-zA-Z._\@+-]*$); # very simple patter
|
|||
our $REPOPATT_PATT=qr(^\@?[0-9a-zA-Z][\\^.$|()[\]*+?{}0-9a-zA-Z._\@/-]*$);
|
||||
|
||||
# these come from the RC file
|
||||
our ($REPO_UMASK, $GL_WILDREPOS, $GL_PACKAGE_CONF, $GL_PACKAGE_HOOKS, $REPO_BASE, $GL_CONF_COMPILED);
|
||||
our ($REPO_UMASK, $GL_WILDREPOS, $GL_PACKAGE_CONF, $GL_PACKAGE_HOOKS, $REPO_BASE, $GL_CONF_COMPILED, $GL_BIG_CONFIG);
|
||||
our %repos;
|
||||
our %groups;
|
||||
|
||||
# ----------------------------------------------------------------------------
|
||||
# convenience subs
|
||||
|
@ -253,24 +257,39 @@ sub parse_acl
|
|||
# want the config dumped as is, really
|
||||
return unless $repo;
|
||||
|
||||
# return with "no wildcard match" status if you found the actual repo in
|
||||
# the config or if wild is unset
|
||||
return $ENV{GL_REPOPATT} = "" if $repos{$repo} or not $GL_WILDREPOS;
|
||||
my ($wild, @repo_plus, @user_plus);
|
||||
# expand $repo and $gl_user into all possible matching values
|
||||
($wild, @repo_plus) = &get_memberships($repo, 1);
|
||||
( @user_plus) = &get_memberships($gl_user, 0);
|
||||
# XXX testing notes: the above should return just one entry during
|
||||
# non-BC usage, whether wild or not
|
||||
die "assert 1 failed" if (@repo_plus > 1 and $repo_plus[-1] ne '@all'
|
||||
or @repo_plus > 2) and not $GL_BIG_CONFIG;
|
||||
|
||||
# didn't find actual repo in %repos, and wild is set, so find the repo
|
||||
# pattern that matches the actual repo
|
||||
my @matched = grep { $repo =~ /^$_$/ } sort keys %repos;
|
||||
# the old "convenience copy" thing. Now on steroids :)
|
||||
|
||||
# didn't find a match? avoid leaking info to user about repo existence;
|
||||
# as before, pretend "no wildcard match" status
|
||||
return $ENV{GL_REPOPATT} = "" unless @matched;
|
||||
# note that when copying the @all entry, we retain the destination name as
|
||||
# @all; we dont change it to $repo or $gl_user
|
||||
for my $r ('@all', @repo_plus) {
|
||||
my $dr = $repo; $dr = '@all' if $r eq '@all';
|
||||
$repos{$dr}{DELETE_IS_D} = 1 if $repos{$r}{DELETE_IS_D};
|
||||
$repos{$dr}{NAME_LIMITS} = 1 if $repos{$r}{NAME_LIMITS};
|
||||
|
||||
die "$repo has multiple matches\n@matched\n" if @matched > 1;
|
||||
for my $u ('@all', @user_plus) {
|
||||
my $du = $gl_user; $du = '@all' if $u eq '@all';
|
||||
$repos{$dr}{C}{$du} = 1 if $repos{$r}{C}{$u};
|
||||
$repos{$dr}{R}{$du} = 1 if $repos{$r}{R}{$u};
|
||||
$repos{$dr}{W}{$du} = 1 if $repos{$r}{W}{$u};
|
||||
|
||||
# found exactly one pattern that matched, copy its ACL for convenience
|
||||
$repos{$repo} = $repos{$matched[0]};
|
||||
# and return the pattern
|
||||
return $ENV{GL_REPOPATT} = $matched[0];
|
||||
next if $r eq $dr and $u eq $du; # no point duplicating those refexes
|
||||
push @{ $repos{$dr}{$du} }, @{ $repos{$r}{$u} }
|
||||
if exists $repos{$r}{$u} and ref($repos{$r}{$u}) eq 'ARRAY';
|
||||
}
|
||||
}
|
||||
|
||||
$ENV{GL_REPOPATT} = "";
|
||||
$ENV{GL_REPOPATT} = $wild if $wild and $GL_WILDREPOS;
|
||||
return ($wild);
|
||||
}
|
||||
|
||||
# ----------------------------------------------------------------------------
|
||||
|
@ -295,6 +314,11 @@ sub report_basic
|
|||
&report_version($GL_ADMINDIR, $user);
|
||||
print "\rthe gitolite config gives you the following access:\r\n";
|
||||
for my $r (sort keys %repos) {
|
||||
if ($r =~ $REPONAME_PATT) {
|
||||
&parse_acl($GL_CONF_COMPILED, $r, "NOBODY", "NOBODY", "NOBODY");
|
||||
} else {
|
||||
&parse_acl($GL_CONF_COMPILED, $r, $ENV{GL_USER}, "NOBODY", "NOBODY");
|
||||
}
|
||||
# @all repos; meaning of read/write flags:
|
||||
# @R => @all users are allowed access to this repo
|
||||
# #R => you're a super user and can see @all repos
|
||||
|
@ -331,7 +355,7 @@ sub expand_wild
|
|||
# actual_repo has to match the pattern being expanded
|
||||
next unless $actual_repo =~ /$repo/;
|
||||
|
||||
my($perm, $creator) = &repo_rights($actual_repo);
|
||||
my($perm, $creator, $wild) = &repo_rights($actual_repo);
|
||||
next unless $perm =~ /\S/;
|
||||
print "$perm\t$creator\t$actual_repo\n";
|
||||
}
|
||||
|
@ -342,64 +366,67 @@ sub expand_wild
|
|||
# how/why). Regardless of how we're called, we assume $ENV{GL_USER} is
|
||||
# already defined
|
||||
{
|
||||
my %normal_repos;
|
||||
|
||||
my $last_repo = '';
|
||||
sub repo_rights {
|
||||
my $repo = shift;
|
||||
$repo =~ s/^\.\///;
|
||||
$repo =~ s/\.git$//;
|
||||
|
||||
return if $last_repo eq $repo; # a wee bit o' caching, though not yet needed
|
||||
|
||||
# we get passed an actual repo name. It may be a normal
|
||||
# (non-wildcard) repo, in which case it is assumed to exist. If it's
|
||||
# a wildrepo, it may or may not exist. If it doesn't exist, the "C"
|
||||
# perms are also filled in, else that column is left blank
|
||||
|
||||
unless (%normal_repos) {
|
||||
unless ($REPO_BASE) {
|
||||
# means we've been called from outside
|
||||
&where_is_rc();
|
||||
die "parse $ENV{GL_RC} failed: " . ($! or $@) unless do $ENV{GL_RC};
|
||||
}
|
||||
|
||||
&parse_acl($GL_CONF_COMPILED, "", "NOBODY", "NOBODY", "NOBODY");
|
||||
%normal_repos = %repos;
|
||||
unless ($REPO_BASE) {
|
||||
# means we've been called from outside; see doc/admin-defined-commands.mkd
|
||||
&where_is_rc();
|
||||
die "parse $ENV{GL_RC} failed: " . ($! or $@) unless do $ENV{GL_RC};
|
||||
}
|
||||
|
||||
my $creator;
|
||||
my $perm = ' ';
|
||||
my $creator;
|
||||
|
||||
# if repo is present "as is" in the config, those permissions will
|
||||
# override anything inherited from a wildcard that may have matched
|
||||
if ($normal_repos{$repo}) {
|
||||
%repos = %normal_repos;
|
||||
$creator = '<gitolite>';
|
||||
} elsif ( -d "$ENV{GL_REPO_BASE_ABS}/$repo.git" ) {
|
||||
# must be a wildrepo, and it has already been created; find the
|
||||
# creator and subsitute in repos
|
||||
# get basic info about the repo and fill %repos
|
||||
my $wild = '';
|
||||
my $exists = -d "$ENV{GL_REPO_BASE_ABS}/$repo.git";
|
||||
if ($exists) {
|
||||
# these will be empty if it's not a wildcard repo anyway
|
||||
my ($read, $write);
|
||||
($creator, $read, $write) = &wild_repo_rights($ENV{GL_REPO_BASE_ABS}, $repo, $ENV{GL_USER});
|
||||
# get access list with these substitutions
|
||||
&parse_acl($GL_CONF_COMPILED, $repo, $creator || "NOBODY", $read || "NOBODY", $write || "NOBODY");
|
||||
$wild = &parse_acl($GL_CONF_COMPILED, $repo, $creator || "NOBODY", $read || "NOBODY", $write || "NOBODY");
|
||||
} else {
|
||||
$wild = &parse_acl($GL_CONF_COMPILED, $repo, $ENV{GL_USER}, "NOBODY", "NOBODY");
|
||||
}
|
||||
|
||||
if ($exists and not $wild) {
|
||||
$creator = '<gitolite>';
|
||||
} elsif ($exists) {
|
||||
# is a wildrepo, and it has already been created
|
||||
$creator = "($creator)";
|
||||
} else {
|
||||
# repo didn't exist; C perms also need to be filled in after
|
||||
# getting access list with only creator filled in
|
||||
&parse_acl($GL_CONF_COMPILED, $repo, $ENV{GL_USER}, "NOBODY", "NOBODY");
|
||||
# repo didn't exist; C perms need to be filled in
|
||||
$perm = ( $repos{$repo}{C}{'@all'} ? ' @C' : ( $repos{$repo}{C}{$ENV{GL_USER}} ? ' =C' : ' ' )) if $GL_WILDREPOS;
|
||||
# if you didn't have perms to create it, delete the "convenience"
|
||||
# copy of the ACL that parse_acl makes
|
||||
delete $repos{$repo} unless $perm =~ /C/;
|
||||
$creator = "<repo_not_found>";
|
||||
$creator = "<notfound>";
|
||||
}
|
||||
$perm .= ( $repos{$repo}{R}{'@all'} ? ' @R' : ( $repos{'@all'}{R}{$ENV{GL_USER}} ? ' #R' : ( $repos{$repo}{R}{$ENV{GL_USER}} ? ' R' : ' ' )));
|
||||
$perm .= ( $repos{$repo}{W}{'@all'} ? ' @W' : ( $repos{'@all'}{W}{$ENV{GL_USER}} ? ' #W' : ( $repos{$repo}{W}{$ENV{GL_USER}} ? ' W' : ' ' )));
|
||||
return($perm, $creator);
|
||||
|
||||
# set up for caching %repos
|
||||
$last_repo = $repo;
|
||||
|
||||
return($perm, $creator, $wild);
|
||||
}
|
||||
}
|
||||
|
||||
# helper/convenience routine to get rights and ownership from a shell command
|
||||
sub cli_repo_rights {
|
||||
my ($perm, $creator) = &repo_rights($_[0]);
|
||||
my ($perm, $creator, $wild) = &repo_rights($_[0]);
|
||||
$perm =~ s/ /_/g;
|
||||
$creator =~ s/^\(|\)$//g;
|
||||
print "$perm $creator\n";
|
||||
|
@ -443,6 +470,84 @@ sub special_cmd
|
|||
}
|
||||
}
|
||||
|
||||
# ----------------------------------------------------------------------------
|
||||
# get memberships
|
||||
# ----------------------------------------------------------------------------
|
||||
|
||||
# given a plain reponame or username, return:
|
||||
# - the name itself, plus all the groups it belongs to if $GL_BIG_CONFIG is
|
||||
# set
|
||||
# OR
|
||||
# - (for repos) if the name itself doesn't exist in the config, a wildcard
|
||||
# matching it, plus all the groups that wildcard belongs to (again if
|
||||
# $GL_BIG_CONFIG is set)
|
||||
|
||||
# A name can normally appear (repo example) (user example)
|
||||
# - directly (repo foo) (RW = bar)
|
||||
# - (only for repos) as a direct wildcard (repo foo/.*)
|
||||
# but if $GL_BIG_CONFIG is set, it can also appear:
|
||||
# - indirectly (@g = foo; repo @g) (@ug = bar; RW = @ug))
|
||||
# - (only for repos) as an indirect wildcard (@g = foo/.*; repo @g).
|
||||
# things that may not be obvious from the above:
|
||||
# - the wildcard stuff does not apply to username memberships
|
||||
# - for repos, wildcard appearances are TOTALLY ignored if a non-wild
|
||||
# appearance (direct or indirect) exists
|
||||
|
||||
sub get_memberships {
|
||||
my $base = shift; # reponame or username
|
||||
my $is_repo = shift; # some true value means a repo name has been passed
|
||||
|
||||
my $wild = '';
|
||||
my (@ret, @ret_w); # maintain wild matches separately from non-wild
|
||||
|
||||
# direct
|
||||
push @ret, $base if not $is_repo or exists $repos{$base};
|
||||
if ($is_repo and $GL_WILDREPOS and not @ret) {
|
||||
for my $i (sort keys %repos) {
|
||||
if ($base =~ /^$i$/) {
|
||||
die "$ABRT $base matches $wild AND $i\n" if $wild and $wild ne $i;
|
||||
$wild = $i;
|
||||
# direct wildcard
|
||||
push @ret_w, $i;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ($GL_BIG_CONFIG) {
|
||||
for my $g (sort keys %groups) {
|
||||
for my $i (sort keys %{ $groups{$g} }) {
|
||||
if ($base eq $i) {
|
||||
# indirect
|
||||
push @ret, $g;
|
||||
} elsif ($is_repo and $GL_WILDREPOS and not @ret and $base =~ /^$i$/) {
|
||||
die "$ABRT $base matches $wild AND $i\n" if $wild and $wild ne $i;
|
||||
$wild = $i;
|
||||
# indirect wildcard
|
||||
push @ret_w, $g;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# deal with returning user info first
|
||||
unless ($is_repo) {
|
||||
# add in group membership info sent in via second and subsequent
|
||||
# arguments to gl-auth-command; be sure to prefix the "@" sign to each
|
||||
# of them!
|
||||
push @ret, map { s/^/@/; $_; } split(' ', $ENV{GL_GROUP_LIST}) if $ENV{GL_GROUP_LIST};
|
||||
return (@ret);
|
||||
}
|
||||
|
||||
# enforce the rule about ignoring all wildcard matches if a non-wild match
|
||||
# exists while returning. (The @ret gating above does not adequately
|
||||
# ensure this, it is only an optimisation).
|
||||
#
|
||||
# Also note that there is an extra return value when called for repos
|
||||
# (compared to usernames)
|
||||
|
||||
return ((@ret ? '' : $wild), (@ret ? @ret : @ret_w));
|
||||
}
|
||||
|
||||
# ----------------------------------------------------------------------------
|
||||
# generic check access routine
|
||||
# ----------------------------------------------------------------------------
|
||||
|
|
|
@ -28,6 +28,7 @@ our ($GL_LOGT, $GL_CONF_COMPILED, $REPO_BASE, $GIT_PATH, $REPO_UMASK, $GL_ADMIND
|
|||
# and these are set by gitolite.pm
|
||||
our ($R_COMMANDS, $W_COMMANDS, $REPONAME_PATT, $REPOPATT_PATT);
|
||||
our %repos;
|
||||
our %groups;
|
||||
|
||||
# the common setup module is in the same directory as this running program is
|
||||
my $bindir = $0;
|
||||
|
@ -55,6 +56,10 @@ my $repo_base_abs = $ENV{GL_REPO_BASE_ABS} = ( $REPO_BASE =~ m(^/) ? $REPO_BASE
|
|||
# start...
|
||||
# ----------------------------------------------------------------------------
|
||||
|
||||
# no arguments given? default user is $USER (fedorahosted works like this,
|
||||
# and it is harmless for others)
|
||||
@ARGV = ($ENV{USER}) unless @ARGV;
|
||||
|
||||
# if the first argument is a "-s", this user is allowed to get a shell using
|
||||
# this key
|
||||
my $shell_allowed = 0;
|
||||
|
@ -66,6 +71,10 @@ if ($ARGV[0] eq '-s') {
|
|||
# first, fix the biggest gripe I have with gitosis, a 1-line change
|
||||
my $user=$ENV{GL_USER}=shift; # there; now that's available everywhere!
|
||||
|
||||
# if there are any more arguments, they're a list of group names that the user
|
||||
# is a member of
|
||||
$ENV{GL_GROUP_LIST} = join(" ", @ARGV) if @ARGV;
|
||||
|
||||
# ----------------------------------------------------------------------------
|
||||
# logging, timestamp env vars
|
||||
# ----------------------------------------------------------------------------
|
||||
|
@ -184,7 +193,7 @@ $ENV{GL_REPO}=$repo;
|
|||
# first level permissions check
|
||||
# ----------------------------------------------------------------------------
|
||||
|
||||
my ($perm, $creator) = &repo_rights($repo);
|
||||
my ($perm, $creator, $wild) = &repo_rights($repo);
|
||||
if ($perm =~ /C/) {
|
||||
# it was missing, and you have create perms
|
||||
wrap_chdir("$repo_base_abs");
|
||||
|
|
|
@ -52,7 +52,7 @@ $Data::Dumper::Sortkeys = 1;
|
|||
open STDOUT, ">", "/dev/null" if (@ARGV and shift eq '-q');
|
||||
|
||||
# these are set by the "rc" file
|
||||
our ($GL_ADMINDIR, $GL_CONF, $GL_KEYDIR, $GL_CONF_COMPILED, $REPO_BASE, $REPO_UMASK, $PROJECTS_LIST, $GIT_PATH, $GL_WILDREPOS, $GL_GITCONFIG_KEYS, $GL_PACKAGE_HOOKS, $GL_SETPERMS_OVERRIDES_CONFIG);
|
||||
our ($GL_ADMINDIR, $GL_CONF, $GL_KEYDIR, $GL_CONF_COMPILED, $REPO_BASE, $REPO_UMASK, $PROJECTS_LIST, $GIT_PATH, $GL_WILDREPOS, $GL_GITCONFIG_KEYS, $GL_PACKAGE_HOOKS, $GL_SETPERMS_OVERRIDES_CONFIG, $GL_BIG_CONFIG, $GL_NO_DAEMON_NO_GITWEB);
|
||||
# and these are set by gitolite.pm
|
||||
our ($REPONAME_PATT, $REPOPATT_PATT, $USERNAME_PATT, $AUTH_COMMAND, $AUTH_OPTIONS, $ABRT, $WARN);
|
||||
|
||||
|
@ -173,6 +173,8 @@ sub parse_conf_file
|
|||
# user or repo groups
|
||||
if (/^(@\S+) = (.*)/)
|
||||
{
|
||||
die "$ABRT defining groups is not allowed inside fragments\n"
|
||||
if $GL_BIG_CONFIG and $fragment ne 'master';
|
||||
# store the members of each group as hash key. Keep track of when
|
||||
# the group was *first* created by using $fragment as the *value*
|
||||
do { $groups{$1}{$_} ||= $fragment } for ( expand_list( split(' ', $2) ) );
|
||||
|
@ -181,10 +183,12 @@ sub parse_conf_file
|
|||
# repo(s)
|
||||
elsif (/^repo (.*)/)
|
||||
{
|
||||
# grab the list and expand any @stuff in it
|
||||
# grab the list...
|
||||
@repos = split ' ', $1;
|
||||
unless (@repos == 1 and $repos[0] eq '@all') {
|
||||
@repos = expand_list ( @repos );
|
||||
# ...expand groups in the default case
|
||||
@repos = expand_list ( @repos ) unless $GL_BIG_CONFIG;
|
||||
# ...sanity check
|
||||
for (@repos) {
|
||||
die "$ABRT bad reponame $_\n"
|
||||
if ($GL_WILDREPOS and $_ !~ $REPOPATT_PATT);
|
||||
|
@ -214,7 +218,7 @@ sub parse_conf_file
|
|||
|
||||
# expand the user list, unless it is just "@all"
|
||||
@users = expand_list ( @users )
|
||||
unless (@users == 1 and $users[0] eq '@all');
|
||||
unless ($GL_BIG_CONFIG or (@users == 1 and $users[0] eq '@all'));
|
||||
do { die "$ABRT bad username $_\n" unless $_ =~ $USERNAME_PATT } for @users;
|
||||
|
||||
s/\bCREAT[EO]R\b/~\$creator/g for @users;
|
||||
|
@ -237,6 +241,8 @@ sub parse_conf_file
|
|||
# fragment is also called 'foo' (you're allowed to have a
|
||||
# fragment that is only concerned with one repo)
|
||||
( $fragment eq $repo ) or
|
||||
# same thing in big-config-land; foo is just @foo now
|
||||
( $GL_BIG_CONFIG and "\@$fragment" eq $repo ) or
|
||||
# fragment is called "bar" and "@bar = foo" has been
|
||||
# defined in the master config
|
||||
( ($groups{"\@$fragment"}{$repo} || '') eq 'master' )
|
||||
|
@ -370,6 +376,7 @@ my $dumped_data = Data::Dumper->Dump([\%repos], [qw(*repos)]);
|
|||
# much...
|
||||
$dumped_data =~ s/'(?=[^']*\$(?:creator|readers|writers|gl_user))~*(.*?)'/"$1"/g;
|
||||
print $compiled_fh $dumped_data;
|
||||
print $compiled_fh Data::Dumper->Dump([\%groups], [qw(*groups)]) if $GL_BIG_CONFIG and %groups;
|
||||
close $compiled_fh or die "$ABRT close compiled-conf failed: $!\n";
|
||||
|
||||
# ----------------------------------------------------------------------------
|
||||
|
@ -402,28 +409,37 @@ die "\n\t\t***** AAARGH! *****\n" .
|
|||
# so if it was not already absolute, prefix $HOME.
|
||||
my $repo_base_abs = ( $REPO_BASE =~ m(^/) ? $REPO_BASE : "$ENV{HOME}/$REPO_BASE" );
|
||||
|
||||
wrap_chdir("$repo_base_abs");
|
||||
{
|
||||
wrap_chdir("$repo_base_abs");
|
||||
|
||||
for my $repo (sort keys %repos) {
|
||||
next unless $repo =~ $REPONAME_PATT;
|
||||
next if $repo =~ m(^EXTCMD/); # these are not real repos
|
||||
next if $repo eq '@all';
|
||||
unless (-d "$repo.git") {
|
||||
print STDERR "creating $repo...\n";
|
||||
new_repo($repo, "$GL_ADMINDIR/hooks/common");
|
||||
# new_repo would have chdir'd us away; come back
|
||||
wrap_chdir("$repo_base_abs");
|
||||
}
|
||||
# autocreate repos. Start with the ones that are normal repos in %repos
|
||||
my @repos = grep { $_ =~ $REPONAME_PATT and not /^@/ } sort keys %repos;
|
||||
# then, for each repogroup, find the members of the group and add them in
|
||||
map { push @repos, keys %{ $groups{$_} } } grep { /^@/ } keys %repos;
|
||||
# weed out duplicates (the code in the loop below is disk activity!)
|
||||
my %seen = map { $_ => 1 } @repos;
|
||||
@repos = sort keys %seen;
|
||||
|
||||
# when repos are copied over from elsewhere, one had to run easy install
|
||||
# once again to make the new (OS-copied) repo contain the proper update
|
||||
# hook. Perhaps we can make this easier now, and eliminate the easy
|
||||
# install, with a quick check (and a new, empty, "hook" as a sentinel)
|
||||
unless (-l "$repo.git/hooks/gitolite-hooked") {
|
||||
ln_sf("$GL_ADMINDIR/hooks/common", "*", "$repo.git/hooks");
|
||||
# in case of package install, GL_ADMINDIR is no longer the top cop;
|
||||
# override with the package hooks
|
||||
ln_sf("$GL_PACKAGE_HOOKS/common", "*", "$repo.git/hooks") if $GL_PACKAGE_HOOKS;
|
||||
for my $repo (sort @repos) {
|
||||
next unless $repo =~ $REPONAME_PATT;
|
||||
next if $repo =~ m(^\@|EXTCMD/); # these are not real repos
|
||||
unless (-d "$repo.git") {
|
||||
print STDERR "creating $repo...\n";
|
||||
new_repo($repo, "$GL_ADMINDIR/hooks/common");
|
||||
# new_repo would have chdir'd us away; come back
|
||||
wrap_chdir("$repo_base_abs");
|
||||
}
|
||||
|
||||
# when repos are copied over from elsewhere, one had to run easy install
|
||||
# once again to make the new (OS-copied) repo contain the proper update
|
||||
# hook. Perhaps we can make this easier now, and eliminate the easy
|
||||
# install, with a quick check (and a new, empty, "hook" as a sentinel)
|
||||
unless (-l "$repo.git/hooks/gitolite-hooked") {
|
||||
ln_sf("$GL_ADMINDIR/hooks/common", "*", "$repo.git/hooks");
|
||||
# in case of package install, GL_ADMINDIR is no longer the top cop;
|
||||
# override with the package hooks
|
||||
ln_sf("$GL_PACKAGE_HOOKS/common", "*", "$repo.git/hooks") if $GL_PACKAGE_HOOKS;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -454,56 +470,60 @@ for my $repo (keys %repo_config) {
|
|||
|
||||
wrap_chdir("$repo_base_abs");
|
||||
|
||||
# daemons first...
|
||||
for my $repo (sort keys %repos) {
|
||||
next unless $repo =~ $REPONAME_PATT;
|
||||
my $export_ok = "$repo.git/git-daemon-export-ok";
|
||||
if ($repos{$repo}{'R'}{'daemon'}) {
|
||||
system("touch $export_ok");
|
||||
} else {
|
||||
unlink($export_ok);
|
||||
}
|
||||
}
|
||||
|
||||
my %projlist = ();
|
||||
# ...then gitwebs
|
||||
for my $repo (sort keys %repos) {
|
||||
next unless $repo =~ $REPONAME_PATT;
|
||||
my $desc_file = "$repo.git/description";
|
||||
# note: having a description also counts as enabling gitweb
|
||||
if ($repos{$repo}{'R'}{'gitweb'} or $desc{"$repo.git"}) {
|
||||
$projlist{"$repo.git"} = 1;
|
||||
# add the description file; no messages to user or error checking :)
|
||||
$desc{"$repo.git"} and open(DESC, ">", $desc_file) and print DESC $desc{"$repo.git"} . "\n" and close DESC;
|
||||
if ($owner{"$repo.git"}) {
|
||||
# set the repository owner
|
||||
system("git", "--git-dir=$repo.git", "config", "gitweb.owner", $owner{"$repo.git"});
|
||||
unless ($GL_NO_DAEMON_NO_GITWEB) {
|
||||
# daemons first...
|
||||
for my $repo (sort keys %repos) {
|
||||
next unless $repo =~ $REPONAME_PATT;
|
||||
next if $repo =~ m(^\@|EXTCMD/); # these are not real repos
|
||||
my $export_ok = "$repo.git/git-daemon-export-ok";
|
||||
if ($repos{$repo}{'R'}{'daemon'}) {
|
||||
system("touch $export_ok");
|
||||
} else {
|
||||
unlink($export_ok);
|
||||
}
|
||||
}
|
||||
|
||||
my %projlist = ();
|
||||
# ...then gitwebs
|
||||
for my $repo (sort keys %repos) {
|
||||
next unless $repo =~ $REPONAME_PATT;
|
||||
next if $repo =~ m(^\@|EXTCMD/); # these are not real repos
|
||||
my $desc_file = "$repo.git/description";
|
||||
# note: having a description also counts as enabling gitweb
|
||||
if ($repos{$repo}{'R'}{'gitweb'} or $desc{"$repo.git"}) {
|
||||
$projlist{"$repo.git"} = 1;
|
||||
# add the description file; no messages to user or error checking :)
|
||||
$desc{"$repo.git"} and open(DESC, ">", $desc_file) and print DESC $desc{"$repo.git"} . "\n" and close DESC;
|
||||
if ($owner{"$repo.git"}) {
|
||||
# set the repository owner
|
||||
system("git", "--git-dir=$repo.git", "config", "gitweb.owner", $owner{"$repo.git"});
|
||||
} else {
|
||||
# remove the repository owner setting
|
||||
system("git --git-dir=$repo.git config --unset-all gitweb.owner 2>/dev/null");
|
||||
}
|
||||
} else {
|
||||
# delete the description file; no messages to user or error checking :)
|
||||
unlink $desc_file;
|
||||
# remove the repository owner setting
|
||||
system("git --git-dir=$repo.git config --unset-all gitweb.owner 2>/dev/null");
|
||||
}
|
||||
} else {
|
||||
# delete the description file; no messages to user or error checking :)
|
||||
unlink $desc_file;
|
||||
# remove the repository owner setting
|
||||
system("git --git-dir=$repo.git config --unset-all gitweb.owner 2>/dev/null");
|
||||
|
||||
# unless there are other gitweb.* keys set, remove the section to keep the
|
||||
# config file clean
|
||||
my $keys = `git --git-dir=$repo.git config --get-regexp '^gitweb\\.' 2>/dev/null`;
|
||||
if (length($keys) == 0) {
|
||||
system("git --git-dir=$repo.git config --remove-section gitweb 2>/dev/null");
|
||||
}
|
||||
}
|
||||
|
||||
# unless there are other gitweb.* keys set, remove the section to keep the
|
||||
# config file clean
|
||||
my $keys = `git --git-dir=$repo.git config --get-regexp '^gitweb\\.' 2>/dev/null`;
|
||||
if (length($keys) == 0) {
|
||||
system("git --git-dir=$repo.git config --remove-section gitweb 2>/dev/null");
|
||||
# update the project list
|
||||
my $projlist_fh = wrap_open( ">", $PROJECTS_LIST);
|
||||
for my $proj (sort keys %projlist) {
|
||||
print $projlist_fh "$proj\n";
|
||||
}
|
||||
close $projlist_fh;
|
||||
}
|
||||
|
||||
# update the project list
|
||||
my $projlist_fh = wrap_open( ">", $PROJECTS_LIST);
|
||||
for my $proj (sort keys %projlist) {
|
||||
print $projlist_fh "$proj\n";
|
||||
}
|
||||
close $projlist_fh;
|
||||
|
||||
# ----------------------------------------------------------------------------
|
||||
# "compile" ssh authorized_keys
|
||||
# ----------------------------------------------------------------------------
|
||||
|
@ -560,10 +580,16 @@ for my $pubkey (`find . -type f`)
|
|||
print $newkeys_fh $pubkey_content;
|
||||
}
|
||||
# lint check 3; a little more severe than the first two I guess...
|
||||
for my $user (sort keys %user_list)
|
||||
{
|
||||
next if $user =~ /^(gitweb|daemon|\@all|~\$creator|\$readers|\$writers)$/ or $user_list{$user} eq 'has pubkey';
|
||||
print STDERR "$WARN user $user in config, but has no pubkey!\n";
|
||||
my @no_pubkey =
|
||||
grep { $_ !~ /^(gitweb|daemon|\@.*|~\$creator|\$readers|\$writers)$/ }
|
||||
grep { $user_list{$_} ne 'has pubkey' }
|
||||
keys %user_list;
|
||||
if (@no_pubkey > 10) {
|
||||
print STDERR "$WARN You have " . scalar(@no_pubkey) . " users WITHOUT pubkeys...!\n";
|
||||
} elsif (@no_pubkey) {
|
||||
print STDERR "$WARN the following users have no pubkeys:\n", join(",", sort @no_pubkey), "\n";
|
||||
}
|
||||
}
|
||||
|
||||
print $newkeys_fh "# gitolite end\n";
|
||||
|
|
Loading…
Add table
Reference in a new issue