Started the work on issue 3, git synchronization.
This commit is contained in:
parent
509d6b6aa9
commit
00f3b08ec6
41
scripts/gitserve.php
Normal file
41
scripts/gitserve.php
Normal file
@ -0,0 +1,41 @@
|
||||
<?php
|
||||
/* -*- tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
|
||||
/*
|
||||
# ***** BEGIN LICENSE BLOCK *****
|
||||
# This file is part of InDefero, an open source project management application.
|
||||
# Copyright (C) 2008 Céondo Ltd and contributors.
|
||||
#
|
||||
# InDefero is free software; you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation; either version 2 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# InDefero is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program; if not, write to the Free Software
|
||||
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
#
|
||||
# ***** END LICENSE BLOCK ***** */
|
||||
|
||||
/**
|
||||
* This script is used to control the access to the git repositories
|
||||
* using a restricted shell access.
|
||||
*
|
||||
* The only argument must be the login of the user.
|
||||
*/
|
||||
// Set the include path to have Pluf and IDF in it.
|
||||
$indefero_path = dirname(__FILE__).'/../src';
|
||||
//$pluf_path = '/path/to/pluf/src';
|
||||
set_include_path(get_include_path()
|
||||
.PATH_SEPARATOR.$indefero_path
|
||||
// .PATH_SEPARATOR.$pluf_path
|
||||
);
|
||||
require 'Pluf.php';
|
||||
Pluf::start(dirname(__FILE__).'/../src/IDF/conf/idf.php');
|
||||
Pluf_Dispatcher::loadControllers(Pluf::f('idf_views'));
|
||||
IDF_Plugin_SyncGit_Serve::main($argv, $_ENV);
|
||||
|
253
src/IDF/Plugin/SyncGit/Serve.php
Normal file
253
src/IDF/Plugin/SyncGit/Serve.php
Normal file
@ -0,0 +1,253 @@
|
||||
<?php
|
||||
/* -*- tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
|
||||
/*
|
||||
# ***** BEGIN LICENSE BLOCK *****
|
||||
# This file is part of InDefero, an open source project management application.
|
||||
# Copyright (C) 2008 Céondo Ltd and contributors.
|
||||
#
|
||||
# InDefero is free software; you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation; either version 2 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# InDefero is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program; if not, write to the Free Software
|
||||
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
#
|
||||
# ***** END LICENSE BLOCK ***** */
|
||||
|
||||
/**
|
||||
* Main application to serve git repositories through a restricted SSH
|
||||
* access.
|
||||
*/
|
||||
class IDF_Plugin_SyncGit_Serve
|
||||
{
|
||||
/**
|
||||
* Regular expression to match the path in the git command.
|
||||
*/
|
||||
public $preg = '#^\'/*(?P<path>[a-zA-Z0-9][a-zA-Z0-9@._-]*(/[a-zA-Z0-9][a-zA-Z0-9@._-]*)*)\'$#';
|
||||
|
||||
public $commands_readonly = array('git-upload-pack', 'git upload-pack');
|
||||
public $commands_write = array('git-receive-pack', 'git receive-pack');
|
||||
|
||||
/**
|
||||
* Check that the command is authorized.
|
||||
*/
|
||||
|
||||
|
||||
/**
|
||||
* Serve a git request.
|
||||
*
|
||||
* @param string Username.
|
||||
* @param string Command to be run.
|
||||
*/
|
||||
public function serve($username, $cmd)
|
||||
{
|
||||
if (false !== strpos($cmd, "\n")) {
|
||||
throw new Exception('Command may not contain newline.');
|
||||
}
|
||||
$splitted = preg_split('/\s/', $cmd, 2);
|
||||
if (count($splitted) != 2) {
|
||||
throw new Exception('Unknown command denied.');
|
||||
}
|
||||
if ($splitted[0] == 'git') {
|
||||
$sub_splitted = preg_split('/\s/', $splitted[1], 2);
|
||||
if (count($sub_splitted) != 2) {
|
||||
throw new Exception('Unknown command denied.');
|
||||
}
|
||||
$verb = sprintf('%s %s', $splitted[0], $sub_splitted[0]);
|
||||
$args = $sub_splitted[1];
|
||||
} else {
|
||||
$verb = $splitted[0];
|
||||
$args = $splitted[1];
|
||||
}
|
||||
if (!in_array($verb, $this->commands_write)
|
||||
and !in_array($verb, $this->commands_readonly)) {
|
||||
throw new Exception('Unknown command denied.');
|
||||
}
|
||||
if (!preg_match($this->preg, $args, $matches)) {
|
||||
throw new Exception('Arguments to command look dangerous.');
|
||||
}
|
||||
$path = $matches['path'];
|
||||
// Check read/write rights
|
||||
$new_path = $this->haveAccess($username, $path, 'writable');
|
||||
if ($new_path == false) {
|
||||
$new_path = $this->haveAccess($username, $path, 'readonly');
|
||||
if ($new_path == false) {
|
||||
throw new Exception('Repository read access denied.');
|
||||
}
|
||||
if (in_array($verb, $this->commands_write)) {
|
||||
throw new Exception('Repository write access denied.');
|
||||
}
|
||||
}
|
||||
list($topdir, $relpath) = $new_path;
|
||||
$repopath = sprintf('%s.git', $relpath);
|
||||
$fullpath = $topdir.DIRECTORY_SEPARATOR.$repopath;
|
||||
if (!file_exists($fullpath)
|
||||
and in_array($verb, $this->commands_write)) {
|
||||
// it doesn't exist on the filesystem, but the
|
||||
// configuration refers to it, we're serving a write
|
||||
// request, and the user is authorized to do that: create
|
||||
// the repository on the fly
|
||||
$p = explode(DIRECTORY_SEPARATOR, $fullpath);
|
||||
$mpath = implode(DIRECTORY_SEPARATOR, array_slice($p, 0, -1));
|
||||
mkdir($mpath, 0750, true);
|
||||
$this->initRepository($fullpath);
|
||||
$this->setGitExport($relpath, $fullpath);
|
||||
}
|
||||
$new_cmd = sprintf("%s '%s'", $verb, $fullpath);
|
||||
return $new_cmd;
|
||||
}
|
||||
|
||||
/**
|
||||
* Main function called by the serve script.
|
||||
*/
|
||||
public static function main($argv, $env)
|
||||
{
|
||||
if (count($argv) != 1) {
|
||||
print('Missing argument USER.');
|
||||
exit(1);
|
||||
}
|
||||
$username = $argv[0];
|
||||
umask(0022);
|
||||
if (!isset($env['SSH_ORIGINAL_COMMAND'])) {
|
||||
print('Need SSH_ORIGINAL_COMMAND in environment.');
|
||||
exit(1);
|
||||
}
|
||||
$cmd = $env['SSH_ORIGINAL_COMMAND'];
|
||||
chdir(Pluf::f('git_home_dir', '/home/git'));
|
||||
$serve = new IDF_Plugin_SyncGit_Serve();
|
||||
try {
|
||||
$new_cmd = $serve->serve($username, $cmd);
|
||||
} catch (Exception $e) {
|
||||
print($e->getMessage());
|
||||
exit(1);
|
||||
}
|
||||
passthru(sprintf('git shell -c %s', $new_cmd), $res);
|
||||
if ($res != 0) {
|
||||
print('Cannot execute git-shell.');
|
||||
exit(1);
|
||||
}
|
||||
exit();
|
||||
}
|
||||
|
||||
/**
|
||||
* Control the access rights to the repository.
|
||||
*
|
||||
* @param string Username
|
||||
* @param string Path including the possible .git
|
||||
* @param string Type of access. 'readonly' or ('writable')
|
||||
* @return mixed False or array(base_git_reps, relative path to repo)
|
||||
*/
|
||||
public function haveAccess($username, $path, $mode='writable')
|
||||
{
|
||||
if ('.git' == substr($path, -4)) {
|
||||
$path = substr($path, 0, -4);
|
||||
}
|
||||
$sql = new Pluf_SQL('shortname=%s', array($path));
|
||||
$projects = Pluf::factory('IDF_Project')->getList(array('filter'=>$sql->gen()));
|
||||
if ($projects->count() != 1) {
|
||||
return false;
|
||||
}
|
||||
$project = $projects[0];
|
||||
$conf = new IDF_Conf();
|
||||
$conf->setProject($project);
|
||||
$scm = $conf->getVal('scm', 'git');
|
||||
if ($scm != 'git') {
|
||||
return false;
|
||||
}
|
||||
$sql = new Pluf_SQL('login=%s', array($username));
|
||||
$users = Pluf::factory('Pluf_User')->getList(array('filter'=>$sql->gen()));
|
||||
if ($users->count() != 1 or !$users[0]->active) {
|
||||
return false;
|
||||
}
|
||||
$user = $users[0];
|
||||
$request = new StdClass();
|
||||
$request->user = $user;
|
||||
if (true === IDF_Precondition::accessTabGeneric($request, 'source_access_rights')) {
|
||||
if ($mode == 'readonly') {
|
||||
return array(Pluf::f('git_base_repositories', '/home/git/repositories'),
|
||||
$project->shortname);
|
||||
}
|
||||
if (true === IDF_Precondition::projectMemberOrOwner($request)) {
|
||||
return array(Pluf::f('git_base_repositories', '/home/git/repositories'),
|
||||
$project->shortname);
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Init a new empty bare repository.
|
||||
*
|
||||
* @param string Full path to the repository
|
||||
*/
|
||||
public function initRepository($fullpath)
|
||||
{
|
||||
mkdir($fullpath, 0750, true);
|
||||
exec(sprintf('git --git-dir=%s init', escapeshellarg($fullpath)),
|
||||
$out, $res);
|
||||
if ($res != 0) {
|
||||
throw new Exception(sprintf('Init repository error, exit status %d.', $res));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the git export value.
|
||||
*
|
||||
* @param string Relative path of the repository (not .git)
|
||||
* @param string Full path of the repository with .git
|
||||
*/
|
||||
public function setGitExport($relpath, $fullpath)
|
||||
{
|
||||
$sql = new Pluf_SQL('shortname=%s', array($relpath));
|
||||
$projects = Pluf::factory('IDF_Project')->getList(array('filter'=>$sql->gen()));
|
||||
if ($projects->count() != 1) {
|
||||
return $this->gitExportDeny($fullpath);
|
||||
}
|
||||
$project = $projects[0];
|
||||
$conf = new IDF_Conf();
|
||||
$conf->setProject($project);
|
||||
$scm = $conf->getVal('scm', 'git');
|
||||
if ($scm != 'git' or $project->private) {
|
||||
return $this->gitExportDeny($fullpath);
|
||||
}
|
||||
if ('all' == $conf->getVal('source_access_rights', 'all')) {
|
||||
return $this->gitExportAllow($fullpath);
|
||||
}
|
||||
return $this->gitExportDeny($fullpath);
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the export flag.
|
||||
*
|
||||
* @param string Full path to the repository
|
||||
*/
|
||||
public function gitExportDeny($fullpath)
|
||||
{
|
||||
@unlink($fullpath.DIRECTORY_SEPARATOR.'git-daemon-export-ok');
|
||||
if (file_exists($fullpath.DIRECTORY_SEPARATOR.'git-daemon-export-ok')) {
|
||||
throw new Exception('Cannot remove git-daemon-export-ok file.');
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the export flag.
|
||||
*
|
||||
* @param string Full path to the repository
|
||||
*/
|
||||
public function gitExportAllow($fullpath)
|
||||
{
|
||||
touch($fullpath.DIRECTORY_SEPARATOR.'git-daemon-export-ok');
|
||||
if (!file_exists($fullpath.DIRECTORY_SEPARATOR.'git-daemon-export-ok')) {
|
||||
throw new Exception('Cannot create git-daemon-export-ok file.');
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
44
src/IDF/Tests/TestSyncGit.php
Normal file
44
src/IDF/Tests/TestSyncGit.php
Normal file
@ -0,0 +1,44 @@
|
||||
<?php
|
||||
/* -*- tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
|
||||
/*
|
||||
# ***** BEGIN LICENSE BLOCK *****
|
||||
# This file is part of InDefero, an open source project management application.
|
||||
# Copyright (C) 2008 Céondo Ltd and contributors.
|
||||
#
|
||||
# InDefero is free software; you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation; either version 2 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# InDefero is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program; if not, write to the Free Software
|
||||
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
#
|
||||
# ***** END LICENSE BLOCK ***** */
|
||||
|
||||
/**
|
||||
* Test the synchro with git.
|
||||
*/
|
||||
class IDF_Tests_TestSyncGit extends UnitTestCase
|
||||
{
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct('Test the synchro with git.');
|
||||
}
|
||||
|
||||
public function testParsePath()
|
||||
{
|
||||
$regex = Pluf::factory('IDF_Plugin_SyncGit_Serve')->preg;
|
||||
$no_matches = array('foo', "'ev!l'", "'something/../evil'");
|
||||
foreach ($no_matches as $test) {
|
||||
preg_match($regex, $test, $matches);
|
||||
$this->assertEqual(false, isset($matches['path']));
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user