Implements following rules: - Users - Users of any project - Users of all projects - Administrators - Signers The 'If No Rules Match' the access is implicitly considered to be 'DENY'. It is not possible to control access based on the Moon phase.
444 lines
13 KiB
PHP
Executable File
444 lines
13 KiB
PHP
Executable File
#!/usr/local/bin/php
|
|
<?php
|
|
|
|
$root = dirname(dirname(dirname(__FILE__)));
|
|
require_once $root.'/scripts/__init_script__.php';
|
|
|
|
function escape_name($name) {
|
|
return preg_replace('/[^A-Za-z0-9\-]/', '_', $name);
|
|
}
|
|
|
|
function startswith($string, $prefix) {
|
|
return substr($string, 0, strlen($prefix)) == $prefix;
|
|
}
|
|
|
|
function endswith($string, $suffix) {
|
|
$suffix_length = strlen($suffix);
|
|
return substr($string, strlen($string) - $suffix_length,
|
|
$suffix_length) == $suffix;
|
|
}
|
|
|
|
function write_ini_file($array, $file) {
|
|
$res = array();
|
|
foreach ($array as $key => $val) {
|
|
if (is_array($val)) {
|
|
$res[] = "[$key]";
|
|
foreach ($val as $skey => $sval) {
|
|
$res[] = "$skey = $sval";
|
|
}
|
|
$res[] = '';
|
|
} else {
|
|
$res[] = "$key = $val";
|
|
}
|
|
}
|
|
file_put_contents($file, implode("\n", $res));
|
|
}
|
|
|
|
function getProjectMembersPHIDs($viewer, $project_phid) {
|
|
$project = id(new PhabricatorProjectQuery())
|
|
->setViewer($viewer)
|
|
->needMembers(true)
|
|
->withPHIDs(array($project_phid))
|
|
->executeOne();
|
|
|
|
return $project->getMemberPHIDs();
|
|
}
|
|
|
|
// Get user's heys and put them to the configuration
|
|
function handleSingleUserPHID(
|
|
$keydir, $viewer, $userPHID, $system_keys, &$used_keys) {
|
|
$user = id(new PhabricatorPeopleQuery())
|
|
->setViewer($viewer)
|
|
->withPHIDs(array($userPHID))
|
|
->executeOne();
|
|
if (!$user) {
|
|
return array();
|
|
}
|
|
|
|
if ($user->getIsDisabled()) {
|
|
return array();
|
|
}
|
|
|
|
$keys = id(new PhabricatorAuthSSHKey())->loadAllWhere(
|
|
'objectPHID = %s',
|
|
$user->getPHID());
|
|
|
|
$members = array();
|
|
foreach ($keys as $key) {
|
|
$full_key_content =
|
|
$key->getKeyType().' '.
|
|
$key->getKeyBody().' '.
|
|
$key->getKeyComment()."\n";
|
|
|
|
if (array_key_exists($full_key_content, $system_keys)) {
|
|
$members[] = $system_keys[$full_key_content];
|
|
} else {
|
|
$escaped_key_name = escape_name($key->getName());
|
|
$member = 'PHAB_'.$user->getUserName().
|
|
'_'.$escaped_key_name.
|
|
'_'.$key->getID();
|
|
$members[] = $member;
|
|
if (!array_key_exists($member, $used_keys)) {
|
|
$used_keys[$member] = true;
|
|
file_put_contents("$keydir/$member.pub", $full_key_content);
|
|
}
|
|
}
|
|
}
|
|
return $members;
|
|
}
|
|
|
|
function handleUsersPolicyRule(
|
|
$keydir, $viewer, $rule, $system_keys, &$used_keys) {
|
|
$members = array();
|
|
foreach ($rule['value'] as $userPHID) {
|
|
$members = array_merge($members,
|
|
handleSingleUserPHID($keydir, $viewer, $userPHID,
|
|
$system_keys, $used_keys));
|
|
}
|
|
return $members;
|
|
}
|
|
|
|
function handleProjectsPolicyRule(
|
|
$keydir, $viewer, $rule, $system_keys, &$used_keys) {
|
|
$members = array();
|
|
foreach ($rule['value'] as $projectPHID) {
|
|
$memberPHIDs = getProjectMembersPHIDs($viewer, $projectPHID);
|
|
foreach ($memberPHIDs as $userPHID) {
|
|
$members = array_merge($members,
|
|
handleSingleUserPHID($keydir, $viewer, $userPHID,
|
|
$system_keys, $used_keys));
|
|
}
|
|
}
|
|
return $members;
|
|
}
|
|
|
|
function handleProjectsAllPolicyRule(
|
|
$keydir, $viewer, $rule, $system_keys, &$used_keys) {
|
|
$is_first_project = true;
|
|
$allowed_members_phids = array();
|
|
foreach ($rule['value'] as $project_phid) {
|
|
$memberPHIDs = getProjectMembersPHIDs($viewer, $project_phid);
|
|
if ($is_first_project) {
|
|
$allowed_members_phids = $memberPHIDs;
|
|
$is_first_project = false;
|
|
} else {
|
|
$allowed_members_phids = array_intersect(
|
|
$allowed_members_phids, $memberPHIDs);
|
|
}
|
|
}
|
|
|
|
$members = array();
|
|
foreach ($allowed_members_phids as $userPHID) {
|
|
$members = array_merge($members,
|
|
handleSingleUserPHID($keydir, $viewer, $userPHID,
|
|
$system_keys, $used_keys));
|
|
}
|
|
return $members;
|
|
}
|
|
|
|
function handleAdministratorsPolicyRule(
|
|
$keydir, $viewer, $rule, $system_keys, &$used_keys) {
|
|
$administrators = id(new PhabricatorPeopleQuery())
|
|
->setViewer($viewer)
|
|
->withIsAdmin(true)
|
|
->execute();
|
|
|
|
$members = array();
|
|
foreach ($administrators as $administrator) {
|
|
$members = array_merge($members,
|
|
handleSingleUserPHID($keydir, $viewer, $administrator->getPHID(),
|
|
$system_keys, $used_keys));
|
|
}
|
|
return $members;
|
|
}
|
|
|
|
function handleLegalpadSingleDocument(
|
|
$keydir, $viewer, $document, $system_keys, &$used_keys) {
|
|
if ($document->getSignatureType() !=
|
|
LegalpadDocument::SIGNATURE_TYPE_INDIVIDUAL) {
|
|
return array();
|
|
}
|
|
|
|
$members = array();
|
|
foreach ($document->getSignatures() as $signature) {
|
|
if ($signature->getSignatureType() !=
|
|
LegalpadDocument::SIGNATURE_TYPE_INDIVIDUAL) {
|
|
continue;
|
|
}
|
|
$members = array_merge($members,
|
|
handleSingleUserPHID($keydir, $viewer, $signature->getSignerPHID(),
|
|
$system_keys, $used_keys));
|
|
}
|
|
return $members;
|
|
}
|
|
|
|
function handleLegalpadSignaturePolicyRule(
|
|
$keydir, $viewer, $rule, $system_keys, &$used_keys) {
|
|
$documents = id(new LegalpadDocumentQuery())
|
|
->setViewer($viewer)
|
|
->withPHIDs($rule['value'])
|
|
->needSignatures(true)
|
|
->execute();
|
|
|
|
$members = array();
|
|
foreach ($documents as $document) {
|
|
$members = array_merge(
|
|
$members,
|
|
handleLegalpadSingleDocument(
|
|
$keydir, $viewer, $document, $system_keys, $used_keys));
|
|
}
|
|
return $members;
|
|
}
|
|
|
|
function handleCustomPolicy(
|
|
$keydir, $viewer, $policy, $system_keys, &$used_keys) {
|
|
$members = array();
|
|
$rules = $policy->getRules();
|
|
foreach ($rules as $rule) {
|
|
// Everyone is denied by default anyway
|
|
if ($rule['action'] != 'allow') {
|
|
continue;
|
|
}
|
|
|
|
$policy_members = array();
|
|
$rule_type = $rule['rule'];
|
|
if ($rule_type == 'PhabricatorPolicyRuleUsers') {
|
|
$policy_members = handleUsersPolicyRule(
|
|
$keydir, $viewer, $rule, $system_keys, $used_keys);
|
|
} else if ($rule_type == 'PhabricatorProjectsPolicyRule') {
|
|
$policy_members = handleProjectsPolicyRule(
|
|
$keydir, $viewer, $rule, $system_keys, $used_keys);
|
|
} else if ($rule_type == 'PhabricatorProjectsAllPolicyRule') {
|
|
$policy_members = handleProjectsAllPolicyRule(
|
|
$keydir, $viewer, $rule, $system_keys, $used_keys);
|
|
} else if ($rule_type == 'PhabricatorAdministratorsPolicyRule') {
|
|
$policy_members = handleAdministratorsPolicyRule(
|
|
$keydir, $viewer, $rule, $system_keys, $used_keys);
|
|
} else if ($rule_type == 'PhabricatorLegalpadSignaturePolicyRule') {
|
|
$policy_members = handleLegalpadSignaturePolicyRule(
|
|
$keydir, $viewer, $rule, $system_keys, $used_keys);
|
|
}
|
|
|
|
$members = array_merge($members, $policy_members);
|
|
}
|
|
|
|
return $members;
|
|
}
|
|
|
|
// Parse repository and put it's members to the config file
|
|
function handleSingleRepository(
|
|
$keydir, $viewer, $repository, $all_repositories, $system_keys,
|
|
&$new_configuration, &$used_keys) {
|
|
$policies = PhabricatorPolicyQuery::loadPolicies(
|
|
$viewer,
|
|
$repository);
|
|
|
|
$pushable = $policies[DiffusionPushCapability::CAPABILITY];
|
|
$type = $pushable->getType();
|
|
|
|
$members = array();
|
|
|
|
if ($type == PhabricatorPolicyType::TYPE_PROJECT) {
|
|
$project = id(new PhabricatorProjectQuery())
|
|
->setViewer($viewer)
|
|
->needMembers(true)
|
|
->withPHIDs(array($pushable->getPHID()))
|
|
->executeOne();
|
|
|
|
$memberPHIDs = $project->getMemberPHIDs();
|
|
foreach ($memberPHIDs as $memberPHID) {
|
|
$members = array_merge($members,
|
|
handleSingleUserPHID($keydir, $viewer, $memberPHID,
|
|
$system_keys, $used_keys));
|
|
}
|
|
} else if ($type == PhabricatorPolicyType::TYPE_USER) {
|
|
$members = handleSingleUserPHID(
|
|
$keydir, $viewer, $pushable->getPHID(), $system_keys, $used_keys);
|
|
} else if ($type == PhabricatorPolicyType::TYPE_CUSTOM) {
|
|
$members = handleCustomPolicy(
|
|
$keydir, $viewer, $pushable, $system_keys, $used_keys);
|
|
} else {
|
|
/* pass */
|
|
}
|
|
|
|
if (count($members)) {
|
|
$uri = $repository->getRemoteURI();
|
|
$repository_name = basename($uri, '.git');
|
|
$escaped_repository_name = escape_name($repository->getName());
|
|
$group_name = "PHAB_${escaped_repository_name}";
|
|
$values = array();
|
|
$values['members'] = join(' ', $members);
|
|
$values['readonly'] = join(' ', $all_repositories);
|
|
$values['writable'] = $repository_name;
|
|
$new_configuration["group $group_name"] = $values;
|
|
}
|
|
}
|
|
|
|
// Remove groups from previous automated configuration built
|
|
function getCleanOldConfiguration($old_configuration) {
|
|
$new_configuration = array();
|
|
foreach ($old_configuration as $group => $values) {
|
|
if (!startswith($group, 'group PHAB')) {
|
|
$new_configuration[$group] = $values;
|
|
}
|
|
}
|
|
return $new_configuration;
|
|
}
|
|
|
|
// Get non-phab keys
|
|
function getSystemPublicKeys($keydir) {
|
|
$files = scandir($keydir);
|
|
$system_keys = array();
|
|
foreach ($files as $file) {
|
|
if (!startswith($file, "PHAB") && endswith($file, '.pub')) {
|
|
$key = file_get_contents("$keydir/$file");
|
|
$system_keys[$key] = basename($file, '.pub');
|
|
}
|
|
}
|
|
return $system_keys;
|
|
}
|
|
|
|
// Remove unused public keys
|
|
function removeUnusedPublicKeys($keydir, $used_keys) {
|
|
$files = scandir($keydir);
|
|
foreach ($files as $file) {
|
|
if (startswith($file, "PHAB")) {
|
|
$member = basename($file, '.pub');
|
|
if (!array_key_exists($member, $used_keys)) {
|
|
unlink("$keydir/$file");
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
function rebuildConfiguration($gitosis_root) {
|
|
$keydir = "$gitosis_root/keydir";
|
|
$configuration_file = "$gitosis_root/gitosis.conf";
|
|
|
|
if (!file_exists($configuration_file)) {
|
|
print("Not found: $configuration_file\n");
|
|
return false;
|
|
}
|
|
|
|
$viewer = id(new PhabricatorUser())
|
|
->loadOneWhere('username = %s', 'sergey');
|
|
|
|
$old_configuration = parse_ini_file(
|
|
$configuration_file, true, INI_SCANNER_RAW);
|
|
|
|
$new_configuration = getCleanOldConfiguration(
|
|
$old_configuration);
|
|
|
|
// Get "system" keys to re-use if phab account uses the
|
|
// same public key
|
|
$system_keys = getSystemPublicKeys($keydir);
|
|
|
|
// Get list of all repos which is awailable for read
|
|
$all_repositories = array();
|
|
foreach ($old_configuration as $group => $values) {
|
|
if (startswith($group, 'repo')) {
|
|
$repository_name = substr($group, 5, strlen($group) - 5);
|
|
if ($repository_name == 'gitosis-admin')
|
|
continue;
|
|
$all_repositories[] = $repository_name;
|
|
}
|
|
}
|
|
|
|
// Fill in new configuration and keys
|
|
$used_keys = array();
|
|
$repositories = id(new PhabricatorRepositoryQuery())
|
|
->setViewer($viewer)
|
|
->execute();
|
|
|
|
foreach ($repositories as $repository_id => $repository) {
|
|
$type = $repository->getVersionControlSystem();
|
|
if ($type == PhabricatorRepositoryType::REPOSITORY_TYPE_GIT) {
|
|
handleSingleRepository(
|
|
$keydir, $viewer, $repository, $all_repositories, $system_keys,
|
|
$new_configuration, $used_keys);
|
|
}
|
|
}
|
|
|
|
write_ini_file($new_configuration, $configuration_file);
|
|
removeUnusedPublicKeys($keydir, $used_keys);
|
|
|
|
return true;
|
|
}
|
|
|
|
function getGitCommand($repository) {
|
|
$git_dir = realpath("$repository/.git");
|
|
$git = "git --git-dir='$git_dir'";
|
|
$git .= ' --work-tree='.realpath($repository);
|
|
return $git;
|
|
}
|
|
|
|
function runGitCommand($repository, $arguments,
|
|
&$output=null, &$return_var=null) {
|
|
$git = getGitCommand($repository);
|
|
$git .= " $arguments";
|
|
exec($git, $output, $return_var);
|
|
return $return_var == 0;
|
|
}
|
|
|
|
function runGitSshCommand($repository, $key, $arguments,
|
|
&$output=null, &$return_var=null) {
|
|
$gitx_ssh = realpath(dirname(__FILE__) . "/gitx-ssh");
|
|
$abs_key = realpath($key);
|
|
$git = "SSH_KEYFILE=$abs_key GIT_SSH=$gitx_ssh ";
|
|
$git .= getGitCommand($repository);
|
|
$git .= " $arguments";
|
|
exec($git, $output, $return_var);
|
|
return $return_var == 0;
|
|
}
|
|
|
|
function repositoryPull($repository, $key) {
|
|
return runGitSshCommand($repository, $key, 'pull');
|
|
}
|
|
|
|
function repositoryCommitAll($repository, $author, $message) {
|
|
if (!runGitCommand(
|
|
$repository, 'ls-files --other --exclude-standard', $untracked_files)) {
|
|
return false;
|
|
}
|
|
if (count($untracked_files)) {
|
|
$flat_files = join(' ', $untracked_files);
|
|
if (!runGitCommand($repository, "add $flat_files")) {
|
|
return false;
|
|
}
|
|
}
|
|
runGitCommand($repository, "update-index -q --refresh", $output);
|
|
runGitCommand($repository, "diff-index --name-only HEAD --", $output);
|
|
if (count($output)) {
|
|
return runGitCommand(
|
|
$repository, "commit --author='$author' -a -m '$message'", $output);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
if (count($argv) != 3) {
|
|
print("Usage: {$argv[0]} /path/to/gitosis-admin /path/to/id_rsa.pub\n");
|
|
exit(1);
|
|
}
|
|
|
|
$gitosis_root = $argv[1];
|
|
$key = $argv[2];
|
|
|
|
if (!repositoryPull($gitosis_root, $key)) {
|
|
print("Failed to pull changes from server.\n");
|
|
exit(1);
|
|
}
|
|
|
|
if (!rebuildConfiguration($gitosis_root)) {
|
|
exit(1);
|
|
}
|
|
|
|
if (!repositoryCommitAll(
|
|
$gitosis_root, 'Rebuild Gitadmin <null@git.blender.org>',
|
|
'Update to correspond changes in Phabricator')) {
|
|
print("Failed to commit changes.\n");
|
|
exit(1);
|
|
}
|
|
|
|
runGitSshCommand($gitosis_root, $key, 'push origin master');
|
|
?>
|