From 77306567e96b2551c1bf126d80eceb0b176093ee Mon Sep 17 00:00:00 2001 From: Sitaram Chamarty Date: Sat, 5 Dec 2009 18:38:46 +0530 Subject: [PATCH] wildrepos: teach compile the new syntax There's a new "C" permission to let someone *create* a repo that matches the pattern given in the "repo ..." line. If the word CREATER appears in the repo pattern, then that is forced to the actual user performing that operation. Something like this (we'll discuss READERS and WRITERS later): repo personal/CREATER/.+ C = @staff R [foo] = READERS RW [bar] = WRITERS ...various other permissions as usual... Delegation checking also changes quite a bit... see comments in code Implementation: there's also a sneaky little trick we're playing here with the dumped hash --- src/gitolite.pm | 2 ++ src/gl-compile-conf | 66 ++++++++++++++++++++++++++++++++------------- 2 files changed, 49 insertions(+), 19 deletions(-) diff --git a/src/gitolite.pm b/src/gitolite.pm index 71c0b0c..5864466 100644 --- a/src/gitolite.pm +++ b/src/gitolite.pm @@ -27,6 +27,8 @@ $W_COMMANDS=qr/^git[ -]receive-pack$/; # note that REPONAME_PATT allows a "/" also, which USERNAME_PATT doesn't $REPONAME_PATT=qr(^\@?[0-9a-zA-Z][0-9a-zA-Z._/-]*$); # very simple pattern $USERNAME_PATT=qr(^\@?[0-9a-zA-Z][0-9a-zA-Z._-]*$); # very simple pattern +# same as REPONAME, plus some common regex metas +$REPOPATT_PATT=qr(^\@?[0-9a-zA-Z][\\^.$|()[\]*+?{}0-9a-zA-Z._/-]*$); # ---------------------------------------------------------------------------- # convenience subs diff --git a/src/gl-compile-conf b/src/gl-compile-conf index 56b11ed..54448f6 100755 --- a/src/gl-compile-conf +++ b/src/gl-compile-conf @@ -54,7 +54,7 @@ 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); # and these are set by gitolite.pm -our ($REPONAME_PATT, $USERNAME_PATT, $AUTH_COMMAND, $AUTH_OPTIONS, $ABRT, $WARN); +our ($REPONAME_PATT, $REPOPATT_PATT, $USERNAME_PATT, $AUTH_COMMAND, $AUTH_OPTIONS, $ABRT, $WARN); # the common setup module is in the same directory as this running program is my $bindir = $0; @@ -85,7 +85,8 @@ our %groups = (); # %repos has two functions. # $repos{repo}{R|W}{user} = 1 if user has R (or W) permissions for at least -# one branch in repo. This is used by the "level 1 check" (see faq) +# one branch in repo. This is used by the "level 1 check" (see faq). There's +# also the new "C" (create a repo) permission now # $repos{repo}{user} is a list of {ref, perms} pairs. This is used by the # level 2 check. In order to allow "exclude" rules, the order of rules now @@ -119,9 +120,7 @@ sub expand_list for my $item (@list) { - # we test with the slightly more relaxed pattern here; we'll catch the - # "/" in user name thing later; it doesn't affect security anyway - die "$ABRT bad user or repo name $item\n" unless $item =~ $REPONAME_PATT; + die "$ABRT bad user or repo name $item\n" unless $item =~ $REPOPATT_PATT; if ($item =~ /^@/) # nested group { die "$ABRT undefined group $item\n" unless $groups{$item}; @@ -183,9 +182,11 @@ sub parse_conf_file # grab the list and expand any @stuff in it @repos = split ' ', $1; @repos = expand_list ( @repos ); + + s/\bCREAT[EO]R\b/\$creater/g for @repos; } # actual permission line - elsif (/^(-|R|RW|RW\+) (.* )?= (.+)/) + elsif (/^(-|C|R|RW|RW\+) (.* )?= (.+)/) { my $perms = $1; my @refs; @refs = split(' ', $2) if $2; @@ -202,21 +203,39 @@ sub parse_conf_file unless (@users == 1 and $users[0] eq '@all'); do { die "$ABRT bad username $_\n" unless $_ =~ $USERNAME_PATT } for @users; + s/\bCREAT[EO]R\b/\$creater/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 for my $repo (@repos) # each repo in the current stanza { # if we're processing a delegated config file (not the master - # config), and if that fragment name is not the same as the - # current repo - if ($fragment ne 'master' and $fragment ne $repo) - { - # then the fragment must be a group name and the repo - # being processed must be a member of that "@group". - # Also, the value of the hash for that combination must be - # "master", signifying a group created in the master - # config file and not in one of the delegates - unless ( ($groups{"\@$fragment"}{$repo} || '') eq 'master') - { + # config), we need to prevent attempts by that admin to obtain + # rights on stuff outside his domain + + # trying to set access for $repo (='foo')... + if ( + # processing the master config, not a fragment + ( $fragment eq 'master' ) or + # fragment is also called 'foo' (you're allowed to have a + # fragment that is only concerned with one repo) + ( $fragment eq $repo ) or + # fragment is called "bar" and "@bar = foo" has been + # defined in the master config + ( ($groups{"\@$fragment"}{$repo} || '') eq 'master' ) + ) { + # all these are fine + } else { + # this is a little more complex + + # fragment is called "bar", one or more "@bar = regex" + # have been specified in master, and "foo" matches some + # such "regex" + my @matched = grep { $repo =~ /^$_$/ } + grep { $groups{"\@$fragment"}{$_} eq 'master' } + sort keys %{ $groups{"\@$fragment"} }; + if (@matched < 1) { $ignored{$fragment}{$repo} = 1; next; } @@ -226,6 +245,7 @@ sub parse_conf_file $user_list{$user}++; # only to catch lint, see later # for 1st level check (see faq/tips doc) + $repos{$repo}{C}{$user} = 1, next if $perms eq 'C'; $repos{$repo}{R}{$user} = 1 if $perms =~ /R/; $repos{$repo}{W}{$user} = 1 if $perms =~ /W/; @@ -302,7 +322,12 @@ for my $fragment_file (glob("conf/fragments/*.conf")) } my $compiled_fh = wrap_open( ">", $GL_CONF_COMPILED ); -print $compiled_fh Data::Dumper->Dump([\%repos], [qw(*repos)]); +my $dumped_data = Data::Dumper->Dump([\%repos], [qw(*repos)]); +# the dump uses single quotes, but we convert any strings containing $creater, +# $readers, $writers, to double quoted strings. A wee bit sneaky, but not too +# much... +$dumped_data =~ s/'(?=[^']*\$(?:creater|readers|writers))(.*?)'/"$1"/g; +print $compiled_fh $dumped_data; close $compiled_fh or die "$ABRT close compiled-conf failed: $!\n"; # ---------------------------------------------------------------------------- @@ -326,6 +351,7 @@ my $repo_base_abs = ( $REPO_BASE =~ m(^/) ? $REPO_BASE : "$ENV{HOME}/$REPO_BASE" wrap_chdir("$repo_base_abs"); for my $repo (sort keys %repos) { + next unless $repo =~ $REPONAME_PATT; unless (-d "$repo.git") { new_repo($repo, "$GL_ADMINDIR/src/hooks"); # new_repo would have chdir'd us away; come back @@ -352,6 +378,7 @@ 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"); @@ -363,6 +390,7 @@ for my $repo (sort keys %repos) { 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"}) { @@ -422,7 +450,7 @@ for my $pubkey (glob("*")) # 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)$/ or $user_list{$user} eq 'has pubkey'; + next if $user =~ /^(gitweb|daemon|\@all|\$creater|\$readers|\$writers)$/ or $user_list{$user} eq 'has pubkey'; print STDERR "$WARN user $user in config, but has no pubkey!\n"; }