added a public field to a project.

added a new mod_perl handler for simpler access control
This commit is contained in:
hyung-hwan 2014-05-15 16:17:21 +00:00
parent c8a9455668
commit d5c9aa6f6d
19 changed files with 412 additions and 13 deletions

View File

@ -9,7 +9,7 @@ License: GPL
Group: Applications/Utilities
Source0: %{name}-%{version}.tar.gz
Requires: httpd php php-ldap php-mysql subversion
Requires: httpd php php-ldap php-mysql subversion mod_dav_svn mod_perl perl-LDAP perl-Config-Simple perl-URI perl-DBI
#BuildRequires:
BuildRoot: %{_tmppath}/%{name}-%{version}-root
@ -50,6 +50,8 @@ rm -rf $RPM_BUILD_ROOT
/etc/codepot/post-commit
/etc/codepot/pre-revprop-change
/etc/codepot/post-revprop-change
/etc/codepot/perl/Codepot/AccessHandler.pm
/etc/codepot/perl/Codepot/AuthenHandler.pm
%attr(-,apache,apache) /var/lib/codepot/svnrepo
%attr(-,apache,apache) /var/lib/codepot/files

View File

@ -1,7 +1,7 @@
cfgdir=$(CFGDIR)
cfg_DATA = codepot.ini codepot.mysql codepot.a2ldap
cfg_SCRIPTS = start-commit pre-commit post-commit pre-revprop-change post-revprop-change
cfg_SCRIPTS = start-commit pre-commit post-commit pre-revprop-change post-revprop-change access-normal.conf access-noanon.conf
EXTRA_DIST = $(cfg_DATA) $(cfg_SCRIPTS)

View File

@ -46,10 +46,25 @@
#Require ldap-group cn=users,ou=groups,dc=sample,dc=net
# allow anynymous for viewing and checking out
# Enable find-grained access control using the access file.
# The specified file must be located under the repository/conf subdirectory.
# AuthzSVNRespsRelativeAccessFile requried subversion 1.7 or later.
# If you're using a older version, there are no automatic repostory
# protection according to the project type (public/private)
# You may have to use AuthzSVNAccessFile for manual control globally
# in such a case.
# AuthzSVNReposRelativeAccessFile access.conf
# Satisfy All
# allow anynymous/guest for viewing and checking out
<Limit GET HEAD OPTIONS REPORT PROPFIND>
Allow from all
#Satisfy any
# Use 'Allow from all' to allow anonymous access.
#Allow from all
# 'Required valid-user' is more strict in that it requires a valid
# user name and password. You may create a guest account to supplement
# anonymous access.
Require valid-user
</Limit>
# require authentication for other operations

View File

@ -179,4 +179,3 @@ force_project_delete = "no"
; Leave this empty for the default footer message
;------------------------------------------------------------------------------
footer = ""

View File

