本文并不适用于所有的需求。适用环境如下:
- redmine 用户分为程序员和普通成员,普通成员不可访问 svn,程序员可以访问,并且可以访问全部 svn 代码。
- 只有项目组成员才被允许向版本库中提交代码。
因为 apache 认证模块的限制,未能实现 svn 的浏览权限与项目绑定。因此程序员允许查阅全部代码。
一、下载并安装 redmine
sudo apt-get install apache2 libapache-ruby1.8 apache2-mpm-worker libapache2-mod-passenger sudo apt-get install redmine redmine-sqlite sudo chown www-data /usr/share/redmine/config/environment.rb
安装时选择 sqlite 数据库。
二、配置 apache
编辑文件:/etc/apache2/sites-available/default
<VirtualHost *:80>
RailsBaseURI /
SetEnv X_DEBIAN_SITEID default
ServerName redmine
DocumentRoot /usr/share/redmine/public
<Directory "/usr/share/redmine/public">
AllowOverride None
Order allow,deny
Allow from all
</Directory>
</VirtualHost>
三、修改密码存储方式兼容 apache 认证
修改文件:app/models/user.rb
return nil unless user.auth_source.authenticate(login, password)
else
+ if user.hashed_password[0,5] != '{SHA}'
+ pw_s = ""
+ user.hashed_password.unpack(
+ 'a2'*(user.hashed_password.length / 2)).collect do |x|
+ pw_s << x.hex
+ end
+ user.hashed_password = '{SHA}' + Base64.encode64(pw_s).chomp
+ end
+
return nil unless User.hash_password(password) == user.hashed_password
end
else
@@ -274,8 +287,10 @@
private
def self.hash_password(clear_password)
- Digest::SHA1.hexdigest(clear_password || "")
+ '{SHA}' + Base64.encode64(Digest::SHA1.digest(clear_password || "")).chomp
end
end
四、安装 dbi 认证
cd /etc/apache2/mods-enabled sudo ln -s ../mods-available/dbd.load sudo ln -s ../mods-available/authn_dbd.load sudo ln -s ../mods-available/auth_basic.load
五、修改认证方式,并关联 redmine 数据库
修改文件:/etc/apache2/mods-available/dav_svn.conf
DBDriver sqlite3 DBDParams "/var/redmine/default/redmine_default" <Location /svn> DAV svn SVNPath /var/svn AuthType Basic AuthName "Private area" AuthBasicProvider dbd AuthDBDUserPWQuery "select hashed_password from users where login=%s and type='User' and status=1 and id in ( select user_id from groups_users,users where group_id=id and type='Group' and (lastname='svn' or lastname='svn_commit'))" Require valid-user </Location>
至此,svn 已被设置为只有属于 svn 或者 svn_commit 两个组的激活用户可以访问 svn,全部具备读写权限。
六、限制提交权限
在 svn\hooks 目录下创建 pre-commit.pl,内容如下。程序中第六行需修改为实际的 sqlite 数据库路径,第 11 行需修改为 svn 的本地实际路径。
#!/usr/bin/env perl
use strict;
use Carp;
use DBI;
my $dbh = DBI->connect("dbi:SQLite:/var/redmine/default/redmine_default") || die "Cannot connect: $DBI::errstr";
my $repos = shift;
my $txn = shift;
my $svnlook = "/usr/bin/svnlook";
my $svnbase = "file:///var/svn/";
my $minchars = 4;
my @svnlooklines = &read_from_process($svnlook, 'log', $repos, '-t', $txn);
my $comment = shift @svnlooklines;
unless (length $comment) {
die "A comment is required!\n";
}
if ( length($comment) < $minchars ) {
die "Comment must be at least $minchars characters.\n";
}
# Get the author from svnlook.
my @svnlooklines = &read_from_process($svnlook, 'author', $repos, '-t', $txn);
my $author = shift @svnlooklines;
unless (length $author)
{
die "$0: txn '$txn' has no author.\n";
}
# Figure out what directories have changed using svnlook..
my @dirs_changed = &read_from_process($svnlook, 'dirs-changed', $repos,
'-t', $txn);
# Lose the trailing slash in the directory names if one exists, except
# in the case of '/'.
my $rootchanged = 0;
for (my $i=0; $i<@dirs_changed; ++$i)
{
if ($dirs_changed[$i] eq '/')
{
$rootchanged = 1;
}
else
{
$dirs_changed[$i] =~ s#^(.+)[/\\]$#$1#;
}
}
# Figure out what files have changed using svnlook.
my @files_changed;
foreach my $line (&read_from_process($svnlook, 'changed', $repos, '-t', $txn))
{
# Split the line up into the modification code and path, ignoring
# property modifications.
if ($line =~ /^.. (.*)$/)
{
push(@files_changed, $1);
}
}
# Create the list of all modified paths.
my @changed = (@dirs_changed, @files_changed);
# There should always be at least one changed path. If there are
# none, then there maybe something fishy going on, so just exit now
# indicating that the commit should not proceed.
unless (@changed)
{
die "$0: no changed paths found in txn '$txn'.\n";
}
# check admin
my $dbconn = $dbh->prepare("select login from users where login='$author' and admin='t'");
$dbconn->execute();
if ( $dbconn->fetchrow_array )
{
exit 0;
}
# check group svn_commit
my $dbconn = $dbh->prepare("select login from users where login='$author' and type='User' and status=1 and id in ( select user_id from groups_users,users where group_id=id and type='Group' and lastname='svn_commit')");
$dbconn->execute();
unless ( $dbconn->fetchrow_array )
{
die "user '$author' does not have permission to commit.\n";
}
my %permissions;
foreach my $path (@changed)
{
$permissions{$path} = 0;
}
my $dbconn = $dbh->prepare("select substr(url, length('$svnbase')+1) from users,repositories,members where repositories.project_id=members.project_id and users.id=members.user_id and users.login='$author' and url like '$svnbase%'");
$dbconn->execute();
while( my $row = $dbconn->fetchrow_array)
{
my ($url) = $row;
if (substr($url, -1) eq "/")
{
$url = substr($url, 0, length($url) - 1);
}
foreach my $path (@changed)
{
if( $path eq $url || ( substr( $path, 0, length($url)) eq $url && substr( $path, length($url), 1) eq "/" ))
{
$permissions{$path} = 1;
}
}
}
my $exit_code = 0;
foreach my $path (@changed)
{
if($permissions{$path} ne 1)
{
print STDERR "user '$author' does not have permission to commit to '$path'\n";
$exit_code = 1;
}
}
exit $exit_code;
# All checks passed, so allow the commit.
sub safe_read_from_pipe
{
unless (@_)
{
croak "$0: safe_read_from_pipe passed no arguments.\n";
}
print "Running @_\n";
my $pid = open(SAFE_READ, '-|');
unless (defined $pid)
{
die "$0: cannot fork: $!\n";
}
unless ($pid)
{
open(STDERR, ">&STDOUT")
or die "$0: cannot dup STDOUT: $!\n";
exec(@_)
or die "$0: cannot exec '@_': $!\n";
}
my @output;
while (<SAFE_READ>)
{
chomp;
push(@output, $_);
}
close(SAFE_READ);
my $result = $?;
my $exit = $result >> 8;
my $signal = $result & 127;
my $cd = $result & 128 ? "with core dump" : "";
if ($signal or $cd)
{
warn "$0: pipe from '@_' failed $cd: exit=$exit signal=$signal\n";
}
if (wantarray)
{
return ($result, @output);
}
else
{
return $result;
}
}
sub read_from_process
{
unless (@_)
{
croak "$0: read_from_process passed no arguments.\n";
}
my ($status, @output) = &safe_read_from_pipe(@_);
if ($status)
{
if (@output)
{
die "$0: '@_' failed with this output:\n", join("\n", @output), "\n";
}
else
{
die "$0: '@_' failed with no output.\n";
}
}
else
{
return @output;
}
}
修改完成后,svn 权限被设置成为必须属于项目组成员才可以提交。
七、配置示例
下面举例说明如何具体使用:
假设 svn 路径为 /var/svn,项目为 prj1
在 redmine 的版本库管理里,需要选择 subversion 并设定地址为:file:///var/svn/prj1
svn 将自动按照 file:///var/svn/ 匹配所属项目,并检验用户是否为合法项目成员,验证成功后才会予以提交。
您可以用合作网站帐号登录:
程序提供: 连接微博