summaryrefslogtreecommitdiffabout
path: root/gitaclhook
authorSergey Poznyakoff <gray@gnu.org.ua>2013-06-23 09:41:49 (GMT)
committer Sergey Poznyakoff <gray@gnu.org.ua>2013-06-23 09:41:49 (GMT)
commite5b2691b92e2d67b65ee3298b048124c761fd622 (patch) (side-by-side diff)
treed77e407448eaf816283b9ff4191f2d10526d6222 /gitaclhook
parent1e744ae787312413fb8600dd622b5fd7374b45ae (diff)
downloadgitaclhook-e5b2691b92e2d67b65ee3298b048124c761fd622.tar.gz
gitaclhook-e5b2691b92e2d67b65ee3298b048124c761fd622.tar.bz2
Rewrite gitaclhook in a modular way.
The new version provides two storage engines: File and LDAP. * git/MANIFEST: New file. * git/Makefile.PL: New file. * git/gitaclhook: Rewrite. * git/lib/GitACL.pm: New file. * git/lib/GitACL/File.pm: New file. * git/lib/GitACL/LDAP.pm: New file.
Diffstat (limited to 'gitaclhook') (more/less context) (ignore whitespace changes)
-rwxr-xr-xgitaclhook355
1 files changed, 123 insertions, 232 deletions
diff --git a/gitaclhook b/gitaclhook
index b8b5513..e57a78a 100755
--- a/gitaclhook
+++ b/gitaclhook
@@ -15,11 +15,12 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>.
use strict;
-use File::Spec;
+use GitACL;
+use GitACL::File;
use Pod::Man;
use Pod::Usage;
use Getopt::Long qw(:config gnu_getopt no_ignore_case);
-
+
=head1 NAME
gitaclhook - control access to git repositories
@@ -28,7 +29,7 @@ gitaclhook - control access to git repositories
B<gitaclhook> I<refname> I<old-sha1> I<new-sha1>
-B<gitacthook> B<--test> I<REPO> I<USER> I<OP> I<REF>
+B<gitacthook> [B<--debug>] B<--test> I<REPO> I<USER> I<OP> I<REF>
B<gitaclhook --help>
@@ -38,12 +39,24 @@ This program is intended to be run as an "update" hook by git.
It is called by B<git-receive-pack> with arguments:
I<refname> I<old-sha1> I<new-sha1>.
-If the B<hooks.aclfile> keyword is defined in the repository's config file,
-this hook will parse the file and allow or deny update depending on
-its settings. If B<hooks.aclfile> is not defined, update is allowed
-unconditionally.
-
+The program reads access control lists from the storage engine
+specified in the repository's config file and allows or denies
+the update depending on their settings. If no storage engine is
+defined update is allowed unconditionally. If it is defined, but
+is not available (e.g. the disk file does not exist or the LDAP
+server cannot be reached, the update is denied.
+
+Two storage engines are supported: B<File>, which reads access control
+lists from a disk file, and B<LDAP>, which obtains them from LDAP.
+The engine to use is defined by the B<hooks.acltype> configuration keyword.
+The default is B<File>.
+
=head1 ACL FILE
+
+The ACL file is used when the B<File> storage engine is requested. The
+path to the file must be given via the B<hooks.aclfile> configuration
+keyword. If B<hooks.aclfile> is not defined, update is allowed
+unconditionally.
The ACL file has the usual line-oriented syntax. Comments are introduced
by the # sign and extend to the end of the physical line. Comments and
@@ -147,21 +160,88 @@ with B<v> and containing only digits afterwards, and the user B<admin> will
be allowed to do anything he pleases. No other users will be allowed to
update that repository.
+=head1 LDAP
+
+The LDAP storage engine is requested by the following configuration statement:
+
+ [hooks]
+ acltype = LDAP
+
+The URI of the LDAP server to use and other data necessary to access it
+are read from the file name given in the B<hooks.aclldapconf> variable,
+or from B</etc/ldap.conf>, if it is not defined. LDAP access control
+entries are similar to the plaintext file ACLs. Each entry has the
+following attrubutes:
+
+=over 4
+
+=item B<gitAclProject> [mandatory]
+
+The project this entry applies to.
+
+=item B<gitAclVerb> [mandatory]
+
+The control verb.
+
+=item B<gitAclUser> [optional]
+
+The user name or group (B<%>I<GROUPNAME>) specification.
+
+=item B<gitAclOp> [optional]
+
+The list of operation codes.
+
+=item B<gitAclRef> [optional]
+
+Git ref.
+
+=item B<gitAclOrder> [optional]
+
+Sorting order (see below).
+
+=back
+
+The program first reads all entries with the B<gitAclProject> attribute
+matching the requested project name. The obtained entries are sorted
+by the value of B<gitAclOrder> attribute. Entries without this attributes
+are assumed to have sorting order B<0>. Entries with the project name
+B<all> are sorted last. Entries with the same sorting order are sorted
+by the count of attributes they carry (in the reverse order). Thus, the
+most specific entries precede the least specific entries in the resulting
+list.
+
+Each list entry is then matched against the current tuple (I<PROJECT>, I<USER>,
+I<OP>, I<REF>), much the same way as described in B<RULE MATCHING>. Missing
+attributes always match. The special B<gitAclProject> value B<all> matches
+all project names.
+
+If no matching entry is found, the update is allowed.
+
=head1 CONFIGURATION SETTINGS
=over 4
+=item B<hooks.acltype> STRING
+
+Type of the storage engine. Valid values are B<File> (default) and B<LDAP>.
+
=item B<hooks.aclfile> STRING
-Name of the ACL file.
+For the B<File> storage engine, name of the ACL file.
+
+=item B<hooks.aclldapconf> STRING
+
+For the B<LDAP> storage engine, the name of the configuration file to use
+instead of B</etc/ldap.conf>.
=item B<hooks.acllog> STRING
Send log info to this file.
-=item B<hooks.acldebug> BOOL
+=item B<hooks.acldebug> NUMBER
-Enable debugging.
+Enable debugging. The bigger the number, the more debugging info will
+be displayed.
=item B<hooks.aclquiet> BOOL
@@ -178,19 +258,20 @@ from the environment variable B<REMOTE_USER>.
=head1 TEST MODE
-The B<--test> option provides a mechanism for testing access control lists
-from the command line. When given this option, B<gitaclhook> expects four
-arguments:
+The B<--test> (B<-t>) option provides a mechanism for testing access control
+lists from the command line. The syntax is:
=over 4
-B<gitacthook> B<--test> I<REPO> I<USER> I<OP> I<REF>
+B<gitacthook> [B<--debug>] [B<-d>] B<--test> I<REPO> I<USER> I<OP> I<REF>
=back
I<REPO> is a pathname of the repository to test, I<USER> is the username,
I<OP> is the operation code and I<REF> is the reference.
+Optional B<--debug> (B<-d>) options increment the debugging level.
+
=head1 ENVIRONMENT
The program uses following environment variables:
@@ -231,242 +312,52 @@ B<git-receive-pack>(1).
=head1 AUTHOR
-Sergey Poznyakoff, <gray@gno.org>
+Sergey Poznyakoff, <gray@gnu.org>
=cut
-my $debug_level = $ENV{GIT_UPDATE_DEBUG} > 0;
-my $logfile;
-my $quiet;
-my ($user_name) = getpwuid $<;
-my $git_dir = $ENV{GIT_DIR};
-my $ref;
-my $old;
-my $new;
-
-my $project_name;
-my $op;
-
-my %opstr = ('C' => 'create',
- 'D' => 'delete',
- 'U' => 'update',
- 'R' => 'rewind/rebase');
-
-sub debug($$) {
- my ($level,$msg) = @_;
- if ($level <= $debug_level) {
- print STDERR "debug: $msg\n";
- }
-}
-
-sub logmsg($$;$) {
- return 0 unless $logfile;
-
- my $status = shift;
- my $message = shift;
- my $loc = shift;
- my $fd;
+my $script;
+($script = $0) =~ s/.*\///;
- open($fd, $logfile);
- if ($loc) {
- print $fd "$status:$loc: $message\n";
- } else {
- print $fd "$status: $message\n";
- }
- close($fd);
-}
-
-sub deny($;$) {
+sub abend($) {
my $msg = shift;
- my $loc = shift;
-
- logmsg("DENY",
- "$project_name:$user_name:$opstr{$op}:$ref:$old:$new: $msg",
- $loc);
-
- debug(1, "denied by $loc") if $loc;
- print STDERR "denied: $msg\n" unless $quiet;
- exit 1;
-}
-
-sub allow($) {
- logmsg("ALLOW",
- "$project_name:$user_name:$opstr{$op}:$ref:$old:$new",
- $_[0]);
- debug(1, "allow $_[0]");
- exit 0;
-}
-
-sub info($) {
- logmsg("INFO", $_[0]);
- print STDERR "info: $_[0]\n" if $debug_level;
-}
-
-sub project_name($) {
- my $dir = shift;
-
- File::Spec->rel2abs($dir) =~ m,/([^/]+)(?:\.git|/\.git)$,;
- return $1;
+ print STDERR "$script: $msg\n";
+ exit 2;
}
-sub git_value(@) {
- my $fd;
-
- open($fd,'-|','git',@_);
- local $_ = <$fd>;
- chop;
- close($fd);
- return $_;
-}
-
-sub match_user($) {
- my $user = shift;
- return 1 if ($user eq 'all');
- return 0 if ($user eq 'none');
- if ($user =~ /^%(.+)/) {
- my ($name,$passwd,$gid,$members) = getgrnam($1) or return 0;
- my @a = split(/\s+/,$members);
- for (my $i = 0; $i <= $#a; $i++) {
- return 1 if $a[$i] eq $user_name;
- }
- } elsif ($user eq $user_name) {
- return 1;
- }
- return 0;
-}
+my %args;
-sub match_ref($) {
- my $expr = shift;
- return ($ref =~ /$expr/) if ($expr =~ /^\^/);
- return ("$ref/" eq $expr or index($ref, $expr) == 0) if ($expr =~ /\/$/);
- return $ref eq $expr;
-}
-
-sub match_tuple($) {
- my $tuple = shift;
- my @x = @{$tuple};
-
- return ( \&deny, "malformed line" ) unless $#x >= 2;
- return ( \&deny, "unknown keyword" )
- unless ($x[0] eq 'allow' || $x[0] eq 'deny');
-
- return ( 0, "project mismatch" )
- if ($x[1] ne "*" and $x[1] ne $project_name);
- return ( 0, "user mismatch" )
- unless match_user($x[2]);
- return ( 0, "op mismatch" )
- if ($#x >= 3 && index(uc $x[3], $op) == -1);
- return ( 0, "ref mismatch" )
- if ($#x == 4 && !match_ref($x[4]));
- if ($x[0] eq 'allow') {
- return ( \&allow );
- } else {
- my $s = "you are not permitted to " . $opstr{$op} . " $ref";
- return ( \&deny, $s );
- }
-}
-
-
-sub check_acl {
- my $fd;
- my $line = 0;
- my @ret;
-
- my $filename = git_value('config', 'hooks.aclfile');
- allow("no ACL configured for $project_name")
- unless defined($filename);
-
- open($fd, "<", $filename) or deny("cannot open configuration file: $!");
- while (<$fd>) {
- ++$line;
- chomp;
- s/^\s+//;
- s/\s+$//;
- s/#.*//;
- next if ($_ eq "");
- my @x = split(/\s+/, $_, 5);
-
- my @res = match_tuple(\@x);
- if ($res[0] == 0) {
- debug(2, "$filename:$line: $res[1]");
- next;
- }
- close($fd);
- if ($res[1]) {
- $res[0]->($res[1], "$filename:$line");
- } else {
- $res[0]->("$filename:$line");
- }
- exit(127);
- }
- close($fd);
- allow("default rule");
-}
-
-####
-
-# Sanity checks
-if ($git_dir) {
- $ref = $ARGV[0];
- $old = $ARGV[1];
- $new = $ARGV[2];
-
- deny "bad old value $old" unless $old =~ /^[a-z0-9]{40}$/;
- deny "bad new value $new" unless $new =~ /^[a-z0-9]{40}$/;
-
- if ($old =~ /^0{40}$/) {
- $op = 'C';
- } elsif ($new =~ /^0{40}$/) {
- $op = 'D';
- } elsif ($ref =~ m,^heads/, && $old eq git_value('merge-base',$old,$new)) {
- $op = 'U';
- } else {
- $op = 'R';
- }
-
- $debug_level = git_value('config', '--bool', 'hooks.acldebug')
- unless ($debug_level);
- $logfile = git_value('config', 'hooks.acllog');
- if ($logfile && $logfile !~ /[>|]/) {
- $logfile = ">>$logfile";
- }
- $quiet = git_value('config', 'hooks.aclquiet') unless ($debug_level);
-} else {
+unless ($ENV{GIT_DIR}) {
+ my $debug;
my $test;
GetOptions("help|h" => sub { pod2usage(-exitstatus => 0, -verbose => 2); },
- "debug|d+" => \$debug_level,
+ "debug|d+" => \$debug,
"test|t" => \$test)
or exit (3);
if ($test) {
abend("--test requires four arguments") unless ($#ARGV == 3);
- $git_dir = $ENV{GIT_DIR} = $ARGV[0];
- $user_name = $ARGV[1];
- $op = $ARGV[2];
- deny("invalid op") unless defined($opstr{$op});
- $ref = $ARGV[3];
- $old = '0000000000000000000000000000000000000000';
- $new = '0000000000000000000000000000000000000001';
+ $args{git_dir} = $ENV{GIT_DIR} = $ARGV[0];
+ $args{user_name} = $ARGV[1];
+ $args{op} = $ARGV[2];
+ $args{ref} = $ARGV[3];
+ $args{old} = '0000000000000000000000000000000000000000';
+ $args{new} = '0000000000000000000000000000000000000001';
+ $args{debug} = $debug;
} else {
- deny "try \"$0 --help\" for fore info"
+ abend("try \"$script --help\" for fore info")
}
+} else {
+ abend("bad number of arguments") unless ($#ARGV == 2);
+ $args{git_dir} = $ENV{GIT_DIR};
+ $args{ref} = $ARGV[0];
+ $args{old} = $ARGV[1];
+ $args{new} = $ARGV[2];
}
-my $httpdusr = git_value('config', 'hooks.httpd-user');
-if (defined($httpdusr) and $user_name eq $httpdusr) {
- deny "need authenticated user" unless $ENV{AUTH_TYPE};
- $user_name = $ENV{REMOTE_USER};
-}
-
-deny "need a ref name" unless $ref;
-deny "bogus ref $ref" unless $ref =~ s,^refs/,,;
-deny "no such user" unless $user_name;
-allow "no change requested" if $old eq $new;
-
-$project_name = project_name($git_dir);
-
-info "$user_name requested $opstr{$op} on $ref in $project_name";
+my $gitacl = GitACL->new(%args);
+$gitacl->check;
-&check_acl;
+exit 0;
# Finis

Return to:

Send suggestions and report system problems to the System administrator.