aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--MANIFEST6
-rw-r--r--Makefile.PL17
-rwxr-xr-xgitaclhook355
-rw-r--r--lib/GitACL.pm235
-rw-r--r--lib/GitACL/File.pm42
-rw-r--r--lib/GitACL/LDAP.pm100
6 files changed, 523 insertions, 232 deletions
diff --git a/MANIFEST b/MANIFEST
new file mode 100644
index 0000000..c89e6ca
--- /dev/null
+++ b/MANIFEST
@@ -0,0 +1,6 @@
+MANIFEST
+Makefile.PL
+gitaclhook
+lib/GitACL.pm
+lib/GitACL/File.pm
+lib/GitACL/LDAP.pm
diff --git a/Makefile.PL b/Makefile.PL
new file mode 100644
index 0000000..9338e3b
--- /dev/null
+++ b/Makefile.PL
@@ -0,0 +1,17 @@
+use ExtUtils::MakeMaker;
+
+my %pm = ('lib/GitACL.pm' => '$(INST_LIBDIR)/GitACL.pm',
+ 'lib/GitACL/File.pm' => '$(INST_LIBDIR)/GitACL/File.pm',
+ 'lib/GitACL/LDAP.pm' => '$(INST_LIBDIR)/GitACL/LDAP.pm');
+
+WriteMakefile(
+ 'NAME' => 'gitaclhook',
+ 'FIRST_MAKEFILE' => 'Makefile',
+ 'VERSION' => '1.00',
+ 'PM' => \%pm,
+ 'EXE_FILES' => [ 'gitaclhook' ],
+ 'PREREQ_PM' => { 'Getopt::Long' => 2.34,
+ 'File::Spec' => 3.39 }
+);
+
+
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
diff --git a/lib/GitACL.pm b/lib/GitACL.pm
new file mode 100644
index 0000000..c8ba2dd
--- /dev/null
+++ b/lib/GitACL.pm
@@ -0,0 +1,235 @@
+package GitACL;
+
+use strict;
+use File::Spec;
+
+my %opstr = ('C' => 'create',
+ 'D' => 'delete',
+ 'U' => 'update',
+ 'R' => 'rewind/rebase');
+
+sub debug($$$) {
+ my ($self,$level,$msg) = @_;
+ if ($level <= $self->{debug}) {
+ print STDERR "debug: $msg\n";
+ }
+}
+
+sub logmsg($$$;$) {
+ my $self = shift;
+ return 0 unless $self->{logfile};
+
+ my $status = shift;
+ my $message = shift;
+ my $loc = shift;
+ my $fd;
+
+ open($fd, $self->logfile);
+ if ($loc) {
+ print $fd "$status:$loc: $message\n";
+ } else {
+ print $fd "$status: $message\n";
+ }
+ close($fd);
+}
+
+sub deny($$;$) {
+ my ($self, $msg, $loc) = @_;
+
+ $self->logmsg("DENY",
+ "$self->{project_name}:$self->{user_name}:".
+ "opstr{$self->{op}}:$self->{ref}:$self->{old}:$self->{new}: $msg",
+ $loc);
+
+ $self->debug(1, "denied by $loc") if $loc;
+ print STDERR "denied: $msg\n" unless $self->{quiet};
+ exit 1;
+}
+
+sub allow($$) {
+ my ($self, $loc) = @_;
+ $self->logmsg("ALLOW",
+ "$self->{project_name}:$self->{user_name}:$opstr{$self->{op}}:$self->{ref}:$self->{old}:$self->{new}",
+ $loc);
+ $self->debug(1, "allow $loc");
+ exit 0;
+}
+
+sub info($$) {
+ my ($self, $msg) = @_;
+ $self->logmsg("INFO", $msg);
+ print STDERR "info: $msg\n" if $self->{debug};
+}
+
+sub get_project_name($) {
+ my $dir = shift;
+
+ File::Spec->rel2abs($dir) =~ m,/([^/]+)(?:\.git|/\.git)$,;
+ return $1;
+}
+
+sub git_value(@) {
+ my $fd;
+
+ open($fd,'-|','git',@_);
+ local $_ = <$fd>;
+ chop;
+ close($fd);
+ return $_;
+}
+
+sub match_user($$) {
+ my ($self, $expr) = @_;
+ return 1 if ($expr eq 'all');
+ return 0 if ($expr eq 'none');
+ if ($expr =~ /^%(.+)/) {
+ 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 $self->{user_name};
+ }
+ } elsif ($expr eq $self->{user_name}) {
+ return 1;
+ }
+ return 0;
+}
+
+sub match_ref($$) {
+ my ($self, $expr) = @_;
+
+ return ($self->{ref} =~ /$expr/) if ($expr =~ /^\^/);
+ return ("$self->{ref}/" eq $expr or index($self->{ref}, $expr) == 0)
+ if ($expr =~ /\/$/);
+ return $self->{ref} eq $expr;
+}
+
+sub match_tuple($$) {
+ my ($self, $tuple) = @_;
+ 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 $self->{project_name});
+ return ( 0, "user mismatch" )
+ unless $self->match_user($x[2]);
+ return ( 0, "op mismatch" )
+ if ($#x >= 3 && index(uc $x[3], $self->{op}) == -1);
+ return ( 0, "ref mismatch" )
+ if ($#x == 4 && !$self->match_ref($x[4]));
+ if ($x[0] eq 'allow') {
+ return ( \&allow );
+ } else {
+ my $s = "you are not permitted to " . $opstr{$self->{op}} . " $self->{ref}";
+ return ( \&deny, $s );
+ }
+}
+
+sub new {
+ my $type = shift;
+ my $class = ref($type) || $type;
+ my $obj = bless {}, $class;
+
+ if ($#_ == 0) {
+ $type = shift;
+ %{$obj} = %{$type};
+ return $obj;
+ }
+
+ my %args = @_;
+
+ if (defined($args{git_dir})) {
+ $obj->{git_dir} = $ENV{GIT_DIR} = $args{git_dir};
+ } elsif (defined($ENV{GIT_DIR})) {
+ $obj->{git_dir} = $ENV{GIT_DIR};
+ } else {
+ $obj->deny("no GIT_DIR");
+ }
+
+ if (defined($args{debug})) {
+ $obj->{debug} = $args{debug};
+ } else {
+ $obj->{debug} = git_value('config', '--bool', 'hooks.acldebug') ||
+ $ENV{GIT_UPDATE_DEBUG} > 0;
+ }
+
+ if (defined($args{logfile})) {
+ $obj->{logfile} = $args{logfile};
+ } else {
+ $obj->{logfile} = git_value('config', 'hooks.acllog');
+ }
+ if ($obj->{logfile} && $obj->logfile !~ /[>|]/) {
+ $obj->{logfile} = ">>${obj->logfile}";
+ }
+
+ if (defined($args{quiet})) {
+ $obj->{quiet} = $args{quiet};
+ } elsif (!$obj->{debug}) {
+ $obj->{quiet} = git_value('config', 'hooks.aclquiet');
+ }
+
+ if (defined($args{user})) {
+ $obj->{user_name} = $args{user};
+ } else {
+ my ($u) = getpwuid $<;
+ $obj->{user_name} = $u;
+ }
+ $obj->deny("no such user") unless $obj->{user_name};
+ my $httpdusr = git_value('config', 'hooks.httpd-user');
+ if (defined($httpdusr) and $obj->{user_name} eq $httpdusr) {
+ $obj->deny("need authenticated user") unless $ENV{AUTH_TYPE};
+ $obj->{user_name} = $ENV{REMOTE_USER};
+ }
+
+ $obj->{project_name} = get_project_name($obj->{git_dir});
+
+ $obj->deny("need a ref name") unless defined($args{ref});
+ $obj->deny("bogus ref $args{ref}") unless $args{ref} =~ s,^refs/,,;
+ $obj->{ref} = $args{ref};
+
+ $obj->deny("bad old value $args{old}")
+ unless $args{old} =~ /^[a-z0-9]{40}$/;
+ $obj->{old} = $args{old};
+ $obj->deny("bad new value $args{new}")
+ unless $args{new} =~ /^[a-z0-9]{40}$/;
+ $obj->{new} = $args{new};
+ $obj->allow("no change requested") if $obj->{old} eq $obj->{new};
+
+ if ($obj->{old} =~ /^0{40}$/) {
+ $obj->{op} = 'C';
+ } elsif ($obj->{new} =~ /^0{40}$/) {
+ $obj->{op} = 'D';
+ } elsif ($obj->{ref} =~ m,^heads/, &&
+ $obj->{old} eq git_value('merge-base',$obj->{old},$obj->{new})) {
+ $obj->{op} = 'U';
+ } else {
+ $obj->{op} = 'R';
+ }
+
+ if (defined($args{op})) {
+ # Hope they know what they're doing
+ $obj->deny("invalid op") unless defined($opstr{$args{op}});
+ $obj->{op} = $args{op};
+ }
+
+ return $obj;
+}
+
+sub check {
+ my $self = shift;
+
+ $self->info("$self->{user_name} requested $opstr{$self->{op}} ".
+ "on $self->{ref} in $self->{project_name}");
+
+ my $type = git_value('config', 'hooks.acltype');
+ $type = "File" unless $type;
+
+ my $r = eval("use GitACL::$type; GitACL::$type->new(\$self);");
+ $self->deny("unsupported acltype: $@") unless $r;
+
+ $r->check_acl;
+}
+
+1;
diff --git a/lib/GitACL/File.pm b/lib/GitACL/File.pm
new file mode 100644
index 0000000..77f3b70
--- /dev/null
+++ b/lib/GitACL/File.pm
@@ -0,0 +1,42 @@
+package GitACL::File;
+use parent 'GitACL';
+
+sub check_acl {
+ my $self = shift;
+ my $fd;
+ my $line = 0;
+ my @ret;
+
+ my $filename = GitACL::git_value('config', 'hooks.aclfile');
+ $self->allow("no ACL configured for $self->project_name")
+ unless defined($filename);
+
+ open($fd, "<", $filename)
+ or $self->deny("cannot open configuration file: $!");
+ while (<$fd>) {
+ ++$line;
+ chomp;
+ s/^\s+//;
+ s/\s+$//;
+ s/#.*//;
+ next if ($_ eq "");
+ my @x = split(/\s+/, $_, 5);
+
+ my @res = $self->match_tuple(\@x);
+ if ($res[0] == 0) {
+ $self->debug(2, "$filename:$line: $res[1]");
+ next;
+ }
+ close($fd);
+ if ($res[1]) {
+ $res[0]->($self, $res[1], "$filename:$line");
+ } else {
+ $res[0]->($self, "$filename:$line");
+ }
+ exit(127);
+ }
+ close($fd);
+ $self->allow("default rule");
+}
+
+1;
diff --git a/lib/GitACL/LDAP.pm b/lib/GitACL/LDAP.pm
new file mode 100644
index 0000000..daa5b72
--- /dev/null
+++ b/lib/GitACL/LDAP.pm
@@ -0,0 +1,100 @@
+package GitACL::LDAP;
+use parent 'GitACL';
+use strict;
+use Net::LDAP;
+
+sub parse_ldap_conf {
+ my $self = shift;
+ my $filename = GitACL::git_value('config', 'hooks.aclldapconf') ||
+ "/etc/ldap.conf";
+
+ my $fd;
+ open($fd, "<", $filename) or
+ $self->deny("cannot open file $filename: $!");
+ while (<$fd>) {
+ chomp;
+ s/^\s+//;
+ s/\s+$//;
+ s/#.*//;
+ next if ($_ eq "");
+ my @x = split(/\s+/, $_, 2);
+ $self->{"ldap_".$x[0]} = $x[1];
+ }
+ close(fd);
+}
+
+sub check_acl($) {
+ my $self = shift;
+ my $filter = "(&(objectClass=gitACL)(|(gitAclProject=$self->{project_name})(gitAclProject=all)))";
+ my %searchargs;
+
+ $self->parse_ldap_conf();
+
+ $searchargs{filter} = $filter;
+
+ $self->debug(2, "connecting to the database");
+ my $ldap = Net::LDAP->new($self->{ldap_uri})
+ or $self->deny("unable to connect to LDAP server $self->{ldap_uri}: $@");
+ $self->debug(2, "searching for $filter");
+ my $sres = $ldap->search(base => $self->{ldap_base},
+ filter => $filter);
+ $self->deny("an error occurred while searching: ".
+ ldap_error_text($sres->code))
+ if ($sres->code);
+ $self->debug(2, "got ".$sres->entries." entries");
+ my @entries = sort {
+ my $pa = $a->get_value('gitAclProject');
+ my $pb = $b->get_value('gitAclProject');
+
+ if ($pa ne $pb) {
+ if ($pa eq "all") {
+ return 1;
+ } else {
+ return -1;
+ }
+ } elsif ($a->exists('gitAclOrder')) {
+ if ($b->exists('gitAclOrder')) {
+ return $a->get_value('gitAclOrder') <=> $b->get_value('gitAclOrder');
+ } else {
+ return 1;
+ }
+ } elsif ($b->exists('gitAclOrder')) {
+ return -1;
+ } else {
+ my @aa = $a->attributes(nooptions => 1);
+ my @ab = $b->attributes(nooptions => 1);
+ return $#ab <=> $#aa;
+ }
+ } $sres->entries;
+
+ foreach my $ent (@entries) {
+ my @x;
+ push(@x, $ent->get_value('gitAclVerb'));
+ push(@x, $ent->get_value('gitAclProject'));
+ push(@x, $ent->exists('gitAclUser') ?
+ $ent->get_value('gitAclUser') : "all");
+ push(@x, $ent->get_value('gitAclOp'))
+ if $ent->exists('gitAclOp');
+ push(@x, $ent->get_value('gitAclRef'))
+ if $ent->exists('gitAclRef');
+
+ my @res = $self->match_tuple(\@x);
+ if ($res[0] == 0) {
+ $self->debug(2, $ent->dn.": $res[1]");
+ next;
+ }
+ $ldap->unbind;
+ if ($res[1]) {
+ $res[0]->($self, $res[1], $ent->dn);
+ } else {
+ $res[0]->($self, $ent->dn);
+ }
+ exit(127);
+ }
+ $ldap->unbind;
+ $self->allow("default rule");
+}
+
+1;
+
+

Return to:

Send suggestions and report system problems to the System administrator.