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 Group: Applications/Utilities
Source0: %{name}-%{version}.tar.gz 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: #BuildRequires:
BuildRoot: %{_tmppath}/%{name}-%{version}-root BuildRoot: %{_tmppath}/%{name}-%{version}-root
@ -50,6 +50,8 @@ rm -rf $RPM_BUILD_ROOT
/etc/codepot/post-commit /etc/codepot/post-commit
/etc/codepot/pre-revprop-change /etc/codepot/pre-revprop-change
/etc/codepot/post-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/svnrepo
%attr(-,apache,apache) /var/lib/codepot/files %attr(-,apache,apache) /var/lib/codepot/files

View File

@ -1,7 +1,7 @@
cfgdir=$(CFGDIR) cfgdir=$(CFGDIR)
cfg_DATA = codepot.ini codepot.mysql codepot.a2ldap 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) EXTRA_DIST = $(cfg_DATA) $(cfg_SCRIPTS)

View File

@ -46,10 +46,25 @@
#Require ldap-group cn=users,ou=groups,dc=sample,dc=net #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> <Limit GET HEAD OPTIONS REPORT PROPFIND>
Allow from all # Use 'Allow from all' to allow anonymous access.
#Satisfy any #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> </Limit>
# require authentication for other operations # require authentication for other operations

View File

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

View File

@ -18,6 +18,7 @@ CREATE TABLE project (
summary VARCHAR(255) NOT NULL, summary VARCHAR(255) NOT NULL,
description TEXT NOT NULL, description TEXT NOT NULL,
commitable CHAR(1) NOT NULL DEFAULT 'Y', commitable CHAR(1) NOT NULL DEFAULT 'Y',
public CHAR(1) NOT NULL DEFAULT 'Y',
createdon DATETIME NOT NULL, createdon DATETIME NOT NULL,
updatedon 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'; $this->projects->projectIsCommitable ($projectid) === FALSE)? 'NO': 'YES';
} }
function logCodeCommit ($type, $repo, $rev, $userid) function logCodeCommit ($type, $repo, $rev, $userid)
{ {
$this->check_access (); $this->check_access ();

View File

@ -57,6 +57,12 @@ class Code extends Controller
} }
else 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); $file = $this->subversion->getFile ($projectid, $path, $rev);
if ($file === FALSE) if ($file === FALSE)
{ {
@ -134,6 +140,12 @@ class Code extends Controller
} }
else 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); $file = $this->subversion->getBlame ($projectid, $path, $rev);
if ($file === FALSE) if ($file === FALSE)
{ {
@ -198,6 +210,12 @@ class Code extends Controller
} }
else 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); $file = $this->subversion->getHistory ($projectid, $path, $rev);
if ($file === FALSE) if ($file === FALSE)
{ {
@ -251,6 +269,12 @@ class Code extends Controller
} }
else 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); $file = $this->subversion->getRevHistory ($projectid, $path, $rev);
if ($file === FALSE) if ($file === FALSE)
{ {
@ -304,6 +328,12 @@ class Code extends Controller
} }
else 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); $file = $this->subversion->getDiff ($projectid, $path, $rev1, $rev2);
if ($file === FALSE) if ($file === FALSE)
{ {

View File

@ -46,6 +46,12 @@ class File extends Controller
} }
else 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); $files = $this->files->getAll ($login['id'], $project);
if ($files === FALSE) if ($files === FALSE)
{ {
@ -89,6 +95,12 @@ class File extends Controller
} }
else 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); $file = $this->files->get ($login['id'], $project, $name);
if ($file === FALSE) if ($file === FALSE)
{ {
@ -139,6 +151,12 @@ class File extends Controller
} }
else 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); $file = $this->files->get ($login['id'], $project, $name);
if ($file === FALSE) if ($file === FALSE)
{ {

View File

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

View File

@ -136,6 +136,12 @@ class Project extends Controller
} }
else 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 ( $log_entries = $this->logs->getEntries (
0, CODEPOT_MAX_LOGS_IN_PROJECT_HOME, $projectid); 0, CODEPOT_MAX_LOGS_IN_PROJECT_HOME, $projectid);
if ($log_entries === FALSE) if ($log_entries === FALSE)
@ -170,6 +176,8 @@ class Project extends Controller
'project_description', 'description', 'required'); 'project_description', 'description', 'required');
$this->form_validation->set_rules ( $this->form_validation->set_rules (
'project_commitable', 'commitable', 'alpha'); 'project_commitable', 'commitable', 'alpha');
$this->form_validation->set_rules (
'project_public', 'public', 'alpha');
$this->form_validation->set_rules ( $this->form_validation->set_rules (
'project_members', 'members', 'required'); 'project_members', 'members', 'required');
$this->form_validation->set_error_delimiters( $this->form_validation->set_error_delimiters(
@ -190,6 +198,7 @@ class Project extends Controller
$project->summary = $this->input->post('project_summary'); $project->summary = $this->input->post('project_summary');
$project->description = $this->input->post('project_description'); $project->description = $this->input->post('project_description');
$project->commitable = $this->input->post('project_commitable'); $project->commitable = $this->input->post('project_commitable');
$project->public = $this->input->post('project_public');
$project->members = $this->input->post('project_members'); $project->members = $this->input->post('project_members');
// validate the form // validate the form
@ -248,6 +257,7 @@ class Project extends Controller
$project->summary = ''; $project->summary = '';
$project->description = ''; $project->description = '';
$project->commitable = 'Y'; $project->commitable = 'Y';
$project->public = 'Y';
$project->members = $login['id']; $project->members = $login['id'];
$this->_edit_project ($project, 'create', $login); $this->_edit_project ($project, 'create', $login);

View File

@ -48,6 +48,12 @@ class Wiki extends Controller
} }
else 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); $wikis = $this->wikis->getAll ($login['id'], $project);
if ($wikis === FALSE) if ($wikis === FALSE)
{ {

View File

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

View File

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

View File

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

View File

@ -100,6 +100,7 @@ class ProjectModel extends Model
$this->db->set ('summary', $project->summary); $this->db->set ('summary', $project->summary);
$this->db->set ('description', $project->description); $this->db->set ('description', $project->description);
$this->db->set ('commitable', $project->commitable); $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 ('createdon', date('Y-m-d H:i:s'));
$this->db->set ('createdby', $userid); $this->db->set ('createdby', $userid);
$this->db->set ('updatedon', date('Y-m-d H:i:s')); $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 ('summary', $project->summary);
$this->db->set ('description', $project->description); $this->db->set ('description', $project->description);
$this->db->set ('commitable', $project->commitable); $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 ('updatedon', date('Y-m-d H:i:s'));
$this->db->set ('updatedby', $userid); $this->db->set ('updatedby', $userid);
$this->db->update ('project'); $this->db->update ('project');
@ -435,6 +437,17 @@ class ProjectModel extends Model
return ($count == 1)? TRUE: FALSE; 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) function _delete_files_uploaded ($files)
{ {
foreach ($files as $file) foreach ($files as $file)

View File

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

View File

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