1 package Gitolite
::Conf
::Load
;
3 # load conf data from stored files
4 # ----------------------------------------------------------------------
21 use Exporter
'import';
30 # ----------------------------------------------------------------------
32 # our variables, because they get loaded by a 'do'
33 our $data_version = '';
42 my $subconf = 'master';
45 'list-groups' => \
&list_groups
,
46 'list-users' => \
&list_users
,
47 'list-repos' => \
&list_repos
,
48 'list-memberships' => \
&list_memberships
,
49 'list-members' => \
&list_members
,
52 # helps maintain the "cache" in both "load_common" and "load_1"
55 # ----------------------------------------------------------------------
61 my $repo = shift or _die
"load() needs a reponame";
63 if ( $repo ne $loaded_repo ) {
72 my ( $repo, $user, $aa, $ref ) = @_;
73 trace
( 2, $repo, $user, $aa, $ref );
74 _die
"invalid user '$user'" if not( $user and $user =~ $USERNAME_PATT );
76 return "$aa any $repo $user DENIED by fallthru" unless update_hook_present
($repo);
82 @rules = rules
( $repo, $user );
83 $deny_rules = option
( $repo, 'deny-rules' );
85 # sanity check the only piece the user can control
86 _die
"invalid characters in ref or filename: '$ref'\n" unless $ref =~ m
(^VREF
/NAME/) or $ref =~ $REF_OR_FILENAME_PATT;
87 # apparently we can't always force sanity; at least what we *return*
88 # should be sane/safe. This pattern is based on REF_OR_FILENAME_PATT.
89 ( my $safe_ref = $ref ) =~ s
([^-0-9a
-zA
-Z
._\@
/+ :,])(.)g
;
90 trace
( 3, "safe_ref", $safe_ref ) if $ref ne $safe_ref;
92 # when a real repo doesn't exist, ^C is a pre-requisite for any other
93 # check to give valid results.
94 if ( $aa ne '^C' and $repo !~ /^\@/ and $repo =~ $REPONAME_PATT and repo_missing
($repo) ) {
95 my $iret = access
( $repo, $user, '^C', $ref );
97 return $iret if $iret =~ /DENIED/;
99 # similarly, ^C must be denied if the repo exists
100 if ( $aa eq '^C' and not repo_missing
($repo) ) {
101 trace
( 2, "DENIED by existence" );
102 return "$aa $safe_ref $repo $user DENIED by existence";
105 trace
( 3, scalar(@rules) . " rules found" );
107 $rc{RULE_TRACE
} = '';
109 $rc{RULE_TRACE
} .= " " . $r->[0] . " ";
112 my $refex = $r->[2]; $refex =~ s
(/USER/)(/$user/);
113 trace
( 3, "perm=$perm, refex=$refex" );
115 $rc{RULE_TRACE
} .= "d";
116 # skip 'deny' rules if the ref is not (yet) known
117 next if $perm eq '-' and $ref eq 'any' and not $deny_rules;
119 $rc{RULE_TRACE
} .= "r";
120 # rule matches if ref matches or ref is any (see gitolite-shell)
121 next unless $ref =~ /^$refex/ or $ref eq 'any';
123 $rc{RULE_TRACE
} .= "D";
124 trace
( 2, "DENIED by $refex" ) if $perm eq '-';
125 return "$aa $safe_ref $repo $user DENIED by $refex" if $perm eq '-';
127 # For repo creation, perm will be C and aa will be "^C". For branch
128 # access, $perm can be RW\+?(C|D|CD|DC)?M?, and $aa can be W, +, C or
129 # D, or any of these followed by "M".
131 # We need to turn $aa into a regex that can match a suitable $perm.
132 # This is trivially true for "^C", "W" and "D", but the others (+, C,
133 # M) need some tweaking.
135 # first, quote the '+':
136 ( my $aaq = $aa ) =~ s/\+/\\+/;
137 # if aa is just "C", the user is trying to create a *branch* (not a
138 # *repo*), so let's make the pattern clearer to reflect that.
139 $aaq = "RW.*C" if $aaq eq "C";
140 # if the aa is, say "WM", make this "W.*M" because the perm could be
141 # 'RW+M', 'RW+CDM' etc, and they are all valid:
144 $rc{RULE_TRACE
} .= "A";
146 # as far as *this* ref is concerned we're ok
147 return $refex if ( $perm =~ /$aaq/ );
149 $rc{RULE_TRACE
} .= "p";
151 $rc{RULE_TRACE
} .= " F";
153 trace
( 2, "DENIED by fallthru" );
154 return "$aa $safe_ref $repo $user DENIED by fallthru";
159 require Gitolite
::Cache
;
160 Gitolite
::Cache
::cache_wrap
('Gitolite::Conf::Load::access');
164 my ( $repo, $key, $empty_values_OK ) = @_;
167 if ( repo_missing
($repo) ) {
173 # read comments bottom up
175 # and take the second and third elements to make up your new hash
176 map { $_->[1] => $_->[2] }
177 # keep only the ones where the second element matches your key
178 grep { $_->[1] =~ qr
($key) }
179 # sort this list of listrefs by the first element in each list ref'd to
180 sort { $a->[0] <=> $b->[0] }
181 # dereference it (into a list of listrefs)
183 # take the value of that entry
185 # if it has an entry in %configs
186 grep { $configs{$_} }
187 # for each "repo" that represents us
188 memberships
( 'repo', $repo );
190 # %configs looks like this (for each 'foo' that is in memberships())
191 # 'foo' => [ [ 6, 'foo.bar', 'repo' ], [ 7, 'foodbar', 'repoD' ], [ 8, 'foo.czar', 'jule' ] ],
192 # the first map gets you the value
193 # [ [ 6, 'foo.bar', 'repo' ], [ 7, 'foodbar', 'repoD' ], [ 8, 'foo.czar', 'jule' ] ],
195 # [ 6, 'foo.bar', 'repo' ], [ 7, 'foodbar', 'repoD' ], [ 8, 'foo.czar', 'jule' ]
196 # the sort rearranges it (in this case it's already sorted but anyway...)
197 # the grep gets you this, assuming the key is foo.bar (and "." is regex ".')
198 # [ 6, 'foo.bar', 'repo' ], [ 7, 'foodbar', 'repoD' ]
199 # and the final map does this:
200 # 'foo.bar'=>'repo' , 'foodbar'=>'repoD'
202 # now some of these will have an empty key; we need to delete them unless
203 # we're told empty values are OK
204 unless ($empty_values_OK) {
206 while ( ( $k, $v ) = each %ret ) {
207 delete $ret{$k} if not $v;
212 my $creator = creator
($repo);
213 while ( ( $k, $v ) = each %ret ) {
214 $v =~ s/%GL_REPO/$repo/g;
215 $v =~ s/%GL_CREATOR/$creator/g if $creator;
219 map { trace
( 3, "$_", "$ret{$_}" ) } ( sort keys %ret ) if $ENV{D
};
224 return unless -f
"$rc{GL_ADMIN_BASE}/conf/gitolite.conf-compiled.pm";
225 # prevent catch-22 during initial install
230 map { delete $ENV{$_} } grep { /^GL_OPTION_/ } keys %ENV;
231 my $h = git_config
( $repo, '^gitolite-options.ENV\.' );
232 while ( my ( $k, $v ) = each %$h ) {
233 next unless $k =~ /^gitolite-options.ENV\.(\w+)$/;
234 $ENV{ "GL_OPTION_" . $1 } = $v;
241 my ( $repo, $option ) = @_;
242 $option = "gitolite-options.$option";
243 my $ret = git_config
( $repo, "^\Q$option\E\$" );
244 return '' unless %$ret;
245 return $ret->{$option};
249 my ($repo, $patt) = @_;
250 $patt ||= $REPOPATT_PATT;
252 _die
"invalid repo '$repo'" if not( $repo and $repo =~ $patt );
253 _die
"'$repo' ends with a '/'" if $repo =~ m
(/$);
254 _die
"'$repo' contains '..'" if $repo =~ $REPONAME_PATT and $repo =~ m
(\
.\
.);
255 _die
"'$repo' contains '.git/'" if $repo =~ $REPONAME_PATT and $repo =~ m
(\
.git
/);
256 _die
"'$repo' ends with '.git'" if $repo =~ m
(\
.git
$);
263 return not -d
"$rc{GL_REPO_BASE}/$repo.git";
266 # ----------------------------------------------------------------------
270 _chdir
( $rc{GL_ADMIN_BASE
} );
272 # we take an unusual approach to caching this function!
273 # (requires that first call to load_common is before first call to load_1)
274 if ( $last_repo and $split_conf{$last_repo} ) {
275 delete $repos{$last_repo};
276 delete $configs{$last_repo};
280 my $cc = "./conf/gitolite.conf-compiled.pm";
282 _die
"parse '$cc' failed: " . ( $! or $@
) unless do $cc;
284 if ( data_version_mismatch
() ) {
285 _system
("gitolite setup");
286 _die
"parse '$cc' failed: " . ( $! or $@
) unless do $cc;
287 _die
"data version update failed; this is serious" if data_version_mismatch
();
293 return if $repo =~ /^\@/;
296 if ( repo_missing
($repo) ) {
297 trace
( 1, "repo '$repo' missing" ) if $repo =~ $REPONAME_PATT;
300 _chdir
("$rc{GL_REPO_BASE}/$repo.git");
302 if ( $repo eq $last_repo ) {
303 $repos{$repo} = $one_repo{$repo};
304 $configs{$repo} = $one_config{$repo} if $one_config{$repo};
308 if ( -f
"gl-conf" ) {
309 return if not $split_conf{$repo} and not $rc{ALLOW_ORPHAN_GL_CONF
};
311 my $cc = "./gl-conf";
312 _die
"parse '$cc' failed: " . ( $@
or $! ) unless do $cc;
315 $repos{$repo} = $one_repo{$repo};
316 $configs{$repo} = $one_config{$repo} if $one_config{$repo};
318 _die
"split conf set, gl-conf not present for '$repo'" if $split_conf{$repo};
328 my ( $repo, $user ) = @_;
329 trace
( 3, $repo, $user );
331 return @cached if ( $lastrepo eq $repo and $lastuser eq $user and @cached );
335 my @repos = memberships
( 'repo', $repo );
336 my @users = memberships
( 'user', $user, $repo );
337 trace
( 3, "memberships: " . scalar(@repos) . " repos and " . scalar(@users) . " users found" );
341 push @rules, @
{ $repos{$r}{$u} } if exists $repos{$r} and exists $repos{$r}{$u};
345 @rules = sort { $a->[0] <=> $b->[0] } @rules;
351 # however if the repo was missing, invalidate the cache
352 $lastrepo = '' if repo_missing
($repo);
358 my ( $repo, $user ) = @_;
359 # fill the cache if needed
360 rules
( $repo, $user ) unless ( $lastrepo eq $repo and $lastuser eq $user and @cached );
363 my @vrefs = grep { /^VREF\// and not $seen{$_}++ } map { $_->[2] } @cached;
370 my ( $type, $base, $repo ) = @_;
375 @ret = ( $base, '@all' );
377 if ( $type eq 'repo' ) {
378 # first, if a repo, say, pub/sitaram/project, has a gl-creator file
379 # that says "sitaram", find memberships for pub/CREATOR/project also
380 $base2 = generic_name
($base);
382 # second, you need to check in %repos also
383 for my $i ( keys %repos, keys %configs ) {
384 if ( $base eq $i or $base =~ /^$i$/ or $base2 and ( $base2 eq $i or $base2 =~ /^$i$/ ) ) {
389 # add in any group names explicitly given in (GIT_DIR)/gl-repo-groups
391 map { s/^\@?/\@/; $_ }
393 split (' ', slurp
("$ENV{GL_REPO_BASE}/$base.git/gl-repo-groups"))
394 if -f
"$ENV{GL_REPO_BASE}/$base.git/gl-repo-groups";
397 push @ret, @
{ $groups{$base} } if exists $groups{$base};
398 push @ret, @
{ $groups{$base2} } if $base2 and exists $groups{$base2};
399 if ($type eq 'repo') {
400 # regexes can only be used for repos, not for users
401 for my $i ( keys %{ $patterns{groups
} } ) {
402 if ( $base =~ /^$i$/ or $base2 and ( $base2 =~ /^$i$/ ) ) {
403 push @ret, @
{ $groups{$i} };
408 push @ret, @
{ ext_grouplist
($base) } if $type eq 'user' and $rc{GROUPLIST_PGM
};
410 if ( $type eq 'user' and $repo and not repo_missing
($repo) ) {
411 # find the roles this user has when accessing this repo and add those
412 # in as groupnames he is a member of. You need the already existing
413 # memberships for this; see below this function for an example
414 push @ret, user_roles
( $base, $repo, @ret );
417 @ret = @
{ sort_u
( \
@ret ) };
418 trace
( 3, sort @ret );
427 # now user is a member of both g1 and g2
429 gl-perms for repo being accessed:
432 This should result in @READERS being added to the memberships that u1 has
433 (when accessing this repo). So we send the current list (@g1, @g2) to
434 user_roles(), otherwise it has to redo that logic.
438 sub data_version_mismatch
{
439 return $data_version ne glrc
('current-data-version');
443 my ( $user, $repo, @eg ) = @_;
445 # eg == existing groups (that user is already known to be a member of)
446 my %eg = map { $_ => 1 } @eg;
449 my $f = "$rc{GL_REPO_BASE}/$repo.git/gl-perms";
452 my $fh = _open
( "<", $f );
453 chomp( @roles = <$fh> );
455 push @roles, "CREATOR = " . creator
($repo);
458 s/^\s+//; s/ +$//; s/=/ /; s/\s+/ /g; s/^\@//;
461 my ( $role, @members ) = split;
462 # role = READERS, members = u3, u4, @g1
463 if ( $role ne 'CREATOR' and not $rc{ROLES
}{$role} ) {
464 _warn
"role '$role' not allowed, ignoring";
467 for my $m (@members) {
468 if ( $m !~ $USERNAME_PATT ) {
469 _warn
"ignoring '$m' in perms line";
472 # if user eq u3/u4, or is a member of @g1, he has role READERS
473 $ret{ '@' . $role } = 1 if $m eq $user or $eg{$m};
485 # get the creator name. For not-yet-born repos this is $ENV{GL_USER},
486 # which should be set in all cases that we care about, viz., where we are
487 # checking ^C permissions before new_wild_repo(), and the info command.
488 # In particular, 'gitolite access' can't be used to check ^C perms on wild
489 # repos that contain "CREATOR" if GL_USER is not set.
490 $creator = creator
($base);
493 $base2 =~ s
(\b$creator\b)(CREATOR
) if $creator;
494 $base2 = '' if $base2 eq $base; # if there was no change
503 return ( $ENV{GL_USER
} || '' ) if repo_missing
($repo);
504 my $f = "$rc{GL_REPO_BASE}/$repo.git/gl-creator";
506 chomp( $creator = slurp
($f) ) if -f
$f;
515 my $pgm = $rc{GROUPLIST_PGM
};
516 return [] if not $pgm;
518 return $cache{$user} if $cache{$user};
519 my @extgroups = map { s/^@?/@/; $_; } split ' ', `$rc{GROUPLIST_PGM} $user`;
520 return ( $cache{$user} = \
@extgroups );
524 # ----------------------------------------------------------------------
526 # ----------------------------------------------------------------------
528 sub lister_dispatch
{
531 my $fn = $listers{$command} or _die
"unknown gitolite sub-command";
536 Usage: gitolite list-groups
538 - lists all group names in conf
539 - no options, no flags
548 while ( my ( $k, $v ) = each(%groups) ) {
551 return ( sort_u
( \
@g ) );
555 Usage: gitolite list-users [<repo name pattern>]
557 List all users and groups explicitly named in a rule.
559 - you will have to run 'list-members' on each group name to expand it -- for
560 details and caveats on that please see its help message.
561 - User names not mentioned in an access rule will not show up at all (for
562 example, if you have users who only have access via an '@all' rule).
564 WARNING: may be slow if you have thousands of repos. The optional repo name
565 pattern is an unanchored regex; it can speed things up if you're interested
566 only in users of a matching set of repos. This is only an optimisation, not
567 an actual access list; you will still have to pipe it to 'gitolite access'
568 with appropriate arguments to get an actual access list.
570 NOTE: If you're running in ssh mode, it may be simpler to parse the authorized
571 keys file in ~/.ssh, like so:
572 perl -lne '/ ([a-z0-9]+)"/; print $1 if $1' < ~/.ssh/authorized_keys | sort -u
573 If you're running in http mode, only your web server knows all the potential
578 my $patt = shift || '.';
579 usage
() if $patt eq '-h' or @_;
585 my @u = map { keys %{$_} } values %repos;
586 $total = scalar( grep { /$patt/ } keys %split_conf );
587 warn "WARNING: you have $total repos to check; this could take some time!\n" if $total > 100;
588 for my $one ( grep { /$patt/ } keys %split_conf ) {
590 $count++; print STDERR
"$count / $total\r" if not( $count % 100 ) and timer
(5);
591 push @u, map { keys %{$_} } values %one_repo;
593 print STDERR
"\n" if $count >= 100;
594 return ( sort_u
( \
@u ) );
598 Usage: gitolite list-repos
600 - lists all repos/repo groups in conf
601 - no options, no flags
610 push @r, keys %split_conf;
612 return ( sort_u
( \
@r ) );
615 =for list_memberships
616 Usage: gitolite list-memberships -u|-r <name>
618 List all groups a name is a member of. One of the flags '-u' or '-r' is
619 mandatory, to specify if the name is a user or a repo.
621 For users, the output includes the result from GROUPLIST_PGM, if it is
622 defined. For repos, the output includes any repo patterns that the repo name
623 matches, as well as any groups that contain those patterns.
626 sub list_memberships
{
627 require Getopt
::Long
;
629 my ( $user, $repo, $help );
631 Getopt
::Long
::GetOptionsFromArray
(
633 'user|u=s' => \
$user,
634 'repo|r=s' => \
$repo,
637 usage
() if $help or ( not $user and not $repo );
642 if ( $user and $repo ) {
643 # unsupported/undocumented except via "in_role()" in Easy.pm
644 @m = memberships
( 'user', $user, $repo );
646 @m = memberships
( 'user', $user );
648 @m = memberships
( 'repo', $repo );
651 @m = grep { $_ ne '@all' and $_ ne ( $user || $repo ) } @m;
652 return ( sort_u
( \
@m ) );
656 Usage: gitolite list-members <group name>
658 - list all members of a group
659 - takes one group name
661 '@all' is not expandable in this context. Also, if you have GROUPLIST_PGM set
662 in your rc file[1], gitolite cannot expand group names completely; only your
663 external database can.
665 [1]: http://gitolite.com/gitolite/conf.html#getting-user-group-info-from-ldap
670 usage
() if @_ and $_[0] eq '-h' or not @_;
677 while ( my ( $k, $v ) = each(%groups) ) {
678 for my $g ( @
{$v} ) {
679 push @m, $k if $g eq $name;
683 return ( sort_u
( \
@m ) );
686 # ----------------------------------------------------------------------
692 unless ($start_time) {
693 $start_time = time();
697 return 0 if time() - $start_time < $elapsed;
698 $start_time = time();