custom perm categories in setperms (WARNING: PLEASE READ FULL COMMIT MESSAGE)

THE COMPILED CONFIG FILE FORMAT CHANGES WITH THIS VERSION.  PLEASE DO
NOT MIX VERSIONS OR DOWNGRADE.  Upgrading using normal gitolite upgrade
means should be fine, though.

Originally, we only allowed "R" and "RW" as categories of users supplied
to the `setperms` command.  These map respectively to "READERS" and
"WRITERS" in the access rules.

Now:

  - we prefer READERS instead of R and WRITERS instead of RW
  - we allow the admin to define other categories as she wishes
    (example: MANAGERS, TESTERS, etc).  These do not have abbreviations,
    however, so they must be supplied in full.

PLEASE, *PLEASE*, read the section in doc/wildcard-repositories.mkd for
more info.  This is a VERY powerful feature and if you're not careful
you could mess up the ACLs nicely.

Backward compat note: you can continue to use the "R" and "RW"
categories when running the "setperms" command, and gitolite will
internally convert them to READERS and WRITERS categories.

----

implementation notes:

  - new RC var called GL_WILDREPOS_PERM_CATS that is a space-sep list of
    the allowed categories in a gl-perms file; defaults to "R RW" if not
    specified

  - wild_repo_rights no longer returns $c, $r, $wC, where $r = $user if
    "R $user", $r = '@all' if "R @all", and similarly with $w and "RW".

    Instead it returns $c and a new hash that effectively gives the same
    info, but expanded to include any other valid categories (listed in
    GL_WILDREPOS_PERM_CATS)

  - consequently, the arguments that parse_acl takes also change the
    same way

  - (side note: R and RW are quietly converted to READERS and WRITERS;
    however, new categories that you define yourself do not have
    abbreviations)

  - setperms validates perms to make sure only allowed categories are
    used; however even if someone changed them behind the scenes,
    wild_repo_rights will also check.  This is necessary in case the
    admin tightened up GL_WILDREPOS_PERM_CATS after someone had already
    setperms-d his repos.

  - as a bonus, we eliminate all the post-Dumper shenanigans, at least
    for READERS and WRITERS.  Those two now look, to the compile script,
    just like any other usernames.
This commit is contained in:
Sitaram Chamarty 2010-11-06 10:46:17 +05:30
parent 4df32c3ff0
commit 047790140a
16 changed files with 383 additions and 84 deletions

View file

@ -239,6 +239,28 @@ $GL_WILDREPOS = 0;
# $GL_WILDREPOS_DEFPERMS = 'R @all'; # $GL_WILDREPOS_DEFPERMS = 'R @all';
# --------------------------------------
# WILDREPOS PERMS CATEGORIES
# Originally, we only allowed "R" and "RW" in the setperms command. Now we
# allow the admin to define other categories as she wishes (example: MANAGERS,
# TESTERS, etc).
# This variable is a space-sep list of the allowed categories.
# PLEASE, *PLEASE*, read the section in doc/wildcard-repositories.mkd for
# caveats and warnings. This is a VERY powerful feature and if you're not
# careful you could mess up the ACLs nicely.
# this is the internal default if you don't set it (like if you didn't update
# your ~/.gitolite.rc with new variables when you upgraded gitolite):
$GL_WILDREPOS_PERM_CATS = "READERS WRITERS";
# you can use your own categories in addition to the standard ones; I suggest
# you include READERS and WRITERS for backward compat though:
# $GL_WILDREPOS_PERM_CATS = "READERS WRITERS MANAGERS";
# $GL_WILDREPOS_PERM_CATS = "READERS WRITERS MANAGERS TESTERS";
# -------------------------------------- # --------------------------------------
# HOOK CHAINING # HOOK CHAINING

View file

