separating "push" from "create"

This is what I *should* have done back then; thanks to Jeff Mitchell for
pointing out a problem with the old method.

The old one is *definitely* a kludge.  <shamefaced grin>
This commit is contained in:
Sitaram Chamarty 2010-06-18 20:14:40 +05:30
parent 78c8caa24c
commit a430cc57c7
6 changed files with 126 additions and 44 deletions

View file

@ -72,13 +72,15 @@
# start line: # start line:
# repo [one or more repos and/or repo groups] # repo [one or more repos and/or repo groups]
# followed by one or more permissions lines: # followed by one or more permissions lines:
# (C|R|RW|RW+|RWD|RW+D) [zero or more refexes] = [one or more users] # (C|R|RW|RW+|RWC|RW+C|RWD|RW+D|RWCD|RW+CD) [zero or more refexes] = [one or more users]
# there are 6 types of permissions: R, RW, and RW+ are simple (the "+" means # there are 6 types of permissions: R, RW, and RW+ are simple (the "+" means
# permission to "rewind" -- force push a non-fast forward to -- a branch). # permission to "rewind" -- force push a non-fast forward to -- a branch).
# The C permission is described in doc/4-wildcard-repositories.mkd. The D # The *standalone* C permission pertains to creating a REPO and is described
# addition to RW/RW+ is described in doc/3-faq-tips-etc.mkd, in the section on # in doc/4-wildcard-repositories.mkd. The C and D *suffixes* to the RW/RW+
# "separating delete and rewind rights". # permissions pertain to creating or deleting a BRANCH, and are described in
# doc/3-faq-tips-etc.mkd, in the sections on "separating push and create
# rights" and "separating delete and rewind rights" respectively.
# how permissions are matched: # how permissions are matched:
# - user, repo, and access (W or +) are known. For that combination, if # - user, repo, and access (W or +) are known. For that combination, if

View file

@ -18,6 +18,7 @@ In this document:
* <a href="#better_logging">better logging</a> * <a href="#better_logging">better logging</a>
* <a href="#exclude_or_deny_rules">"exclude" (or "deny") rules</a> * <a href="#exclude_or_deny_rules">"exclude" (or "deny") rules</a>
* <a href="#separating_delete_and_rewind_rights">separating delete and rewind rights</a> * <a href="#separating_delete_and_rewind_rights">separating delete and rewind rights</a>
* <a href="#separating_create_and_push_rights">separating create and push rights</a>
* <a href="#file_dir_NAME_based_restrictions">file/dir NAME based restrictions</a> * <a href="#file_dir_NAME_based_restrictions">file/dir NAME based restrictions</a>
* <a href="#delegating_parts_of_the_config_file">delegating parts of the config file</a> * <a href="#delegating_parts_of_the_config_file">delegating parts of the config file</a>
* <a href="#convenience_features">convenience features</a> * <a href="#convenience_features">convenience features</a>
@ -381,8 +382,29 @@ Note 2: a quick way to make this the default for *all* your repos is:
where foo can be either the administrator, or if you can ignore the warning where foo can be either the administrator, or if you can ignore the warning
message when you push, a non-existant user. message when you push, a non-existant user.
Note 3: you can combine this with the "create a branch" permissions described
in the next section, as the example line in conf/example.conf shows.
[sdrr]: http://groups.google.com/group/gitolite/browse_thread/thread/9f2b4358ce406d4c# [sdrr]: http://groups.google.com/group/gitolite/browse_thread/thread/9f2b4358ce406d4c#
<a name="separating_create_and_push_rights"></a>
##### separating create and push rights
This feature is similar in spirit to the previous one, so please read that
section for a general understanding.
Briefly:
* branch creation is permitted by using `RWC` or `RW+C` -- essentially the
current branch permissions with a `C` suffixed
* if a repo has a rule containing such a `C`, then the `RW` and `RW+`
permissions (for that repo) no longer permit creation of the ref matched;
they will only allow pushing to an existing ref
Note: you can combine this with the "delete a branch" permissions described in
the previous section, as the example line in conf/example.conf shows.
<a name="file_dir_NAME_based_restrictions"></a> <a name="file_dir_NAME_based_restrictions"></a>
##### file/dir NAME based restrictions ##### file/dir NAME based restrictions

View file

@ -67,6 +67,8 @@ $att_acc = '+' if $oldsha ne $merge_base;
# were any 'D' perms specified? If they were, it means we have to separate # 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' # deletes from rewinds, so if the new sha is all 0's, change the '+' to a 'D'
$att_acc = '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;
# similarly C for create a branch
$att_acc = 'C' if ( $repos{$ENV{GL_REPO}}{CREATE_IS_C} or $repos{'@all'}{CREATE_IS_C} ) and $oldsha eq '0' x 40;
my @allowed_refs; my @allowed_refs;
# @all repos: see comments in similar code in check_access # @all repos: see comments in similar code in check_access

View file