@ -18,6 +18,7 @@ CREATE TABLE project (
summary VARCHAR(255) NOT NULL,
description TEXT NOT NULL,
commitable CHAR(1) NOT NULL DEFAULT 'Y',
public CHAR(1) NOT NULL DEFAULT 'Y',
createdon DATETIME NOT NULL,
updatedon DATETIME NOT NULL,

View File

@ -0,0 +1,281 @@
#
# This file is not desinged to be used in conjuntion with other AAA providers.
# This file requires to be used alone as shown below for apache httpd2.
# You may change AuthName or SVNParentPath.
#
# <Location "/svn">
# DAV svn
# SVNParentPath "/var/lib/codepot/svnrepo"
# PerlAccessHandler Codepot::AccessHandler
# PerlAuthenHandler Codepot::AuthenHandler
# AuthType Basic
# AuthName "codepot"
# require valid-user
# </Location>
#
package Codepot::AccessHandler;
use strict;
use warnings;
use Apache2::Access ();
use Apache2::RequestUtil ();
use Apache2::RequestRec ();
use Apache2::Log;
use APR::Table;
use APR::Base64;
use Config::Simple;
use Net::LDAP;
use URI;
use DBI;
use Apache2::Const -compile => qw(OK DECLINED FORBIDDEN HTTP_UNAUTHORIZED HTTP_INTERNAL_SERVER_ERROR PROXYREQ_PROXY);
sub get_config
{
my $cfg = new Config::Simple();
if (!$cfg->read ('/etc/codepot/codepot.ini'))
{
return undef;
}
my $config = {
ldap_server_uri => $cfg->param ("ldap_server_uri"),
ldap_server_protocol_version => $cfg->param ("ldap_server_protocol_version"),
ldap_auth_mode => $cfg->param ("ldap_auth_mode"),
ldap_userid_format => $cfg->param ("ldap_userid_format"),
ldap_password_format => $cfg->param ("ldap_password_format"),
ldap_userid_admin_binddn => $cfg->param ("ldap_admin_binddn"),
ldap_userid_admin_password => $cfg->param ("ldap_admin_password"),
ldap_userid_search_base => $cfg->param ("ldap_userid_search_base"),
ldap_userid_search_fitler => $cfg->param ("ldap_userid_search_filter"),
database_hostname => $cfg->param ("database_hostname"),
database_username => $cfg->param ("database_username"),
database_password => $cfg->param ("database_password"),
database_name => $cfg->param ("database_name"),
database_driver => $cfg->param ("database_driver"),
database_prefix => $cfg->param ("database_prefix")
};
return $config;
}
sub format_string
{
my ($fmt, $userid, $password) = @_;
my $out = $fmt;
$out =~ s/\$\{userid\}/$userid/g;
$out =~ s/\$\{password\}/$password/g;
return $out;
}
sub authenticate
{
my ($cfg, $userid, $password) = @_;
my $binddn;
my $passwd;
# get the next line removed once you implement the second mode
if ($cfg->{ldap_auth_mode} == 2) { return -2; }
my $uri = URI->new ($cfg->{ldap_server_uri});
my $ldap = Net::LDAP->new ($uri->host,
scheme => $uri->scheme,
port => $uri->port,
version => $cfg->{ldap_server_protocol_version}
);
if (!defined($ldap))
{
# error
return -1;
}
if ($cfg->{ldap_auth_mode} == 2)
{
# YET TO BE WRITTEN
}
else
{
$binddn = format_string ($cfg->{ldap_userid_format}, $userid, $password);
}
$passwd = format_string ($cfg->{ldap_password_format}, $userid, $password);
my $res = $ldap->bind ($binddn, password => $passwd);
print $res->code;
print "\n";
$ldap->unbind();
return ($res->code == 0)? 1: 0;
}
sub open_database
{
my ($cfg) = @_;
my $dbtype = $cfg->{database_driver};
my $dbname = $cfg->{database_name};
my $dbhost = $cfg->{database_hostname};
my $dbh = DBI->connect(
"DBI:$dbtype:$dbname:$dbhost",
$cfg->{database_username},
$cfg->{database_password},
{ RaiseError => 0, PrintError => 0, AutoCommit => 0 }
);
return $dbh;
}
sub close_database
{
my ($dbh) = @_;
$dbh->disconnect ();
}
sub is_project_member
{
my ($dbh, $prefix, $projectid, $userid) = @_;
my $query = $dbh->prepare ("SELECT projectid FROM ${prefix}project_membership WHERE userid=? AND projectid=?");
if (!$query || !$query->execute ($userid, $projectid))
{
return (-1, $dbh->errstr());
}
my @row = $query->fetchrow_array;
return (((scalar(@row) > 0)? 1: 0), undef);
}
sub is_project_public
{
my ($dbh, $prefix, $projectid) = @_;
my $query = $dbh->prepare ("SELECT public FROM ${prefix}project WHERE id=?");
if (!$query || !$query->execute ($projectid))
{
return (-1, $dbh->errstr());
}
my @row = $query->fetchrow_array;
return (((scalar(@row) > 0 && $row[0] eq 'Y')? 1: 0), undef);
}
sub __handler
{
my ($r, $cfg, $dbh) = @_;
my ($empty, $base, $repo, $dummy) = split ('/', $r->uri(), 4);
my $method = uc($r->method());
my $author;
my $userid = undef;
my $password = undef;
if ($r->proxyreq() == Apache2::Const::PROXYREQ_PROXY)
{
$author = $r->headers_in->{'Proxy-Authorization'};
}
else
{
$author = $r->headers_in->{'Authorization'};
}
if (defined($author))
{
my ($rc, $pass) = $r->get_basic_auth_pw ();
if ($rc != Apache2::Const::OK) { return $rc; }
#$author = APR::Base64::decode((split(/ /,$author))[1]);
#($userid,$password) = split(/:/, $author);
$userid = $r->user();
$password = $pass;
}
if (!defined($userid)) { $userid = ""; }
if (!defined($password)) { $password = ""; }
if ($method eq "GET" ||
$method eq "HEAD" ||
$method eq "OPTIONS" ||
$method eq "REPORT" ||
$method eq "PROPFIND")
{
my ($public, $errmsg) = is_project_public ($dbh, $cfg->{database_prefix}, $repo);
if ($public <= -1)
{
# failed to contact the authentication server
$r->log_error ("Cannot check if a project is public - $errmsg");
return Apache2::Const::HTTP_INTERNAL_SERVER_ERROR;
}
elsif ($public >= 1)
{
return Apache2::Const::OK;
}
}
my $auth = authenticate ($cfg, $userid, $password);
if ($auth <= -1)
{
# failed to contact the authentication server
return Apache2::Const::HTTP_INTERNAL_SERVER_ERROR;
}
elsif ($auth == 0)
{
# authentication denied
$r->note_basic_auth_failure ();
return Apache2::Const::HTTP_UNAUTHORIZED;
}
# authentication successful.
my ($member, $errmsg) = is_project_member ($dbh, $cfg->{database_prefix}, $repo, $userid);
if ($member <= -1)
{
$r->log_error ("Cannot check project membership - $errmsg");
return Apache2::Const::HTTP_INTERNAL_SERVER_ERROR;
}
elsif ($member == 0)
{
# access denined
return Apache2::Const::FORBIDDEN;
}
else
{
# the user is a member of project. access granted.
return Apache2::Const::OK;
}
}
sub handler: method
{
my ($class, $r) = @_;
my $res;
my $cfg;
$cfg = get_config ();
if (!defined($cfg))
{
$r->log_error ("Cannot load configuration");
return Apache2::Const::HTTP_INTERNAL_SERVER_ERROR;
}
my $dbh = open_database ($cfg);
if (!defined($dbh))
{
$r->log_error ("Cannot open database");
return Apache2::Const::HTTP_INTERNAL_SERVER_ERROR;
}
$res = __handler ($r, $cfg, $dbh);
close_database ($dbh);
return $res;
}
1;

View File

@ -0,0 +1,12 @@
package Codepot::AuthenHandler;
use strict;
use warnings;
use Apache2::Const -compile => qw(OK DECLINED FORBIDDEN HTTP_UNAUTHORIZED HTTP_INTERNAL_SERVER_ERROR);
sub handler: method
{
return Apache2::Const::OK;
}
1;

View File

@ -45,7 +45,6 @@ class API extends Controller
$this->projects->projectIsCommitable ($projectid) === FALSE)? 'NO': 'YES';
}
function logCodeCommit ($type, $repo, $rev, $userid)
{
$this->check_access ();

View File

@ -57,6 +57,12 @@ class Code extends Controller
}
else
{
if ($project->public !== 'Y' && $login['id'] == '')
{
// non-public projects require sign-in.
redirect ("main/signin/" . $this->converter->AsciiTohex(current_url()));
}
$file = $this->subversion->getFile ($projectid, $path, $rev);
if ($file === FALSE)
{
@ -134,6 +140,12 @@ class Code extends Controller
}
else
{
if ($project->public !== 'Y' && $login['id'] == '')
{
// non-public projects require sign-in.
redirect ("main/signin/" . $this->converter->AsciiTohex(current_url()));
}
$file = $this->subversion->getBlame ($projectid, $path, $rev);
if ($file === FALSE)
{
@ -198,6 +210,12 @@ class Code extends Controller
}
else
{
if ($project->public !== 'Y' && $login['id'] == '')
{
// non-public projects require sign-in.
redirect ("main/signin/" . $this->converter->AsciiTohex(current_url()));
}
$file = $this->subversion->getHistory ($projectid, $path, $rev);
if ($file === FALSE)
{
@ -251,6 +269,12 @@ class Code extends Controller
}
else
{
if ($project->public !== 'Y' && $login['id'] == '')
{
// non-public projects require sign-in.
redirect ("main/signin/" . $this->converter->AsciiTohex(current_url()));
}
$file = $this->subversion->getRevHistory ($projectid, $path, $rev);
if ($file === FALSE)
{
@ -304,6 +328,12 @@ class Code extends Controller
}
else
{
if ($project->public !== 'Y' && $login['id'] == '')
{
// non-public projects require sign-in.
redirect ("main/signin/" . $this->converter->AsciiTohex(current_url()));
}
$file = $this->subversion->getDiff ($projectid, $path, $rev1, $rev2);
if ($file === FALSE)
{

View File

@ -46,6 +46,12 @@ class File extends Controller
}
else
{
if ($project->public !== 'Y' && $login['id'] == '')
{
// non-public projects require sign-in.
redirect ("main/signin/" . $this->converter->AsciiTohex(current_url()));
}
$files = $this->files->getAll ($login['id'], $project);
if ($files === FALSE)
{
@ -89,6 +95,12 @@ class File extends Controller
}
else
{
if ($project->public !== 'Y' && $login['id'] == '')
{
// non-public projects require sign-in.
redirect ("main/signin/" . $this->converter->AsciiTohex(current_url()));
}
$file = $this->files->get ($login['id'], $project, $name);
if ($file === FALSE)
{
@ -139,6 +151,12 @@ class File extends Controller
}
else
{
if ($project->public !== 'Y' && $login['id'] == '')
{
// non-public projects require sign-in.
redirect ("main/signin/" . $this->converter->AsciiTohex(current_url()));
}
$file = $this->files->get ($login['id'], $project, $name);
if ($file === FALSE)
{

View File

@ -48,6 +48,12 @@ class Issue extends Controller
}
else
{
if ($project->public !== 'Y' && $login['id'] == '')
{
// non-public projects require sign-in.
redirect ("main/signin/" . $this->converter->AsciiTohex(current_url()));
}
if ($filter == '')
{
$search->type = '';

View File

@ -136,6 +136,12 @@ class Project extends Controller
}
else
{
if ($project->public !== 'Y' && $login['id'] == '')
{
// non-public projects require sign-in.
redirect ("main/signin/" . $this->converter->AsciiTohex(current_url()));
}
$log_entries = $this->logs->getEntries (
0, CODEPOT_MAX_LOGS_IN_PROJECT_HOME, $projectid);
if ($log_entries === FALSE)
@ -170,6 +176,8 @@ class Project extends Controller
'project_description', 'description', 'required');
$this->form_validation->set_rules (
'project_commitable', 'commitable', 'alpha');
$this->form_validation->set_rules (
'project_public', 'public', 'alpha');
$this->form_validation->set_rules (
'project_members', 'members', 'required');
$this->form_validation->set_error_delimiters(
@ -190,6 +198,7 @@ class Project extends Controller
$project->summary = $this->input->post('project_summary');
$project->description = $this->input->post('project_description');
$project->commitable = $this->input->post('project_commitable');
$project->public = $this->input->post('project_public');
$project->members = $this->input->post('project_members');
// validate the form
@ -248,6 +257,7 @@ class Project extends Controller
$project->summary = '';
$project->description = '';
$project->commitable = 'Y';
$project->public = 'Y';
$project->members = $login['id'];
$this->_edit_project ($project, 'create', $login);

View File

@ -48,6 +48,12 @@ class Wiki extends Controller
}
else
{
if ($project->public !== 'Y' && $login['id'] == '')
{
// non-public projects require sign-in.
redirect ("main/signin/" . $this->converter->AsciiTohex(current_url()));
}
$wikis = $this->wikis->getAll ($login['id'], $project);
if ($wikis === FALSE)
{

View File

@ -60,6 +60,7 @@ $lang['Path'] = 'Path';
$lang['Priority'] = 'Priority';
$lang['Project'] = 'Project';
$lang['Projects'] = 'Projects';
$lang['Public'] = 'Public';
$lang['Purge'] = 'Purge';
$lang['Recently resolved issues'] = 'Recently resolved issues';
$lang['Repository'] = 'Repository';

View File

@ -56,6 +56,7 @@ $lang['Path'] = 'Path';
$lang['Priority'] = 'Pirority';
$lang['Project'] = 'Proyek';
$lang['Projects'] = 'Proyek';
$lang['Public'] = 'Public';
$lang['Purge'] = 'Purge';
$lang['Recently resolved issues'] = 'Recently resolved issues';
$lang['Repository'] = 'Repository';

View File

@ -60,6 +60,7 @@ $lang['Path'] = '경로';
$lang['Priority'] = '중요도';
$lang['Project'] = '프로젝트';
$lang['Projects'] = '프로젝트';
$lang['Public'] = '공개';
$lang['Purge'] = '정화하기';
$lang['Recently resolved issues'] = '최근해결이슈';
$lang['Repository'] = '저장소';

View File

@ -100,6 +100,7 @@ class ProjectModel extends Model
$this->db->set ('summary', $project->summary);
$this->db->set ('description', $project->description);
$this->db->set ('commitable', $project->commitable);
$this->db->set ('public', $project->public);
$this->db->set ('createdon', date('Y-m-d H:i:s'));
$this->db->set ('createdby', $userid);
$this->db->set ('updatedon', date('Y-m-d H:i:s'));
@ -209,6 +210,7 @@ class ProjectModel extends Model
$this->db->set ('summary', $project->summary);
$this->db->set ('description', $project->description);
$this->db->set ('commitable', $project->commitable);
$this->db->set ('public', $project->public);
$this->db->set ('updatedon', date('Y-m-d H:i:s'));
$this->db->set ('updatedby', $userid);
$this->db->update ('project');
@ -435,6 +437,17 @@ class ProjectModel extends Model
return ($count == 1)? TRUE: FALSE;
}
function projectIsPublic ($projectid)
{
$this->db->trans_start ();
$this->db->where ('id', $projectid);
$this->db->where ('public', 'Y');
$count = $this->db->count_all_results ('project');
$this->db->trans_complete ();
if ($this->db->trans_status() === FALSE) return FALSE;
return ($count == 1)? TRUE: FALSE;
}
function _delete_files_uploaded ($files)
{
foreach ($files as $file)

View File

@ -109,6 +109,10 @@ $this->load->view (
<?=form_label($this->lang->line('Commitable').': ', 'project_commitable')?>
<?=form_checkbox('project_commitable', 'Y', set_checkbox('project_commitable', $project->commitable, $project->commitable == 'Y'))?>
<?=form_error('project_commitable')?>
<?=form_label($this->lang->line('Public').': ', 'project_public')?>
<?=form_checkbox('project_public', 'Y', set_checkbox('project_public', $project->public, $project->public == 'Y'))?>
<?=form_error('project_public')?>
</div>
</div>

View File

@ -136,7 +136,7 @@ foreach ($latest_projects as $project)
printf (
htmlspecialchars ($fmt),
htmlspecialchars ($x['propname']),
anchor ("/site/userlog/{$code['author']}", htmlspecialchars ($x['author'])));
anchor ("/site/userlog/{$x['author']}", htmlspecialchars ($x['author'])));
}
else
{