@ -4,9 +4,9 @@
This feature may be somewhat "brittle" in terms of security. Creating This feature may be somewhat "brittle" in terms of security. Creating
repositories based on wild cards, giving "ownership" to the specific user who repositories based on wild cards, giving "ownership" to the specific user who
created it, allowing him/her to hand out R and RW permissions to other users created it, allowing him/her to hand out permissions to other users to
to collaborate, all these are possible. And any of these could have a bug in collaborate, all these are possible. And any of these could have a bug in it.
it. I haven't found any yet, but that doesn't mean there aren't any. I haven't found any yet, but that doesn't mean there aren't any.
---- ----
@ -17,9 +17,12 @@ In this document:
* <a href="#_examples_of_wildcard_repos">examples of wildcard repos</a> * <a href="#_examples_of_wildcard_repos">examples of wildcard repos</a>
* <a href="#_wildcard_repos_with_creator_name_in_them">wildcard repos with creator name in them</a> * <a href="#_wildcard_repos_with_creator_name_in_them">wildcard repos with creator name in them</a>
* <a href="#_wildcard_repos_without_creator_name_in_them">wildcard repos without creator name in them</a> * <a href="#_wildcard_repos_without_creator_name_in_them">wildcard repos without creator name in them</a>
* <a href="#_side_note_valid_regexes">side-note: valid regexes</a>
* <a href="#_side_note_line_anchored_regexes">side-note: line-anchored regexes</a> * <a href="#_side_note_line_anchored_regexes">side-note: line-anchored regexes</a>
* <a href="#_contrast_with_refexes">contrast with refexes</a> * <a href="#_contrast_with_refexes">contrast with refexes</a>
* <a href="#_handing_out_rights_to_wildcard_matched_repos">handing out rights to wildcard-matched repos</a> * <a href="#_handing_out_rights_to_wildcard_matched_repos">handing out rights to wildcard-matched repos</a>
* <a href="#_admin_adding_other_categories_than_READERS_and_WRITERS">(admin) adding other categories than READERS and WRITERS</a>
* <a href="#_IMPORTANT_WARNING_ABOUT_THIS_FEATURE_">**IMPORTANT WARNING ABOUT THIS FEATURE**</a>
* <a href="#_setting_a_gitweb_description_for_a_wildcard_matched_repo">setting a gitweb description for a wildcard-matched repo</a> * <a href="#_setting_a_gitweb_description_for_a_wildcard_matched_repo">setting a gitweb description for a wildcard-matched repo</a>
* <a href="#_reporting">reporting</a> * <a href="#_reporting">reporting</a>
* <a href="#_how_it_actually_works">how it actually works</a> * <a href="#_how_it_actually_works">how it actually works</a>
@ -120,7 +123,7 @@ and have a TA create the repos in advance.
In either case, they could then use the `setperms` feature to specify which In either case, they could then use the `setperms` feature to specify which
users are "READERS" and which are "WRITERS". See later for details. users are "READERS" and which are "WRITERS". See later for details.
<a name="_side_note_line_anchored_regexes"></a> <a name="_side_note_valid_regexes"></a>
### side-note: valid regexes ### side-note: valid regexes
@ -131,6 +134,8 @@ look like a regex to gitolite. Use `foo/..*` if you want that.
Also, `..*` by itself is not considered a valid repo pattern. Try Also, `..*` by itself is not considered a valid repo pattern. Try
`[a-zA-Z0-9].*`. `[a-zA-Z0-9].*`.
<a name="_side_note_line_anchored_regexes"></a>
### side-note: line-anchored regexes ### side-note: line-anchored regexes
A regex like A regex like
@ -170,40 +175,80 @@ The use case is that, although our toy example has only 3 students, in reality
there will be a few dozen, but each assignment will be worked on only by a there will be a few dozen, but each assignment will be worked on only by a
handful from among those. This allows the creator to take ad hoc sets of handful from among those. This allows the creator to take ad hoc sets of
users from among the actual users in the system, and place them into one of users from among the actual users in the system, and place them into one of
two categories (whose permissions are, in this example, R and RW two categories (in this example, READERS and WRITERS respectively). In theory
respectively). In theory you could do the same thing by creating lots of you could do the same thing by creating lots of little "assignment-NN" groups
little "assignment-NN" groups in the config file but that may be a little too in the config file but that may be a little too cumbersome for non-secret
cumbersome for non-secret environments. environments.
Create a small text file that contains the permissions you desire: Create a small text file that contains the permissions you desire:
$ cat > myperms $ cat > myperms
R u5 READERS u5
RW u6 WRITERS u6
(hit ctrl-d here) (hit ctrl-d here)
...and use the new "setperms" command to set permissions for your repo: ...and use the new "setperms" command to set permissions for your repo:
$ ssh git@server setperms assignments/u4/a12 < myperms $ ssh git@server setperms assignments/u4/a12 < myperms
New perms are: New perms are:
R u5 READERS u5
RW u6 WRITERS u6
'setperms' will helpfully print what the new permissions are but you can also 'setperms' will helpfully print what the new permissions are but you can also
use 'getperms' to check: use 'getperms' to check:
$ ssh git@server getperms assignments/u4/a12 $ ssh git@server getperms assignments/u4/a12
R u5 READERS u5
RW u6 WRITERS u6
The following points are important: The following points are important:
* note the syntax of the commands; it's not a "git" command, and there's no * note the syntax of the commands; it's not a "git" command, and there's no
`:` like in a repo URL. The first space-separated word is R or RW, and `:` like in a repo URL. The first space-separated word is READERS or
the rest are simple usernames. WRITERS, and the rest are simple usernames.
* whoever you specify as "R" will match the special user READERS. "RW" will <a name="_admin_adding_other_categories_than_READERS_and_WRITERS"></a>
match WRITERS.
### (admin) adding other categories than READERS and WRITERS
Let's say your needs are more complex and you need more categories of users.
For example, you might like to have a setup where only a tester can update
tags, and only a manager can delete branches:
repo foo/..*
C = u1
RW refs/tags/ = TESTERS
- refs/tags/ = @all
RW+ = WRITERS
RW = INTERNS
R = READERS
RW+D = MANAGERS
As you can see, someone pre-creates the repo and assigns rights to various
people, say by sending something like this to `setperms`:
READERS wally
WRITERS dilbert alice
MANAGERS phb
INTERNS ashok
TESTERS ashok
You can enable do this by setting the `GL_WILDREPOS_PERM_CATS` variable in the
rc file. The example rc file (`conf/example.gitolite.rc`) explains how to do
this, with sample code.
<a name="_IMPORTANT_WARNING_ABOUT_THIS_FEATURE_"></a>
#### **IMPORTANT WARNING ABOUT THIS FEATURE**
Please make sure that none of the category names conflict with any of the
**usernames** in the system. For example, if you have a user called "foo",
make sure you do not include "foo" as a valid category in
`$GL_WILDREPOS_PERM_CATS`.
You can keep things sane by using UPPERCASE names for categories, while
keeping all your usernames lowercase; then you don't have to worry about this
problem.
<a name="_setting_a_gitweb_description_for_a_wildcard_matched_repo"></a> <a name="_setting_a_gitweb_description_for_a_wildcard_matched_repo"></a>
@ -253,12 +298,9 @@ Now find a repo pattern that matches the actual reponame being pushed -- this
tells you which set of rules to apply. There can be multiple matches; if so, tells you which set of rules to apply. There can be multiple matches; if so,
they will all be applied in the sequence they appear in the config file. they will all be applied in the sequence they appear in the config file.
If the invoking user has been given "RW" permissions using `setperms`, all If the invoking user has been put in the "WRITERS" category using `setperms`, all
occurrences of the word WRITERS are replaced by the invoking username. permissions for the the user WRITERS are given to the invoking username (and
Otherwise -- and this includes the "new repo" case, since you couldn't have similarly for READERS).
run `setperms` on a non-existant repo -- they are replaced by "NOBODY".
(The same thing is done with "R" access and the word "READERS".)
At this point we have an effective ruleset, and the normal access rules (R, At this point we have an effective ruleset, and the normal access rules (R,
RW, etc) apply, with the addition that the invoking user needs "C" access to RW, etc) apply, with the addition that the invoking user needs "C" access to
@ -269,8 +311,7 @@ be able to create a repo.
> situations, as you can see in our example). > situations, as you can see in our example).
Assuming user "u4" trying to push-create a new repo called Assuming user "u4" trying to push-create a new repo called
`assignments/u4/a23`, this is what the effective ruleset looks like (we're `assignments/u4/a23`, this is what the effective ruleset looks like:
ignoring the "NOBODY" business):
repo assignments/u4/a23 repo assignments/u4/a23
C = @students C = @students
@ -278,8 +319,8 @@ ignoring the "NOBODY" business):
RW = @TAs RW = @TAs
R = @prof R = @prof
If u4 gives "RW" perms to u5 using `setperms`, and u5 tries to access that If u4 puts u5 in the "WRITERS" category using `setperms`, and u5 tries to
repo, the ruleset looks like: access that repo, the ruleset looks like:
repo assignments/u4/a23 repo assignments/u4/a23
C = @students C = @students

