From a430cc57c7fc82827a43524bddb7a5611edfa3a0 Mon Sep 17 00:00:00 2001 From: Sitaram Chamarty Date: Fri, 18 Jun 2010 20:14:40 +0530 Subject: [PATCH] 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. --- conf/example.conf | 10 ++-- doc/3-faq-tips-etc.mkd | 22 +++++++ hooks/common/update | 2 + src/gitolite.pm | 1 + src/gl-compile-conf | 3 +- t/t52-deny-create-ref | 132 +++++++++++++++++++++++++++++------------ 6 files changed, 126 insertions(+), 44 deletions(-) diff --git a/conf/example.conf b/conf/example.conf index cde1130..4cf280d 100644 --- a/conf/example.conf +++ b/conf/example.conf @@ -72,13 +72,15 @@ # start line: # repo [one or more repos and/or repo groups] # 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 # 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 -# addition to RW/RW+ is described in doc/3-faq-tips-etc.mkd, in the section on -# "separating delete and rewind rights". +# The *standalone* C permission pertains to creating a REPO and is described +# in doc/4-wildcard-repositories.mkd. The C and D *suffixes* to the RW/RW+ +# 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: # - user, repo, and access (W or +) are known. For that combination, if diff --git a/doc/3-faq-tips-etc.mkd b/doc/3-faq-tips-etc.mkd index f5ac1da..935e4b3 100644 --- a/doc/3-faq-tips-etc.mkd +++ b/doc/3-faq-tips-etc.mkd @@ -18,6 +18,7 @@ In this document: * better logging * "exclude" (or "deny") rules * separating delete and rewind rights + * separating create and push rights * file/dir NAME based restrictions * delegating parts of the config file * convenience features @@ -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 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# + + +##### 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. + ##### file/dir NAME based restrictions diff --git a/hooks/common/update b/hooks/common/update index b77d6ec..5999449 100755 --- a/hooks/common/update +++ b/hooks/common/update @@ -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 # 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; +# 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; # @all repos: see comments in similar code in check_access diff --git a/src/gitolite.pm b/src/gitolite.pm index c2de81f..13d1451 100644 --- a/src/gitolite.pm +++ b/src/gitolite.pm @@ -291,6 +291,7 @@ sub parse_acl 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}{CREATE_IS_C} = 1 if $repos{$r}{CREATE_IS_C}; $repos{$dr}{NAME_LIMITS} = 1 if $repos{$r}{NAME_LIMITS}; for my $u ('@all', "$gl_user - wild", @user_plus) { diff --git a/src/gl-compile-conf b/src/gl-compile-conf index 2e75e83..f0b27fa 100755 --- a/src/gl-compile-conf +++ b/src/gl-compile-conf @@ -204,7 +204,7 @@ sub parse_conf_file s/\bCREAT[EO]R\b/\$creator/g for @repos; } # actual permission line - elsif (/^(-|C|R|RW\+?D?) (.* )?= (.+)/) + elsif (/^(-|C|R|RW\+?(?:C?D?|D?C?)) (.* )?= (.+)/) { my $perms = $1; 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+ # to no longer permit deletes (see update hook) $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 my $ref (@refs) diff --git a/t/t52-deny-create-ref b/t/t52-deny-create-ref index ff04f8c..9afa369 100644 --- a/t/t52-deny-create-ref +++ b/t/t52-deny-create-ref @@ -1,51 +1,105 @@ # vim: syn=sh: -for wr in 0 1 +for bc in 0 1 do - for bc in 0 1 - do - cd $TESTDIR - $TESTDIR/rollback - editrc GL_WILDREPOS $wr - editrc GL_BIG_CONFIG $bc + cd $TESTDIR + $TESTDIR/rollback + editrc GL_BIG_CONFIG $bc - # ---------- + # ---------- - name "INTERNAL" - echo " - @leads = u1 u2 - @devs = u1 u2 u3 u4 - repo foo - RW+ = @leads - - CREATE_REF = @devs - RW+ = @devs - " | ugc + # test "C" permissions - cd ~/td - runlocal git clone u1:foo + name "INTERNAL" + echo " + @leads = u1 u2 + @devs = u1 u2 u3 u4 - cd foo - mdc; mdc; mdc; mdc; mdc + repo foo + RW+C = @leads + RW+C personal/USER/ = @devs + RW = @devs + " | ugc - name "u1 can push master" - runlocal git push origin master - expect_push_ok "master -> master" + cd ~/td + runlocal git clone u1:foo - name "u2 can create newbr1" - runlocal git push u2:foo master:newbr1 - expect_push_ok "master -> newbr1" + cd foo + mdc; mdc; mdc; mdc; mdc - name "u3 can push newbr1" - mdc; mdc; mdc; mdc; mdc - runlocal git push u3:foo master:newbr1 - expect_push_ok "master -> newbr1" + name "u1 can push/rewind master on foo" + runlocal git push origin master + expect_push_ok "master -> master" + runlocal git push -f origin master^^:master + expect_push_ok "master^^ -> master" - name "u4 canNOT push newbr3" - mdc; mdc; mdc; mdc; mdc - runlocal git push u3:foo master:newbr3 - 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 "u2 can create newbr1 on foo" + runlocal git push u2:foo master:newbr1 + expect_push_ok "master -> newbr1" - name INTERNAL - done + name "u3 can push newbr1 on foo" + 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