InDefero

Sign in or create your account | Project List | Help

InDefero Commit Details

Date:2009-01-14 21:23:52 (1 year 7 months ago)
Author:Loïc d'Anterroches
Commit:00f3b08ec68b1eb30d72710b6522e9ce895187e2
Message:Started the work on issue 3, git synchronization.

Files: scripts/gitserve.php (1 diff)
src/IDF/Plugin/SyncGit/Serve.php (1 diff)
src/IDF/Tests/TestSyncGit.php (1 diff)

Change Details

scripts/gitserve.php
1<?php
2/* -*- tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
3/*
4# ***** BEGIN LICENSE BLOCK *****
5# This file is part of InDefero, an open source project management application.
6# Copyright (C) 2008 Céondo Ltd and contributors.
7#
8# InDefero is free software; you can redistribute it and/or modify
9# it under the terms of the GNU General Public License as published by
10# the Free Software Foundation; either version 2 of the License, or
11# (at your option) any later version.
12#
13# InDefero is distributed in the hope that it will be useful,
14# but WITHOUT ANY WARRANTY; without even the implied warranty of
15# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16# GNU General Public License for more details.
17#
18# You should have received a copy of the GNU General Public License
19# along with this program; if not, write to the Free Software
20# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
21#
22# ***** END LICENSE BLOCK ***** */
23
24/**
25 * This script is used to control the access to the git repositories
26 * using a restricted shell access.
27 *
28 * The only argument must be the login of the user.
29 */
30// Set the include path to have Pluf and IDF in it.
31$indefero_path = dirname(__FILE__).'/../src';
32//$pluf_path = '/path/to/pluf/src';
33set_include_path(get_include_path()
34                 .PATH_SEPARATOR.$indefero_path
35// .PATH_SEPARATOR.$pluf_path
36                 );
37require 'Pluf.php';
38Pluf::start(dirname(__FILE__).'/../src/IDF/conf/idf.php');
39Pluf_Dispatcher::loadControllers(Pluf::f('idf_views'));
40IDF_Plugin_SyncGit_Serve::main($argv, $_ENV);
41
src/IDF/Plugin/SyncGit/Serve.php
1<?php
2/* -*- tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
3/*
4# ***** BEGIN LICENSE BLOCK *****
5# This file is part of InDefero, an open source project management application.
6# Copyright (C) 2008 Céondo Ltd and contributors.
7#
8# InDefero is free software; you can redistribute it and/or modify
9# it under the terms of the GNU General Public License as published by
10# the Free Software Foundation; either version 2 of the License, or
11# (at your option) any later version.
12#
13# InDefero is distributed in the hope that it will be useful,
14# but WITHOUT ANY WARRANTY; without even the implied warranty of
15# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16# GNU General Public License for more details.
17#
18# You should have received a copy of the GNU General Public License
19# along with this program; if not, write to the Free Software
20# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
21#
22# ***** END LICENSE BLOCK ***** */
23
24/**
25 * Main application to serve git repositories through a restricted SSH
26 * access.
27 */
28class IDF_Plugin_SyncGit_Serve
29{
30    /**
31     * Regular expression to match the path in the git command.
32     */
33    public $preg = '#^\'/*(?P<path>[a-zA-Z0-9][a-zA-Z0-9@._-]*(/[a-zA-Z0-9][a-zA-Z0-9@._-]*)*)\'$#';
34
35    public $commands_readonly = array('git-upload-pack', 'git upload-pack');
36    public $commands_write = array('git-receive-pack', 'git receive-pack');
37
38    /**
39     * Check that the command is authorized.
40     */
41
42
43    /**
44     * Serve a git request.
45     *
46     * @param string Username.
47     * @param string Command to be run.
48     */
49    public function serve($username, $cmd)
50    {
51        if (false !== strpos($cmd, "\n")) {
52            throw new Exception('Command may not contain newline.');
53        }
54        $splitted = preg_split('/\s/', $cmd, 2);
55        if (count($splitted) != 2) {
56            throw new Exception('Unknown command denied.');
57        }
58        if ($splitted[0] == 'git') {
59            $sub_splitted = preg_split('/\s/', $splitted[1], 2);
60            if (count($sub_splitted) != 2) {
61                throw new Exception('Unknown command denied.');
62            }
63            $verb = sprintf('%s %s', $splitted[0], $sub_splitted[0]);
64            $args = $sub_splitted[1];
65        } else {
66            $verb = $splitted[0];
67            $args = $splitted[1];
68        }
69        if (!in_array($verb, $this->commands_write)
70            and !in_array($verb, $this->commands_readonly)) {
71            throw new Exception('Unknown command denied.');
72        }
73        if (!preg_match($this->preg, $args, $matches)) {
74            throw new Exception('Arguments to command look dangerous.');
75        }
76        $path = $matches['path'];
77        // Check read/write rights
78        $new_path = $this->haveAccess($username, $path, 'writable');
79        if ($new_path == false) {
80            $new_path = $this->haveAccess($username, $path, 'readonly');
81            if ($new_path == false) {
82                throw new Exception('Repository read access denied.');
83            }
84            if (in_array($verb, $this->commands_write)) {
85                throw new Exception('Repository write access denied.');
86            }
87        }
88        list($topdir, $relpath) = $new_path;
89        $repopath = sprintf('%s.git', $relpath);
90        $fullpath = $topdir.DIRECTORY_SEPARATOR.$repopath;
91        if (!file_exists($fullpath)
92            and in_array($verb, $this->commands_write)) {
93            // it doesn't exist on the filesystem, but the
94            // configuration refers to it, we're serving a write
95            // request, and the user is authorized to do that: create
96            // the repository on the fly
97            $p = explode(DIRECTORY_SEPARATOR, $fullpath);
98            $mpath = implode(DIRECTORY_SEPARATOR, array_slice($p, 0, -1));
99            mkdir($mpath, 0750, true);
100            $this->initRepository($fullpath);
101            $this->setGitExport($relpath, $fullpath);
102        }
103        $new_cmd = sprintf("%s '%s'", $verb, $fullpath);
104        return $new_cmd;
105    }
106
107    /**
108     * Main function called by the serve script.
109     */
110    public static function main($argv, $env)
111    {
112        if (count($argv) != 1) {
113            print('Missing argument USER.');
114            exit(1);
115        }
116        $username = $argv[0];
117        umask(0022);
118        if (!isset($env['SSH_ORIGINAL_COMMAND'])) {
119            print('Need SSH_ORIGINAL_COMMAND in environment.');
120            exit(1);
121        }
122        $cmd = $env['SSH_ORIGINAL_COMMAND'];
123        chdir(Pluf::f('git_home_dir', '/home/git'));
124        $serve = new IDF_Plugin_SyncGit_Serve();
125        try {
126            $new_cmd = $serve->serve($username, $cmd);
127        } catch (Exception $e) {
128            print($e->getMessage());
129            exit(1);
130        }
131        passthru(sprintf('git shell -c %s', $new_cmd), $res);
132        if ($res != 0) {
133            print('Cannot execute git-shell.');
134            exit(1);
135        }
136        exit();
137    }
138
139    /**
140     * Control the access rights to the repository.
141     *
142     * @param string Username
143     * @param string Path including the possible .git
144     * @param string Type of access. 'readonly' or ('writable')
145     * @return mixed False or array(base_git_reps, relative path to repo)
146     */
147    public function haveAccess($username, $path, $mode='writable')
148    {
149        if ('.git' == substr($path, -4)) {
150            $path = substr($path, 0, -4);
151        }
152        $sql = new Pluf_SQL('shortname=%s', array($path));
153        $projects = Pluf::factory('IDF_Project')->getList(array('filter'=>$sql->gen()));
154        if ($projects->count() != 1) {
155            return false;
156        }
157        $project = $projects[0];
158        $conf = new IDF_Conf();
159        $conf->setProject($project);
160        $scm = $conf->getVal('scm', 'git');
161        if ($scm != 'git') {
162            return false;
163        }
164        $sql = new Pluf_SQL('login=%s', array($username));
165        $users = Pluf::factory('Pluf_User')->getList(array('filter'=>$sql->gen()));
166        if ($users->count() != 1 or !$users[0]->active) {
167            return false;
168        }
169        $user = $users[0];
170        $request = new StdClass();
171        $request->user = $user;
172        if (true === IDF_Precondition::accessTabGeneric($request, 'source_access_rights')) {
173            if ($mode == 'readonly') {
174                return array(Pluf::f('git_base_repositories', '/home/git/repositories'),
175                             $project->shortname);
176            }
177            if (true === IDF_Precondition::projectMemberOrOwner($request)) {
178                return array(Pluf::f('git_base_repositories', '/home/git/repositories'),
179                             $project->shortname);
180            }
181        }
182        return false;
183    }
184
185    /**
186     * Init a new empty bare repository.
187     *
188     * @param string Full path to the repository
189     */
190    public function initRepository($fullpath)
191    {
192        mkdir($fullpath, 0750, true);
193        exec(sprintf('git --git-dir=%s init', escapeshellarg($fullpath)),
194             $out, $res);
195        if ($res != 0) {
196            throw new Exception(sprintf('Init repository error, exit status %d.', $res));
197        }
198    }
199
200    /**
201     * Set the git export value.
202     *
203     * @param string Relative path of the repository (not .git)
204     * @param string Full path of the repository with .git
205     */
206    public function setGitExport($relpath, $fullpath)
207    {
208        $sql = new Pluf_SQL('shortname=%s', array($relpath));
209        $projects = Pluf::factory('IDF_Project')->getList(array('filter'=>$sql->gen()));
210        if ($projects->count() != 1) {
211            return $this->gitExportDeny($fullpath);
212        }
213        $project = $projects[0];
214        $conf = new IDF_Conf();
215        $conf->setProject($project);
216        $scm = $conf->getVal('scm', 'git');
217        if ($scm != 'git' or $project->private) {
218            return $this->gitExportDeny($fullpath);
219        }
220        if ('all' == $conf->getVal('source_access_rights', 'all')) {
221            return $this->gitExportAllow($fullpath);
222        }
223        return $this->gitExportDeny($fullpath);
224    }
225
226    /**
227     * Remove the export flag.
228     *
229     * @param string Full path to the repository
230     */
231    public function gitExportDeny($fullpath)
232    {
233        @unlink($fullpath.DIRECTORY_SEPARATOR.'git-daemon-export-ok');
234        if (file_exists($fullpath.DIRECTORY_SEPARATOR.'git-daemon-export-ok')) {
235            throw new Exception('Cannot remove git-daemon-export-ok file.');
236        }
237        return true;
238    }
239
240    /**
241     * Set the export flag.
242     *
243     * @param string Full path to the repository
244     */
245    public function gitExportAllow($fullpath)
246    {
247        touch($fullpath.DIRECTORY_SEPARATOR.'git-daemon-export-ok');
248        if (!file_exists($fullpath.DIRECTORY_SEPARATOR.'git-daemon-export-ok')) {
249            throw new Exception('Cannot create git-daemon-export-ok file.');
250        }
251        return true;
252    }
253}
src/IDF/Tests/TestSyncGit.php
1<?php
2/* -*- tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
3/*
4# ***** BEGIN LICENSE BLOCK *****
5# This file is part of InDefero, an open source project management application.
6# Copyright (C) 2008 Céondo Ltd and contributors.
7#
8# InDefero is free software; you can redistribute it and/or modify
9# it under the terms of the GNU General Public License as published by
10# the Free Software Foundation; either version 2 of the License, or
11# (at your option) any later version.
12#
13# InDefero is distributed in the hope that it will be useful,
14# but WITHOUT ANY WARRANTY; without even the implied warranty of
15# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16# GNU General Public License for more details.
17#
18# You should have received a copy of the GNU General Public License
19# along with this program; if not, write to the Free Software
20# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
21#
22# ***** END LICENSE BLOCK ***** */
23
24/**
25 * Test the synchro with git.
26 */
27class IDF_Tests_TestSyncGit extends UnitTestCase
28{
29
30    public function __construct()
31    {
32        parent::__construct('Test the synchro with git.');
33    }
34
35    public function testParsePath()
36    {
37        $regex = Pluf::factory('IDF_Plugin_SyncGit_Serve')->preg;
38        $no_matches = array('foo', "'ev!l'", "'something/../evil'");
39        foreach ($no_matches as $test) {
40            preg_match($regex, $test, $matches);
41            $this->assertEqual(false, isset($matches['path']));
42        }
43    }
44}

Archive Download the corresponding diff file

Branches:
dev
develop
master
newdiff
svn

Tags:
v1.0