@ -291,6 +291,7 @@ sub parse_acl
for my $r ('@all', @repo_plus) { for my $r ('@all', @repo_plus) {
my $dr = $repo; $dr = '@all' if $r eq '@all'; my $dr = $repo; $dr = '@all' if $r eq '@all';
$repos{$dr}{DELETE_IS_D} = 1 if $repos{$r}{DELETE_IS_D}; $repos{$dr}{DELETE_IS_D} = 1 if $repos{$r}{DELETE_IS_D};
$repos{$dr}{CREATE_IS_C} = 1 if $repos{$r}{CREATE_IS_C};
$repos{$dr}{NAME_LIMITS} = 1 if $repos{$r}{NAME_LIMITS}; $repos{$dr}{NAME_LIMITS} = 1 if $repos{$r}{NAME_LIMITS};
for my $u ('@all', "$gl_user - wild", @user_plus) { for my $u ('@all', "$gl_user - wild", @user_plus) {

View file

@ -204,7 +204,7 @@ sub parse_conf_file
s/\bCREAT[EO]R\b/\$creator/g for @repos; s/\bCREAT[EO]R\b/\$creator/g for @repos;
} }
# actual permission line # actual permission line
elsif (/^(-|C|R|RW\+?D?) (.* )?= (.+)/) elsif (/^(-|C|R|RW\+?(?:C?D?|D?C?)) (.* )?= (.+)/)
{ {
my $perms = $1; my $perms = $1;
my @refs; @refs = split(' ', $2) if $2; my @refs; @refs = split(' ', $2) if $2;
@ -286,6 +286,7 @@ sub parse_conf_file
# that fact easy to find; this changes the meaning of RW+ # that fact easy to find; this changes the meaning of RW+
# to no longer permit deletes (see update hook) # to no longer permit deletes (see update hook)
$repos{$repo}{DELETE_IS_D} = 1 if $perms =~ /D/; $repos{$repo}{DELETE_IS_D} = 1 if $perms =~ /D/;
$repos{$repo}{CREATE_IS_C} = 1 if $perms =~ /RW.*C/;
# for 2nd level check, store each "ref, perms" pair in order # for 2nd level check, store each "ref, perms" pair in order
for my $ref (@refs) for my $ref (@refs)

View file

@ -1,51 +1,105 @@
# vim: syn=sh: # vim: syn=sh:
for wr in 0 1 for bc in 0 1
do do
for bc in 0 1 cd $TESTDIR
do $TESTDIR/rollback
cd $TESTDIR editrc GL_BIG_CONFIG $bc
$TESTDIR/rollback
editrc GL_WILDREPOS $wr
editrc GL_BIG_CONFIG $bc
# ---------- # ----------
name "INTERNAL" # test "C" permissions
echo "
@leads = u1 u2
@devs = u1 u2 u3 u4
repo foo
RW+ = @leads
- CREATE_REF = @devs
RW+ = @devs
" | ugc
cd ~/td name "INTERNAL"
runlocal git clone u1:foo echo "
@leads = u1 u2
@devs = u1 u2 u3 u4
cd foo repo foo
mdc; mdc; mdc; mdc; mdc RW+C = @leads
RW+C personal/USER/ = @devs
RW = @devs
" | ugc
name "u1 can push master" cd ~/td
runlocal git push origin master runlocal git clone u1:foo
expect_push_ok "master -> master"
name "u2 can create newbr1" cd foo
runlocal git push u2:foo master:newbr1 mdc; mdc; mdc; mdc; mdc
expect_push_ok "master -> newbr1"
name "u3 can push newbr1" name "u1 can push/rewind master on foo"
mdc; mdc; mdc; mdc; mdc runlocal git push origin master
runlocal git push u3:foo master:newbr1 expect_push_ok "master -> master"
expect_push_ok "master -> newbr1" runlocal git push -f origin master^^:master
expect_push_ok "master^^ -> master"
name "u4 canNOT push newbr3" name "u2 can create newbr1 on foo"
mdc; mdc; mdc; mdc; mdc runlocal git push u2:foo master:newbr1
runlocal git push u3:foo master:newbr3 expect_push_ok "master -> newbr1"
expect "remote: W refs/heads/CREATE_REF u3 DENIED by refs/heads/CREATE_REF"
expect "\[remote rejected\] master -> newbr3 (hook declined)"
expect "failed to push"
name INTERNAL name "u3 can push newbr1 on foo"
done mdc; mdc; mdc; mdc; mdc
runlocal git push u3:foo master:newbr1
expect_push_ok "master -> newbr1"
name "u4 canNOT create newbr2 on foo"
mdc; mdc; mdc; mdc; mdc
runlocal git push u3:foo master:newbr2
expect "remote: C refs/heads/newbr2 foo u3 DENIED by fallthru"
expect "hook declined"
expect "failed to push"
name "u4 can create/rewind personal/u4/newbr3 on foo"
mdc; mdc; mdc; mdc; mdc
runlocal git push u4:foo master:personal/u4/newbr3
expect_push_ok "master -> personal/u4/newbr3"
runlocal git push -f origin master^^:personal/u4/newbr3
expect_push_ok "master^^ -> personal/u4/newbr3"
# bar, without "C" permissions, should behave like old
name "INTERNAL"
echo "
@leads = u1 u2
@devs = u1 u2 u3 u4
repo bar
RW+ = @leads
RW+ personal/USER/ = @devs
RW = @devs
" | ugc
cd ~/td
runlocal git clone u1:bar
cd bar
mdc; mdc; mdc; mdc; mdc
name "u1 can push/rewind master on bar"
runlocal git push origin master
expect_push_ok "master -> master"
runlocal git push -f origin master^^:master
expect_push_ok "master^^ -> master"
name "u2 can create newbr1 on bar"
runlocal git push u2:bar master:newbr1
expect_push_ok "master -> newbr1"
name "u3 can push newbr1 on bar"
mdc; mdc; mdc; mdc; mdc
runlocal git push u3:bar master:newbr1
expect_push_ok "master -> newbr1"
name "u4 can create newbr2 on bar"
mdc; mdc; mdc; mdc; mdc
runlocal git push u3:bar master:newbr2
expect_push_ok "master -> newbr2"
name "u4 can create/rewind personal/u4/newbr3 on bar"
mdc; mdc; mdc; mdc; mdc
runlocal git push u4:bar master:personal/u4/newbr3
expect_push_ok "master -> personal/u4/newbr3"
runlocal git push -f origin master^^:personal/u4/newbr3
expect_push_ok "master^^ -> personal/u4/newbr3"
name INTERNAL
done done