diff --git a/codepot/codepot.spec.in b/codepot/codepot.spec.in index cb1917b2..aba7bf69 100644 --- a/codepot/codepot.spec.in +++ b/codepot/codepot.spec.in @@ -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 diff --git a/codepot/etc/Makefile.am b/codepot/etc/Makefile.am index b9c031a5..f4db0bed 100644 --- a/codepot/etc/Makefile.am +++ b/codepot/etc/Makefile.am @@ -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) diff --git a/codepot/etc/codepot.a2ldap.in b/codepot/etc/codepot.a2ldap.in index d36d8e10..1bb66a48 100644 --- a/codepot/etc/codepot.a2ldap.in +++ b/codepot/etc/codepot.a2ldap.in @@ -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 - 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 # require authentication for other operations diff --git a/codepot/etc/codepot.ini.in b/codepot/etc/codepot.ini.in index e29feddc..692c36cc 100644 --- a/codepot/etc/codepot.ini.in +++ b/codepot/etc/codepot.ini.in @@ -179,4 +179,3 @@ force_project_delete = "no" ; Leave this empty for the default footer message ;------------------------------------------------------------------------------ footer = "" - diff --git a/codepot/etc/codepot.mysql b/codepot/etc/codepot.mysql index d79f26cb..e94780ec 100644 --- a/codepot/etc/codepot.mysql +++ b/codepot/etc/codepot.mysql @@ -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, @@ -38,7 +39,7 @@ CREATE TABLE project_membership ( CREATE TABLE wiki ( projectid VARCHAR(32) NOT NULL, name VARCHAR(255) NOT NULL, - text TEXT NOT NULL, + text TEXT NOT NULL, createdon DATETIME NOT NULL, updatedon DATETIME NOT NULL, diff --git a/codepot/etc/perl/Codepot/AccessHandler.pm b/codepot/etc/perl/Codepot/AccessHandler.pm new file mode 100644 index 00000000..17050030 --- /dev/null +++ b/codepot/etc/perl/Codepot/AccessHandler.pm @@ -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. +# +# +# DAV svn +# SVNParentPath "/var/lib/codepot/svnrepo" +# PerlAccessHandler Codepot::AccessHandler +# PerlAuthenHandler Codepot::AuthenHandler +# AuthType Basic +# AuthName "codepot" +# require valid-user +# +# + +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; diff --git a/codepot/etc/perl/Codepot/AuthenHandler.pm b/codepot/etc/perl/Codepot/AuthenHandler.pm new file mode 100644 index 00000000..00bc7f55 --- /dev/null +++ b/codepot/etc/perl/Codepot/AuthenHandler.pm @@ -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; diff --git a/codepot/src/codepot/controllers/api.php b/codepot/src/codepot/controllers/api.php index b217340a..4cd0f317 100644 --- a/codepot/src/codepot/controllers/api.php +++ b/codepot/src/codepot/controllers/api.php @@ -45,7 +45,6 @@ class API extends Controller $this->projects->projectIsCommitable ($projectid) === FALSE)? 'NO': 'YES'; } - function logCodeCommit ($type, $repo, $rev, $userid) { $this->check_access (); diff --git a/codepot/src/codepot/controllers/code.php b/codepot/src/codepot/controllers/code.php index f35aa679..a1104d33 100644 --- a/codepot/src/codepot/controllers/code.php +++ b/codepot/src/codepot/controllers/code.php @@ -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) { @@ -124,7 +130,7 @@ class Code extends Controller { $data['message'] = 'DATABASE ERROR'; $this->load->view ($this->VIEW_ERROR, $data); - } + } else if ($project === NULL) { $data['message'] = @@ -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) { diff --git a/codepot/src/codepot/controllers/file.php b/codepot/src/codepot/controllers/file.php index 519a547d..af25fcf7 100644 --- a/codepot/src/codepot/controllers/file.php +++ b/codepot/src/codepot/controllers/file.php @@ -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) { @@ -77,7 +83,7 @@ class File extends Controller $project = $this->projects->get ($projectid); if ($project === FALSE) { - $data['message'] = 'DATABASE ERROR'; + $data['message'] = 'DATABASE ERROR'; $this->load->view ($this->VIEW_ERROR, $data); } else if ($project === NULL) @@ -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) { diff --git a/codepot/src/codepot/controllers/issue.php b/codepot/src/codepot/controllers/issue.php index 2191320a..e970137a 100644 --- a/codepot/src/codepot/controllers/issue.php +++ b/codepot/src/codepot/controllers/issue.php @@ -38,7 +38,7 @@ class Issue extends Controller { $data['message'] = 'DATABASE ERROR'; $this->load->view ($this->VIEW_ERROR, $data); - } + } else if ($project === NULL) { $data['message'] = @@ -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 = ''; diff --git a/codepot/src/codepot/controllers/project.php b/codepot/src/codepot/controllers/project.php index 6d0918d2..12b41a11 100644 --- a/codepot/src/codepot/controllers/project.php +++ b/codepot/src/codepot/controllers/project.php @@ -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); diff --git a/codepot/src/codepot/controllers/wiki.php b/codepot/src/codepot/controllers/wiki.php index abd82e86..6e01eafb 100644 --- a/codepot/src/codepot/controllers/wiki.php +++ b/codepot/src/codepot/controllers/wiki.php @@ -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) { diff --git a/codepot/src/codepot/language/english/common_lang.php b/codepot/src/codepot/language/english/common_lang.php index 22b668c3..38b17f6f 100644 --- a/codepot/src/codepot/language/english/common_lang.php +++ b/codepot/src/codepot/language/english/common_lang.php @@ -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'; diff --git a/codepot/src/codepot/language/indonesian/common_lang.php b/codepot/src/codepot/language/indonesian/common_lang.php index 28162f01..b1efc997 100644 --- a/codepot/src/codepot/language/indonesian/common_lang.php +++ b/codepot/src/codepot/language/indonesian/common_lang.php @@ -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'; diff --git a/codepot/src/codepot/language/korean/common_lang.php b/codepot/src/codepot/language/korean/common_lang.php index b92bf56f..1d5b49f0 100644 --- a/codepot/src/codepot/language/korean/common_lang.php +++ b/codepot/src/codepot/language/korean/common_lang.php @@ -60,6 +60,7 @@ $lang['Path'] = '경로'; $lang['Priority'] = '중요도'; $lang['Project'] = '프로젝트'; $lang['Projects'] = '프로젝트'; +$lang['Public'] = '공개'; $lang['Purge'] = '정화하기'; $lang['Recently resolved issues'] = '최근해결이슈'; $lang['Repository'] = '저장소'; diff --git a/codepot/src/codepot/models/projectmodel.php b/codepot/src/codepot/models/projectmodel.php index f1be016d..0dca7dd0 100644 --- a/codepot/src/codepot/models/projectmodel.php +++ b/codepot/src/codepot/models/projectmodel.php @@ -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) diff --git a/codepot/src/codepot/views/project_edit.php b/codepot/src/codepot/views/project_edit.php index de81673c..a6f58718 100644 --- a/codepot/src/codepot/views/project_edit.php +++ b/codepot/src/codepot/views/project_edit.php @@ -107,8 +107,12 @@ $this->load->view (
lang->line('Commitable').': ', 'project_commitable')?> - commitable, $project->commitable == 'Y'))?> + commitable, $project->commitable == 'Y'))?> + + lang->line('Public').': ', 'project_public')?> + public, $project->public == 'Y'))?> +
diff --git a/codepot/src/codepot/views/site_home.php b/codepot/src/codepot/views/site_home.php index ab3d87db..27b5b676 100644 --- a/codepot/src/codepot/views/site_home.php +++ b/codepot/src/codepot/views/site_home.php @@ -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 {