View file

@ -40,12 +40,12 @@ our $REPOPATT_PATT=qr(^\@?[0-9a-zA-Z[][\\^.$|()[\]*+?{}0-9a-zA-Z._\@/-]*$);
our $ADC_CMD_ARGS_PATT=qr(^[0-9a-zA-Z._\@/+:-]*$); our $ADC_CMD_ARGS_PATT=qr(^[0-9a-zA-Z._\@/+:-]*$);
# these come from the RC file # these come from the RC file
our ($REPO_UMASK, $GL_WILDREPOS, $GL_PACKAGE_CONF, $GL_PACKAGE_HOOKS, $REPO_BASE, $GL_CONF_COMPILED, $GL_BIG_CONFIG, $GL_PERFLOGT, $PROJECTS_LIST, $GL_ALL_INCLUDES_SPECIAL, $GL_SITE_INFO, $GL_GET_MEMBERSHIPS_PGM); our ($REPO_UMASK, $GL_WILDREPOS, $GL_PACKAGE_CONF, $GL_PACKAGE_HOOKS, $REPO_BASE, $GL_CONF_COMPILED, $GL_BIG_CONFIG, $GL_PERFLOGT, $PROJECTS_LIST, $GL_ALL_INCLUDES_SPECIAL, $GL_SITE_INFO, $GL_GET_MEMBERSHIPS_PGM, $GL_WILDREPOS_PERM_CATS);
our %repos; our %repos;
our %groups; our %groups;
our %repo_config; our %repo_config;
our $data_version; our $data_version;
our $current_data_version = '1.5'; our $current_data_version = '1.6';
# ---------------------------------------------------------------------------- # ----------------------------------------------------------------------------
# convenience subs # convenience subs
@ -316,6 +316,8 @@ sub new_repo
# "who created this repo", "am I on the R list", and "am I on the RW list"? # "who created this repo", "am I on the R list", and "am I on the RW list"?
sub wild_repo_rights sub wild_repo_rights
{ {
# set default categories
$GL_WILDREPOS_PERM_CATS ||= "READERS WRITERS";
my ($repo, $user) = @_; my ($repo, $user) = @_;
# pull in basic group info # pull in basic group info
unless ($cache_filled) { unless ($cache_filled) {
@ -337,26 +339,46 @@ sub new_repo
my $fh = wrap_open("<", "$ENV{GL_REPO_BASE_ABS}/$repo.git/gl-creater"); my $fh = wrap_open("<", "$ENV{GL_REPO_BASE_ABS}/$repo.git/gl-creater");
chomp($c = <$fh>); chomp($c = <$fh>);
} }
# $user's R and W rights
my ($r, $w); $r = ''; $w = ''; # now get the permission categories (used to be just R and RW. Now
# there can be any others that the admin defines in the RC file via
# $GL_WILDREPOS_PERM_CATS variable (space separated list)
# For instance, if the user is "foo", and gl-perms has "R bar", "RW
# foo baz", and "TESTERS frob @all", this hash will then contain
# "WRITERS=>foo" and "TESTERS=>@all"
my %perm_cats;
if ($user and -f "$ENV{GL_REPO_BASE_ABS}/$repo.git/gl-perms") { if ($user and -f "$ENV{GL_REPO_BASE_ABS}/$repo.git/gl-perms") {
my $fh = wrap_open("<", "$ENV{GL_REPO_BASE_ABS}/$repo.git/gl-perms"); my $fh = wrap_open("<", "$ENV{GL_REPO_BASE_ABS}/$repo.git/gl-perms");
my $perms = join ("", <$fh>); my $perms = join ("", <$fh>);
# $perms is say "R alice @foo @bar\nRW bob @baz" (the entire gl-perms # discard comments
$perms =~ s/#.*//g;
# convert R and RW to the actual category names in the config file
$perms =~ s/^\s*R /READERS /mg;
$perms =~ s/^\s*RW /WRITERS /mg;
# $perms is say "READERS alice @foo @bar\nRW bob @baz" (the entire gl-perms
# file). We replace each @foo with $user if $cached_groups{'@foo'}{$user} # file). We replace each @foo with $user if $cached_groups{'@foo'}{$user}
# exists (i.e., $user is a member of @foo) # exists (i.e., $user is a member of @foo)
for my $g ($perms =~ /\s(\@\S+)/g) { for my $g ($perms =~ /\s(\@\S+)/g) {
$perms =~ s/ $g(?!\S)/ $user/ if $cached_groups{$g}{$user}; $perms =~ s/ $g(?!\S)/ $user/ if $cached_groups{$g}{$user};
} }
# now setup the perm_cats hash to be returned
if ($perms) { if ($perms) {
$r ='@all' if $perms =~ /^\s*R(?=\s).*\s\@all(\s|$)/m; # let's say our user is "foo". gl-perms has "CAT bar @all",
$r = $user if $perms =~ /^\s*R(?=\s).*\s$user(\s|$)/m; # you add CAT => @all to the hash. similarly, if gl-perms has
$w ='@all' if $perms =~ /^\s*RW(?=\s).*\s\@all(\s|$)/m; # "DOG bar foo baz", you add DOG => foo to the hash. And
$w = $user if $perms =~ /^\s*RW(?=\s).*\s$user(\s|$)/m; # since specific perms must override @all, we do @all first.
$perm_cats{$1} = '@all' while ($perms =~ /^\h*(\S+)(?=\h).*\h\@all(\h|$)/mg);
$perm_cats{$1} = $user while ($perms =~ /^\h*(\S+)(?=\h).*\h$user(\h|$)/mg);
# validate the categories being sent back
for (sort keys %perm_cats) {
die "invalid permission category $_\n" unless $GL_WILDREPOS_PERM_CATS =~ /(^|\s)$_(\s|$)/;
}
} }
} }
return ($c, $r, $w); return ($c, %perm_cats);
} }
} }
@ -367,16 +389,30 @@ sub new_repo
sub get_set_perms sub get_set_perms
{ {
my($repo, $verb, $user) = @_; my($repo, $verb, $user) = @_;
# set default categories
$GL_WILDREPOS_PERM_CATS ||= "READERS WRITERS";
my ($creator, $dummy, $dummy2) = &wild_repo_rights($repo, ""); my ($creator, $dummy, $dummy2) = &wild_repo_rights($repo, "");
die "$repo doesnt exist or is not yours\n" unless $user eq $creator; die "$repo doesnt exist or is not yours\n" unless $user eq $creator;
wrap_chdir("$ENV{GL_REPO_BASE_ABS}"); wrap_chdir("$ENV{GL_REPO_BASE_ABS}");
wrap_chdir("$repo.git"); wrap_chdir("$repo.git");
if ($verb eq 'getperms') { if ($verb eq 'getperms') {
system("cat", "gl-perms") if -f "gl-perms"; return unless -f "gl-perms";
my $perms = `cat gl-perms`;
# convert R and RW to the actual category names in the config file
$perms =~ s/^\s*R /READERS /mg;
$perms =~ s/^\s*RW /WRITERS /mg;
print $perms;
} else { } else {
system("cat > gl-perms"); system("cat > gl-perms");
my $perms = `cat gl-perms`;
# convert R and RW to the actual category names in the config file
$perms =~ s/^\s*R /READERS /mg;
$perms =~ s/^\s*RW /WRITERS /mg;
for my $g ($perms =~ /^\s*(\S+)/g) {
die "invalid permission category $g\n" unless $GL_WILDREPOS_PERM_CATS =~ /(^|\s)$g(\s|$)/;
}
print "New perms are:\n"; print "New perms are:\n";
system("cat", "gl-perms"); print $perms;
# gitweb and daemon # gitweb and daemon
setup_daemon_access($repo); setup_daemon_access($repo);
@ -492,8 +528,10 @@ sub parse_acl
# IMPLEMENTATION NOTE: a wee bit of this is duplicated in the update hook; # 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 # please update that also if the interface or the env vars change
my ($GL_CONF_COMPILED, $repo, $c, $r, $w) = @_; my ($GL_CONF_COMPILED, $repo, $c, %perm_cats) = @_;
$c = $r = $w = "NOBODY" unless $GL_WILDREPOS; my $perm_cats_sig = '';
map { $perm_cats_sig .= "$_.$perm_cats{$_}," } sort keys %perm_cats;
$c = "NOBODY" unless $GL_WILDREPOS;
# set up the variables for a parse to interpolate stuff from the dumped # set up the variables for a parse to interpolate stuff from the dumped
# hash (remember the selective conversion of single to double quotes?). # hash (remember the selective conversion of single to double quotes?).
@ -504,19 +542,17 @@ sub parse_acl
# parse without any special code # parse without any special code
our $creator = $ENV{GL_CREATOR} = $c || $ENV{GL_CREATOR} || "NOBODY"; our $creator = $ENV{GL_CREATOR} = $c || $ENV{GL_CREATOR} || "NOBODY";
our $readers = $ENV{GL_READERS} = $r || $ENV{GL_READERS} || "NOBODY";
our $writers = $ENV{GL_WRITERS} = $w || $ENV{GL_WRITERS} || "NOBODY";
our $gl_user = $ENV{GL_USER}; our $gl_user = $ENV{GL_USER};
# these need to persist across calls to this function, so "our" # these need to persist across calls to this function, so "our"
our $saved_crwu; our $saved_crwu;
our (%saved_repos, %saved_groups); our (%saved_repos, %saved_groups);
if ($saved_crwu and $saved_crwu eq "$creator,$readers,$writers,$gl_user") { if ($saved_crwu and $saved_crwu eq "$creator,$perm_cats_sig,$gl_user") {
%repos = %saved_repos; %groups = %saved_groups; %repos = %saved_repos; %groups = %saved_groups;
} else { } else {
die "parse $GL_CONF_COMPILED failed: " . ($! or $@) unless do $GL_CONF_COMPILED; die "parse $GL_CONF_COMPILED failed: " . ($! or $@) unless do $GL_CONF_COMPILED;
$saved_crwu = "$creator,$readers,$writers,$gl_user"; $saved_crwu = "$creator,$perm_cats_sig,$gl_user";
%saved_repos = %repos; %saved_groups = %groups; %saved_repos = %repos; %saved_groups = %groups;
} }
unless (defined($data_version) and $data_version eq $current_data_version) { unless (defined($data_version) and $data_version eq $current_data_version) {
@ -547,8 +583,8 @@ sub parse_acl
$repos{$dr}{NAME_LIMITS} = 1 if $repos{$r}{NAME_LIMITS}; $repos{$dr}{NAME_LIMITS} = 1 if $repos{$r}{NAME_LIMITS};
$repo_config{$dr} = $repo_config{$r} if $repo_config{$r}; $repo_config{$dr} = $repo_config{$r} if $repo_config{$r};
for my $u ('@all', "$gl_user - wild", @user_plus) { for my $u ('@all', "$gl_user - wild", @user_plus, keys %perm_cats) {
my $du = $gl_user; $du = '@all' if $u eq '@all'; my $du = $gl_user; $du = '@all' if $u eq '@all' or ($perm_cats{$u} || '') eq '@all';
$repos{$dr}{C}{$du} = 1 if $repos{$r}{C}{$u}; $repos{$dr}{C}{$du} = 1 if $repos{$r}{C}{$u};
$repos{$dr}{R}{$du} = 1 if $repos{$r}{R}{$u}; $repos{$dr}{R}{$du} = 1 if $repos{$r}{R}{$u};
$repos{$dr}{W}{$du} = 1 if $repos{$r}{W}{$u}; $repos{$dr}{W}{$du} = 1 if $repos{$r}{W}{$u};
@ -599,7 +635,7 @@ sub report_basic
# rights for some other user this way # rights for some other user this way
local $ENV{GL_USER} = $user; local $ENV{GL_USER} = $user;
&parse_acl($GL_CONF_COMPILED, "", "CREATOR", "READERS", "WRITERS"); &parse_acl($GL_CONF_COMPILED, "", "CREATOR");
# send back some useful info if no command was given # send back some useful info if no command was given
&report_version($GL_ADMINDIR, $user); &report_version($GL_ADMINDIR, $user);
@ -610,10 +646,10 @@ sub report_basic
# if $GL_BIG_CONFIG is on, limit the number of output lines to 20 # if $GL_BIG_CONFIG is on, limit the number of output lines to 20
next if $GL_BIG_CONFIG and $count++ >= 20; next if $GL_BIG_CONFIG and $count++ >= 20;
if ($r =~ $REPONAME_PATT and $r !~ /\bCREAT[EO]R\b/) { if ($r =~ $REPONAME_PATT and $r !~ /\bCREAT[EO]R\b/) {
&parse_acl($GL_CONF_COMPILED, $r, "NOBODY", "NOBODY", "NOBODY"); &parse_acl($GL_CONF_COMPILED, $r, "NOBODY");
} else { } else {
$r =~ s/\bCREAT[EO]R\b/$user/g; $r =~ s/\bCREAT[EO]R\b/$user/g;
&parse_acl($GL_CONF_COMPILED, $r, $ENV{GL_USER}, "NOBODY", "NOBODY"); &parse_acl($GL_CONF_COMPILED, $r, $ENV{GL_USER});
} }
# @all repos; meaning of read/write flags: # @all repos; meaning of read/write flags:
# @R => @all users are allowed access to this repo # @R => @all users are allowed access to this repo
@ -692,13 +728,16 @@ sub expand_wild
my $wild = ''; my $wild = '';
my $exists = -d "$ENV{GL_REPO_BASE_ABS}/$repo.git"; my $exists = -d "$ENV{GL_REPO_BASE_ABS}/$repo.git";
if ($exists) { if ($exists) {
# the list of permission categories within gl-perms that this user is a member
# of, or that specify @all as a member. See comments in
# "wild_repo_rights" sub for nuances.
my (%perm_cats);
# these will be empty if it's not a wildcard repo anyway # these will be empty if it's not a wildcard repo anyway
my ($read, $write); ($creator, %perm_cats) = &wild_repo_rights($repo, $ENV{GL_USER});
($creator, $read, $write) = &wild_repo_rights($repo, $ENV{GL_USER});
# get access list with these substitutions # get access list with these substitutions
$wild = &parse_acl($GL_CONF_COMPILED, $repo, $creator || "NOBODY", $read || "NOBODY", $write || "NOBODY"); $wild = &parse_acl($GL_CONF_COMPILED, $repo, $creator || "NOBODY", %perm_cats);
} else { } else {
$wild = &parse_acl($GL_CONF_COMPILED, $repo, $ENV{GL_USER}, "NOBODY", "NOBODY"); $wild = &parse_acl($GL_CONF_COMPILED, $repo, $ENV{GL_USER});
} }
if ($exists) { if ($exists) {
@ -840,7 +879,7 @@ sub setup_authkeys
# lint check 3; a little more severe than the first two I guess... # lint check 3; a little more severe than the first two I guess...
{ {
my @no_pubkey = my @no_pubkey =
grep { $_ !~ /^(gitweb|daemon|\@.*|~\$creator|\$readers|\$writers)$/ } grep { $_ !~ /^(gitweb|daemon|\@.*|~\$creator)$/ }
grep { $user_list_p->{$_} ne 'has pubkey' } grep { $user_list_p->{$_} ne 'has pubkey' }
keys %{$user_list_p}; keys %{$user_list_p};
if (@no_pubkey > 10) { if (@no_pubkey > 10) {

View file

@ -217,8 +217,6 @@ sub parse_conf_line
do { die "$ABRT bad username $_\n" unless $_ =~ $USERNAME_PATT } for @users; do { die "$ABRT bad username $_\n" unless $_ =~ $USERNAME_PATT } for @users;
s/\bCREAT[EO]R\b/~\$creator/g for @users; s/\bCREAT[EO]R\b/~\$creator/g for @users;
s/\bREADERS\b/\$readers/g for @users;
s/\bWRITERS\b/\$writers/g for @users;
# ok, we can finally populate the %repos hash # ok, we can finally populate the %repos hash
for my $repo (@{ $repos_p }) # each repo in the current stanza for my $repo (@{ $repos_p }) # each repo in the current stanza
@ -266,7 +264,7 @@ sub parse_conf_line
# that do *not* use NAME limits. Setting a flag that # that do *not* use NAME limits. Setting a flag that
# can be checked right away will help us do that # can be checked right away will help us do that
$repos{$repo}{NAME_LIMITS} = 1 if $ref =~ /^NAME\//; $repos{$repo}{NAME_LIMITS} = 1 if $ref =~ /^NAME\//;
my $p_user = $user; $p_user =~ s/(creator|readers|writers)$/$1 - wild/; my $p_user = $user; $p_user =~ s/creator$/creator - wild/;
push @{ $repos{$repo}{$p_user} }, [ $rule_seq++, $ref, $perms ] push @{ $repos{$repo}{$p_user} }, [ $rule_seq++, $ref, $perms ]
unless $rurp_seen{$repo}{$p_user}{$ref}{$perms}++; unless $rurp_seen{$repo}{$p_user}{$ref}{$perms}++;
} }
@ -402,15 +400,14 @@ my $data_version = $current_data_version;
print $compiled_fh Data::Dumper->Dump([$data_version], [qw(*data_version)]); print $compiled_fh Data::Dumper->Dump([$data_version], [qw(*data_version)]);
my $dumped_data = Data::Dumper->Dump([\%repos], [qw(*repos)]); my $dumped_data = Data::Dumper->Dump([\%repos], [qw(*repos)]);
$dumped_data .= Data::Dumper->Dump([\%repo_config], [qw(*repo_config)]) if %repo_config; $dumped_data .= Data::Dumper->Dump([\%repo_config], [qw(*repo_config)]) if %repo_config;
# the dump uses single quotes, but we convert any strings containing $creator, # the dump uses single quotes, but we convert any strings containing $creator
# $readers, $writers, to double quoted strings. A wee bit sneaky, but not too # and $gl_user to double quoted strings. A bit sneaky, but not too much...
# much... $dumped_data =~ s/'(?=[^']*\$(?:creator|gl_user))~?(.*?)'/"$1"/g;
$dumped_data =~ s/'(?=[^']*\$(?:creator|readers|writers|gl_user))~?(.*?)'/"$1"/g;
print $compiled_fh $dumped_data; print $compiled_fh $dumped_data;
if (%groups) { if (%groups) {
$dumped_data = Data::Dumper->Dump([\%groups], [qw(*groups)]); $dumped_data = Data::Dumper->Dump([\%groups], [qw(*groups)]);
$dumped_data =~ s/\bCREAT[EO]R\b/\$creator/g; $dumped_data =~ s/\bCREAT[EO]R\b/\$creator/g;
$dumped_data =~ s/'(?=[^']*\$(?:creator|readers|writers|gl_user))~?(.*?)'/"$1"/g; $dumped_data =~ s/'(?=[^']*\$(?:creator|gl_user))~?(.*?)'/"$1"/g;
print $compiled_fh $dumped_data; print $compiled_fh $dumped_data;
} }
close $compiled_fh or die "$ABRT close compiled-conf failed: $!\n"; close $compiled_fh or die "$ABRT close compiled-conf failed: $!\n";

View file

@ -1,4 +1,4 @@
$data_version = '1.5'; $data_version = '1.6';
%repos = ( %repos = (
'aa' => { 'aa' => {
'R' => { 'R' => {

View file

@ -1,4 +1,4 @@
$data_version = '1.5'; $data_version = '1.6';
%repos = ( %repos = (
'aa' => { 'aa' => {
'R' => { 'R' => {

View file

@ -1,4 +1,4 @@
$data_version = '1.5'; $data_version = '1.6';
%repos = ( %repos = (
'@g1' => { '@g1' => {
'@g1' => [ '@g1' => [

View file

@ -1,4 +1,4 @@
$data_version = '1.5'; $data_version = '1.6';
%repos = ( %repos = (
'aa' => { 'aa' => {
'R' => { 'R' => {

View file

@ -1,4 +1,4 @@
$data_version = '1.5'; $data_version = '1.6';
%repos = ( %repos = (
'aa' => { 'aa' => {
'R' => { 'R' => {

View file

@ -1,4 +1,4 @@
$data_version = '1.5'; $data_version = '1.6';
%repos = ( %repos = (
'aa' => { 'aa' => {
'@g1' => [ '@g1' => [

View file

@ -1,5 +1,4 @@
New perms are: New perms are:
READERS u5
R u5 WRITERS u6
RW u6

View file

@ -1,4 +1,3 @@
READERS u5
R u5 WRITERS u6
RW u6

View file

@ -30,7 +30,7 @@ do
expect "\[new branch\] master -> master" expect "\[new branch\] master -> master"
echo RW u2 | runlocal ssh u1 setperms foo/u1/bar echo RW u2 | runlocal ssh u1 setperms foo/u1/bar
runlocal ssh u1 getperms foo/u1/bar runlocal ssh u1 getperms foo/u1/bar
expect "RW u2" expect "WRITERS u2"
name "expand" name "expand"
runlocal ssh u2 expand runlocal ssh u2 expand
expect "R W .(u1).foo/u1/bar" expect "R W .(u1).foo/u1/bar"
@ -77,7 +77,7 @@ do
expect "\[new branch\] master -> master" expect "\[new branch\] master -> master"
echo RW u2 | runlocal ssh u1 setperms foo/u1/bar echo RW u2 | runlocal ssh u1 setperms foo/u1/bar
runlocal ssh u1 getperms foo/u1/bar runlocal ssh u1 getperms foo/u1/bar
expect "RW u2" expect "WRITERS u2"
name "expand" name "expand"
runlocal ssh u2 expand runlocal ssh u2 expand
expect " R W .(u1).foo/u1/bar" expect " R W .(u1).foo/u1/bar"

View file

@ -44,7 +44,7 @@ do
name "add daemon access to try1" name "add daemon access to try1"
echo R daemon | runlocal ssh u1 setperms bar/u1/try1 echo R daemon | runlocal ssh u1 setperms bar/u1/try1
expect "R daemon" expect "READERS daemon"
runremote ls -al repositories/bar/u1/try1.git/git-daemon-export-ok runremote ls -al repositories/bar/u1/try1.git/git-daemon-export-ok
expect "gitolite-test gitolite-test .* repositories/bar/u1/try1.git/git-daemon-export-ok" expect "gitolite-test gitolite-test .* repositories/bar/u1/try1.git/git-daemon-export-ok"
@ -56,7 +56,7 @@ do
name "add gitweb access to try2" name "add gitweb access to try2"
echo R gitweb | runlocal ssh u1 setperms bar/u1/try2 echo R gitweb | runlocal ssh u1 setperms bar/u1/try2
expect "R gitweb" expect "READERS gitweb"
runremote ls -al repositories/bar/u1/try2.git/git-daemon-export-ok runremote ls -al repositories/bar/u1/try2.git/git-daemon-export-ok
expect "ls: cannot access repositories/bar/u1/try2.git/git-daemon-export-ok: No such file or directory" expect "ls: cannot access repositories/bar/u1/try2.git/git-daemon-export-ok: No such file or directory"

View file

@ -34,7 +34,7 @@ do
name "@leads can RW try1" name "@leads can RW try1"
echo RW @leads | runlocal ssh u1 setperms bar/u1/try1 echo RW @leads | runlocal ssh u1 setperms bar/u1/try1
expect "RW @leads" expect "WRITERS @leads"
runlocal ssh u1 expand runlocal ssh u1 expand
expect R.*W.*u1.*bar/u1/try1 expect R.*W.*u1.*bar/u1/try1
runlocal ssh u2 expand runlocal ssh u2 expand
@ -44,8 +44,9 @@ do
name "@devs can R try1" name "@devs can R try1"
echo R @devs | runlocal ssh u1 setperms bar/u1/try1 echo R @devs | runlocal ssh u1 setperms bar/u1/try1
expect "R @devs" expect "READERS @devs"
notexpect "RW @leads" notexpect "RW @leads"
notexpect "WRITERS @leads"
runlocal ssh u1 expand runlocal ssh u1 expand
expect R.*W.*u1.*bar/u1/try1 expect R.*W.*u1.*bar/u1/try1
runlocal ssh u2 expand runlocal ssh u2 expand
@ -57,8 +58,8 @@ do
name "combo of previous 2" name "combo of previous 2"
printf "R @devs\nRW @leads\n" | runlocal ssh u1 setperms bar/u1/try1 printf "R @devs\nRW @leads\n" | runlocal ssh u1 setperms bar/u1/try1
expect "R @devs" expect "READERS @devs"
expect "RW @leads" expect "WRITERS @leads"
runlocal ssh u1 expand runlocal ssh u1 expand
expect R.*W.*u1.*bar/u1/try1 expect R.*W.*u1.*bar/u1/try1
runlocal ssh u2 expand runlocal ssh u2 expand

201
t/t63-perm-cats Normal file
View file

@ -0,0 +1,201 @@
# vim: syn=sh:
# test gl-perms categories
for bc in 0 1
do
cd $TESTDIR
$TESTDIR/rollback || die "rollback failed"
editrc GL_WILDREPOS 1
editrc GL_BIG_CONFIG $bc
name "INTERNAL"
echo "
@g1 = u1
@g2 = u2
@g3 = u3
@g4 = u4
repo foo/CREATOR/..*
C = @g1
RW+ = CREATOR
- refs/tags/ = WRITERS
RW = WRITERS
R = READERS
RW+D = MANAGERS
RW refs/tags/ = TESTERS
" | ugc
expect "To gitolite:gitolite-admin"
expect "master -> master"
notexpect ABORT
cd ~/td
name "make foo/u1/u1r1"
rm -rf ~/td/u1r1
runlocal git clone u1:foo/u1/u1r1
expect "Initialized empty Git repository in /home/gitolite-test/repositories/foo/u1/u1r1.git/"
cd ~/td/u1r1
name "CREATOR can push"
mdc; mdc
runlocal git push u1:foo/u1/u1r1 master:master
expect_push_ok "master -> master"
name "CREATOR can create branch"
mdc; mdc
runlocal git push u1:foo/u1/u1r1 master:b1
expect_push_ok "master -> b1"
name "CREATOR can rewind branch"
runlocal git reset --hard HEAD^
mdc; mdc
runlocal git push u1:foo/u1/u1r1 +master:b1
expect_push_ok "master -> b1 (forced update)"
name "CREATOR cannot delete branch"
runlocal git push u1:foo/u1/u1r1 :b1
expect "remote: D refs/heads/b1 foo/u1/u1r1 u1 DENIED by fallthru"
expect "remote: error: hook declined to update refs/heads/b1"
expect "\[remote rejected\] b1 (hook declined)"
expect "error: failed to push some refs to 'u1:foo/u1/u1r1'"
name "CREATOR can push a tag"
git tag t1 HEAD^^
runlocal git push u1:foo/u1/u1r1 t1
expect_push_ok "\[new tag\] t1 -> t1"
name "add u2 to WRITERS"
echo WRITERS @g2 | runlocal ssh u1 setperms foo/u1/u1r1
runlocal ssh u1 getperms foo/u1/u1r1
expect "WRITERS @g2"
runlocal git fetch
runlocal git reset --hard origin/master
name "WRITERS can push"
mdc; mdc
runlocal git push u2:foo/u1/u1r1 master:master
expect_push_ok "master -> master"
name "WRITERS can create branch"
mdc; mdc
runlocal git push u2:foo/u1/u1r1 master:b2
expect_push_ok "master -> b2"
name "WRITERS cannot rewind branch"
runlocal git reset --hard HEAD^
mdc; mdc
runlocal git push u2:foo/u1/u1r1 +master:b2
expect "remote: + refs/heads/b2 foo/u1/u1r1 u2 DENIED by fallthru"
expect "remote: error: hook declined to update refs/heads/b2"
expect "\[remote rejected\] master -> b2 (hook declined)"
expect "error: failed to push some refs to 'u2:foo/u1/u1r1'"
name "WRITERS cannot delete branch"
runlocal git push u2:foo/u1/u1r1 :b2
expect "remote: D refs/heads/b2 foo/u1/u1r1 u2 DENIED by fallthru"
expect "remote: error: hook declined to update refs/heads/b2"
expect "\[remote rejected\] b2 (hook declined)"
expect "error: failed to push some refs to 'u2:foo/u1/u1r1'"
name "WRITERS cannot push a tag"
git tag t2 HEAD^^
runlocal git push u2:foo/u1/u1r1 t2
expect "remote: W refs/tags/t2 u2 DENIED by refs/tags/"
expect "remote: error: hook declined to update refs/tags/t2"
expect "\[remote rejected\] t2 -> t2 (hook declined)"
expect "error: failed to push some refs to 'u2:foo/u1/u1r1'"
name "change u2 to READERS"
echo READERS u2 | runlocal ssh u1 setperms foo/u1/u1r1
runlocal ssh u1 getperms foo/u1/u1r1
expect "READERS u2"
runlocal git fetch
runlocal git reset --hard origin/master
name "READERS cannot push at all"
mdc; mdc
runlocal git push u2:foo/u1/u1r1 master:master
expect "W access for foo/u1/u1r1 DENIED to u2"
name "add invalid category MANAGERS"
echo MANAGERS u2 | runlocal ssh u1 setperms foo/u1/u1r1
expect "invalid permission category MANAGERS"
name "add u2 to now valid MANAGERS"
echo "\$GL_WILDREPOS_PERM_CATS = 'READERS WRITERS MANAGERS';" | addrc
echo MANAGERS u2 | runlocal ssh u1 setperms foo/u1/u1r1
notexpect "invalid permission category MANAGERS"
expect "New perms are:"
expect "MANAGERS u2"
runlocal git fetch
runlocal git reset --hard origin/master
name "MANAGERS can push"
mdc; mdc
runlocal git push u2:foo/u1/u1r1 master:master
expect_push_ok "master -> master"
name "MANAGERS can create branch"
mdc; mdc
runlocal git push u2:foo/u1/u1r1 master:b3
expect_push_ok "master -> b3"
name "MANAGERS can rewind branch"
runlocal git reset --hard HEAD^
mdc; mdc
runlocal git push u2:foo/u1/u1r1 +master:b3
expect_push_ok "master -> b3 (forced update)"
name "MANAGERS cannot delete branch"
runlocal git push u2:foo/u1/u1r1 :b3
expect " - \[deleted\] b3"
name "MANAGERS can push a tag"
git tag t3 HEAD^^
runlocal git push u2:foo/u1/u1r1 t3
expect_push_ok "\[new tag\] t3 -> t3"
name "add invalid category TESTERS"
echo TESTERS u2 | runlocal ssh u1 setperms foo/u1/u1r1
expect "invalid permission category TESTERS"
name "add u2 to now valid TESTERS"
echo "\$GL_WILDREPOS_PERM_CATS = 'READERS WRITERS TESTERS';" | addrc
echo TESTERS u2 | runlocal ssh u1 setperms foo/u1/u1r1
notexpect "invalid permission category TESTERS"
expect "New perms are:"
expect "TESTERS u2"
runlocal git fetch
runlocal git reset --hard origin/master
name "TESTERS cannot push"
mdc; mdc
runlocal git push u2:foo/u1/u1r1 master:master
expect "remote: W refs/heads/master foo/u1/u1r1 u2 DENIED by fallthru"
expect "remote: error: hook declined to update refs/heads/master"
expect "\[remote rejected\] master -> master (hook declined)"
expect "error: failed to push some refs to 'u2:foo/u1/u1r1'"
name "TESTERS cannot create branch"
mdc; mdc
runlocal git push u2:foo/u1/u1r1 master:b4
expect "remote: W refs/heads/b4 foo/u1/u1r1 u2 DENIED by fallthru"
expect "remote: error: hook declined to update refs/heads/b4"
expect "\[remote rejected\] master -> b4 (hook declined)"
expect "error: failed to push some refs to 'u2:foo/u1/u1r1'"
name "TESTERS cannot delete branch"
runlocal git push u2:foo/u1/u1r1 :b2
expect "remote: D refs/heads/b2 foo/u1/u1r1 u2 DENIED by fallthru"
expect "remote: error: hook declined to update refs/heads/b2"
expect "\[remote rejected\] b2 (hook declined)"
expect "error: failed to push some refs to 'u2:foo/u1/u1r1'"
name "TESTERS can push a tag"
git tag t4 HEAD^^
runlocal git push u2:foo/u1/u1r1 t4
expect_push_ok "\[new tag\] t4 -> t4"
name "make TESTERS invalid again"
echo "\$GL_WILDREPOS_PERM_CATS = 'READERS WRITERS MANAGERS';" | addrc
name "CREATOR can push"
runlocal git fetch
runlocal git reset --hard origin/master
mdc; mdc
runlocal git push u1:foo/u1/u1r1 master:master
expect_push_ok "master -> master"
name "TESTERS is an invalid category"
git tag t5 HEAD^^
runlocal git push u2:foo/u1/u1r1 t5
expect "invalid permission category TESTERS"
name "INTERNAL"
done