Indefero

Indefero Git Source Tree

Root/src/IDF/Plugin/SyncSvn.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/**
26 * This classes is a plugin which allows to synchronise access rights
27 * between indefero and a DAV powered Subversion repository.
28 */
29class IDF_Plugin_SyncSvn
30{
31
32 /**
33 * Entry point of the plugin.
34 */
35 static public function entry($signal, &$params)
36 {
37 // First check for the 3 mandatory config variables.
38 if (!Pluf::f('idf_plugin_syncsvn_authz_file', false) or
39 !Pluf::f('idf_plugin_syncsvn_passwd_file', false) or
40 !Pluf::f('idf_plugin_syncsvn_svn_path', false)) {
41 return;
42 }
43 include_once 'File/Passwd/Authdigest.php'; // $ pear install File_Passwd
44 $plug = new IDF_Plugin_SyncSvn();
45 switch ($signal) {
46 case 'IDF_Project::created':
47 $plug->processSvnCreate($params['project']);
48 break;
49 case 'IDF_Project::membershipsUpdated':
50 $plug->processSyncAuthz($params['project']);
51 break;
52 case 'Pluf_User::passwordUpdated':
53 $plug->processSyncPasswd($params['user']);
54 break;
55 case 'IDF_Project::preDelete':
56 $plug->processSvnDelete($params['project']);
57 break;
58 case 'svnpostcommit.php::run':
59 $plug->processSvnUpdateTimeline($params);
60 break;
61 }
62 }
63
64 /**
65 * Run svnadmin command to create the corresponding Subversion
66 * repository.
67 *
68 * @param IDF_Project
69 * @return bool Success
70 */
71 function processSvnCreate($project)
72 {
73 if ($project->getConf()->getVal('scm') != 'svn') {
74 return false;
75 }
76 $shortname = $project->shortname;
77 if (false===($svn_path=Pluf::f('idf_plugin_syncsvn_svn_path',false))) {
78 throw new Pluf_Exception_SettingError("'idf_plugin_syncsvn_svn_path' must be defined in your configuration file.");
79 }
80 if (file_exists($svn_path.'/'.$shortname)) {
81 throw new Exception(sprintf(__('The repository %s already exists.'),
82 $svn_path.'/'.$shortname));
83 }
84 $return = 0;
85 $output = array();
86 $cmd = sprintf(Pluf::f('svnadmin_path', 'svnadmin').' create %s',
87 escapeshellarg($svn_path.'/'.$shortname));
88 $cmd = Pluf::f('idf_exec_cmd_prefix', '').$cmd;
89 $ll = exec($cmd, $output, $return);
90 if ($return != 0) {
91 Pluf_Log::error(array('IDF_Plugin_SyncSvn::processSvnCreate',
92 'Error',
93 array('path' => $svn_path.'/'.$shortname,
94 'output' => $output)));
95 return;
96 }
97 $p = realpath(dirname(__FILE__).'/../../../scripts/svn-post-commit');
98 exec(sprintf(Pluf::f('idf_exec_cmd_prefix', '').'ln -s %s %s',
99 escapeshellarg($p),
100 escapeshellarg($svn_path.'/'.$shortname.'/hooks/post-commit')),
101 $out, $res);
102 if ($res != 0) {
103 Pluf_Log::warn(array('IDF_Plugin_SyncSvn::processSvnCreate',
104 'post-commit hook creation error.',
105 $svn_path.'/'.$shortname.'/hooks/post-commit'));
106 return;
107 }
108 $p = realpath(dirname(__FILE__).'/../../../scripts/svn-post-revprop-change');
109 exec(sprintf(Pluf::f('idf_exec_cmd_prefix', '').'ln -s %s %s',
110 escapeshellarg($p),
111 escapeshellarg($svn_path.'/'.$shortname.'/hooks/post-revprop-change')),
112 $out, $res);
113 if ($res != 0) {
114 Pluf_Log::warn(array('IDF_Plugin_SyncSvn::processSvnCreate',
115 'post-revprop-change hook creation error.',
116 $svn_path.'/'.$shortname.'/hooks/post-revprop-change'));
117 return;
118 }
119
120 return ($return == 0);
121 }
122
123 /**
124 * Remove the project from the drive and update the access rights.
125 *
126 * @param IDF_Project
127 * @return bool Success
128 */
129 function processSvnDelete($project)
130 {
131 if (!Pluf::f('idf_plugin_syncsvn_remove_orphans', false)) {
132 return;
133 }
134 if ($project->getConf()->getVal('scm') != 'svn') {
135 return false;
136 }
137 $this->SyncAccess($project); // exclude $project
138 $shortname = $project->shortname;
139 if (false===($svn_path=Pluf::f('idf_plugin_syncsvn_svn_path',false))) {
140 throw new Pluf_Exception_SettingError("'idf_plugin_syncsvn_svn_path' must be defined in your configuration file.");
141 }
142 if (file_exists($svn_path.'/'.$shortname)) {
143 $cmd = Pluf::f('idf_exec_cmd_prefix', '').'rm -rf '.$svn_path.'/'.$shortname;
144 exec($cmd);
145 }
146 }
147
148 /**
149 * Synchronise an user's password.
150 *
151 * @param Pluf_User
152 */
153 function processSyncPasswd($user)
154 {
155 $passwd_file = Pluf::f('idf_plugin_syncsvn_passwd_file');
156 if (!file_exists($passwd_file) or !is_writable($passwd_file)) {
157 return false;
158 }
159 $ht = new File_Passwd_Authbasic($passwd_file);
160 $ht->load();
161 $ht->setMode(FILE_PASSWD_SHA);
162 if ($ht->userExists($user->login)) {
163 $ht->changePasswd($user->login, $this->getSvnPass($user));
164 } else {
165 $ht->addUser($user->login, $this->getSvnPass($user));
166 }
167 $ht->save();
168 return true;
169 }
170
171 /**
172 * Synchronize the authz file and the passwd file for the project.
173 *
174 * @param IDF_Project
175 */
176 function processSyncAuthz($project)
177 {
178 $this->SyncAccess();
179 $this->generateProjectPasswd($project);
180 }
181
182 /**
183 * Get the repository password for the user
184 */
185 function getSvnPass($user){
186 return substr(sha1($user->password.Pluf::f('secret_key')), 0, 8);
187 }
188
189 /**
190 * For a particular project: update all passwd information
191 */
192 function generateProjectPasswd($project)
193 {
194 $passwd_file = Pluf::f('idf_plugin_syncsvn_passwd_file');
195 if (!file_exists($passwd_file) or !is_writable($passwd_file)) {
196 return false;
197 }
198 $ht = new File_Passwd_Authbasic($passwd_file);
199 $ht->setMode(FILE_PASSWD_SHA);
200 $ht->load();
201 $mem = $project->getMembershipData();
202 $members = array_merge((array)$mem['members'], (array)$mem['owners'],
203 (array)$mem['authorized']);
204 foreach($members as $user) {
205 if ($ht->userExists($user->login)) {
206 $ht->changePasswd($user->login, $this->getSvnPass($user));
207 } else {
208 $ht->addUser($user->login, $this->getSvnPass($user));
209 }
210 }
211 $ht->save();
212 }
213
214 /**
215 * Generate the dav_svn.authz file
216 *
217 * We rebuild the complete file each time. This is just to be sure
218 * not to bork the rights when trying to just edit part of the
219 * file.
220 *
221 * @param IDF_Project Possibly exclude a project (null)
222 */
223 function SyncAccess($exclude=null)
224 {
225 $authz_file = Pluf::f('idf_plugin_syncsvn_authz_file');
226 $access_owners = Pluf::f('idf_plugin_syncsvn_access_owners', 'rw');
227 $access_members = Pluf::f('idf_plugin_syncsvn_access_members', 'rw');
228 $access_extra = Pluf::f('idf_plugin_syncsvn_access_extra', 'r');
229 $access_public = Pluf::f('idf_plugin_syncsvn_access_public', 'r');
230 $access_public_priv = Pluf::f('idf_plugin_syncsvn_access_private', '');
231 if (!file_exists($authz_file) or !is_writable($authz_file)) {
232 return false;
233 }
234 $fcontent = '';
235 foreach (Pluf::factory('IDF_Project')->getList() as $project) {
236 if ($exclude and $exclude->id == $project->id) {
237 continue;
238 }
239 $conf = new IDF_Conf();
240 $conf->setProject($project);
241 if ($conf->getVal('scm') != 'svn' or
242 strlen($conf->getVal('svn_remote_url')) > 0) {
243 continue;
244 }
245 $mem = $project->getMembershipData();
246 // [shortname:/]
247 $fcontent .= '['.$project->shortname.':/]'."\n";
248 foreach ($mem['owners'] as $v) {
249 $fcontent .= $v->login.' = '.$access_owners."\n";
250 }
251 foreach ($mem['members'] as $v) {
252 $fcontent .= $v->login.' = '.$access_members."\n";
253 }
254 // access for all users
255 if ($project->private == true) {
256 foreach ($mem['authorized'] as $v) {
257 $fcontent .= $v->login.' = '.$access_extra."\n";
258 }
259 $fcontent .= '* = '.$access_public_priv."\n";
260 } else {
261 $fcontent .= '* = '.$access_public."\n";
262 }
263 $fcontent .= "\n";
264 }
265 file_put_contents($authz_file, $fcontent, LOCK_EX);
266 return true;
267 }
268
269 /**
270 * Update the timeline in post commit.
271 *
272 */
273 public function processSvnUpdateTimeline($params)
274 {
275 $pname = basename($params['repo_dir']);
276 try {
277 $project = IDF_Project::getOr404($pname);
278 } catch (Pluf_HTTP_Error404 $e) {
279 Pluf_Log::event(array('IDF_Plugin_SyncSvn::processSvnUpdateTimeline', 'Project not found.', array($pname, $params)));
280 return false; // Project not found
281 }
282 // Now we have the project and can update the timeline
283 Pluf_Log::debug(array('IDF_Plugin_SyncGit::processSvnUpdateTimeline', 'Project found', $pname, $project->id));
284 IDF_Scm::syncTimeline($project, true);
285 Pluf_Log::event(array('IDF_Plugin_SyncGit::processSvnUpdateTimeline', 'sync', array($pname, $project->id)));
286
287
288 }
289}
290

Archive Download this file