diff --git a/AUTHORS b/AUTHORS index 0e45cc9..3402a5f 100644 --- a/AUTHORS +++ b/AUTHORS @@ -5,4 +5,5 @@ Much appreciated contributors: Nicolas Lassalle - Subversion support bohwaz - Benjamin Jorand - Mercurial support \ No newline at end of file + Benjamin Jorand - Mercurial support + Baptiste Michaud - Subversion synchronization \ No newline at end of file diff --git a/doc/syncsvn.mdtext b/doc/syncsvn.mdtext new file mode 100644 index 0000000..140fd23 --- /dev/null +++ b/doc/syncsvn.mdtext @@ -0,0 +1,75 @@ +# Plugin SyncSvn by Baptiste Michaud + +The SyncSvn plugin allow the direct creation and synchronisation of +subversion repositories with the InDefero database. This requires +giving access to the repositories using the DAV_SVN module of Apache2. + +## To Contact the Author + + Baptiste Michaud + bactisme@gmail.com + webplay.fr - frandroid.com - lost-in-translation.fr + +## Apache configuration + +You will first need to install the DAV_SVN module for Apache. On +Debian/Ubuntu based systems just run: + + $ sudo apt-get install libapache2-svn + $ sudo a2enmod dav_svn + +Then, you need to configure dav_svn, this is an example of +configuration: + + + DAV svn + SVNParentPath /home/svn/repositories + AuthzSVNAccessFile /home/svn/dav_svn.authz + Satisfy Any + Require valid-user + AuthType Basic + AuthName "Subversion Repository" + AuthUserFile /home/svn/dav_svn.passwd + + +Be sure to [read the documentation before](http://svnbook.red-bean.com/en/1.5/svn.serverconfig.httpd.html). + +The files `/home/svn/dav_svn.authz`, `/home/svn/dav_svn.passwd` and +the directory `/home/svn/repositories` must be writable by your +webserver process. To ensure that, do: + + $ sudo mkdir --parents /home/svn/repositories + $ sudo touch /home/svn/dav_svn.authz + $ sudo touch /home/svn/dav_svn.passwd + $ sudo chown -R www-data:www-data /home/svn + +Now, you need to restart apache: + + $ sudo /etc/init.d/apache2 force-reload + +## InDefero Configuration + +Based on the paths provided in the Apache configuration and if your +Apache server is serving the domain `www.mydomain.com`, the you need +to put the following in your configuration file: + + $cfg['svn_repositories'] = 'file:///home/svn/repositories/%s'; + // We add "trunk" to invite people to checkout the trunk of the + // project. + $cfg['svn_remote_url'] = 'http://www.mydomain.com/svn/%s/trunk'; + + // Synchronisation specific configuration variables + $cfg['idf_plugin_syncsvn_authz_file'] = '/home/svn/dav_svn.authz'; + $cfg['idf_plugin_syncsvn_passwd_file'] = '/home/svn/dav_svn.passwd'; + $cfg['idf_plugin_syncsvn_svn_path'] = '/home/svn/repositories'; + +You can have more control over the permissions given to the owners, +members, extra authorized users and anonymous users if you want with +the following configuration variables: + +* **idf_plugin_syncsvn_access_owners ('rw')**: Access for the project owners. +* **idf_plugin_syncsvn_access_members ('rw')**: Access for the project members. +* **idf_plugin_syncsvn_access_extra ('r')**: Access for the extra authorized people in case of a private project. +* **idf_plugin_syncsvn_access_public ('r')**: Anonymous access. +* **idf_plugin_syncsvn_access_private ('')**: Anonymous access in the case of a private project. + diff --git a/src/IDF/Form/TabsConf.php b/src/IDF/Form/TabsConf.php index e973b36..3c64de2 100644 --- a/src/IDF/Form/TabsConf.php +++ b/src/IDF/Form/TabsConf.php @@ -97,6 +97,7 @@ class IDF_Form_TabsConf extends Pluf_Form $this->project->private = 0; } $this->project->update(); + $this->project->membershipsUpdated(); } } diff --git a/src/IDF/Plugin/SyncSvn.php b/src/IDF/Plugin/SyncSvn.php index c10e087..54b4ff1 100644 --- a/src/IDF/Plugin/SyncSvn.php +++ b/src/IDF/Plugin/SyncSvn.php @@ -24,171 +24,175 @@ require_once 'File/Passwd/Authdigest.php'; // $ pear install File_Passwd /** - * This classes is a plugin which allows to synchronise access rights between indefero - * and a DAV powered SVN repository. + * This classes is a plugin which allows to synchronise access rights + * between indefero and a DAV powered Subversion repository. */ class IDF_Plugin_SyncSvn { /** - * Entry point of the each plugins. + * Entry point of the plugin. */ - static function entry($signal, $params){ - // if not actif, do nothing - - if ($signal == 'IDF_Project::created'){ - $project = $params['project']; - - $plug = new IDF_Plugin_SyncSVN(); - //$plug->processSVNCreate($project->shortname); - - }else if ($signal == 'IDF_Project::membershipsUpdated'){ - $project = $params['project']; - - $plug = new IDF_Plugin_SyncSVN(); - $plug->processSyncAuthz($project); - - }else if ($signal == 'IDF_User::passwordUpdated'){ - $plug = new IDF_Plugin_SyncSVN(); + static public function entry($signal, $params) + { + // First check for the 3 mandatory config variables. + if (!Pluf::f('idf_plugin_syncsvn_authz_file', false) or + !Pluf::f('idf_plugin_syncsvn_passwd_file', false) or + !Pluf::f('idf_plugin_syncsvn_svn_path'. false)) { + return; + } + $plug = new IDF_Plugin_SyncSvn(); + switch ($signal) { + case 'IDF_Project::created': + $plug->processSvnCreate($params['project']); + break; + case 'IDF_Project::membershipsUpdated': + $plug->processSyncAuthz($params['project']); + break; + case 'Pluf_User::passwordUpdated': $plug->processSyncPasswd($params['user']); - }else { - // do nothing + break; } } /** - * Run svnadmin command to create a usable SVN repository - * @param Project name + * Run svnadmin command to create the corresponding Subversion + * repository. + * + * @param IDF_Project + * @return bool Success */ - function processSVNCreate($shortname){ - - $svn_path = Pluf::f('idf_plugin_syncsvn_svn_path'); - $svn_import_path = Pluf::f('idf_plugin_syncsvn_svn_import_path'); - $chown_user = Pluf::f('idf_plugin_syncsvn_svn_import_path'); - - $c = 0; - $createsvn = "svnadmin create ".$svn_path."/".$shortname; - Pluf_Utils::runExternal($createsvn, $c); - - if ($svn_import_path != ""){ - //perform initial import - // TODO + function processSvnCreate($project) + { + $shortname = $project->shortname; + if (false===($svn_path=Pluf::f('idf_plugin_syncsvn_svn_path',false))) { + throw new Pluf_Exception_SettingError("'idf_plugin_syncsvn_svn_path' must be defined in your configuration file."); } - - if ($chown_user != ""){ - $chown = "chown ".$chown_user." ".$svn_path."/".$shortname." -R"; - Pluf_Utils::runExternal($chown, $c); + if (file_exists($svn_path.'/'.$shortname)) { + throw new Exception(sprintf(__('The repository %s already exists.'), + $svn_path.'/'.$shortname)); } + $return = 0; + $output = array(); + $cmd = sprintf('svnadmin create %s', + escapeshellarg($svn_path.'/'.$shortname)); + $ll = exec($cmd, $output, $return); + return ($return == 0); } /** - * Synchronise an user's password - * @param $user Pluf_User + * Synchronise an user's password. + * + * @param Pluf_User */ - function processSyncPasswd($user){ + function processSyncPasswd($user) + { $passwd_file = Pluf::f('idf_plugin_syncsvn_passwd_file'); + if (!file_exists($passwd_file) or !is_writable($passwd_file)) { + return false; + } $ht = new File_Passwd_Authbasic($passwd_file); $ht->parse(); - $ht->setMode(FILE_PASSWD_SHA); // not anymore a option - $ht->addUser($user, $this->getSVNPass($user)); + $ht->setMode(FILE_PASSWD_SHA); + if ($ht->userExists($user->login)) { + $ht->changePasswd($user->login, $this->getSvnPass($user)); + } else { + $ht->addUser($user->login, $this->getSvnPass($user)); + } $ht->save(); + return true; } /** - * Synchronize the authz file and the passwd file for the project - * @param $project IDF_Project + * Synchronize the authz file and the passwd file for the project. + * + * @param IDF_Project */ - function processSyncAuthz($project){ - //synchronise authz file + function processSyncAuthz($project) + { $this->SyncAccess(); - //synchronise pass file for $this->generateProjectPasswd($project); } /** * Get the repository password for the user */ - function getSVNPass($user){ + function getSvnPass($user){ return substr(sha1($user->password.Pluf::f('secret_key')), 0, 8); } /** * For a particular project: update all passwd information */ - function generateProjectPasswd($project){ + function generateProjectPasswd($project) + { $passwd_file = Pluf::f('idf_plugin_syncsvn_passwd_file'); + if (!file_exists($passwd_file) or !is_writable($passwd_file)) { + return false; + } $ht = new File_Passwd_Authbasic($passwd_file); - - $ht->setMode(FILE_PASSWD_SHA); // not anymore a option + $ht->setMode(FILE_PASSWD_SHA); $ht->parse(); - $mem = $project->getMembershipData(); - $members = $mem['members']; - $owners = $mem['owners']; - - foreach($owners as $v){ - $ht->addUser($v->login, $this->getSVNPass($v)); - } - - foreach($members as $v){ - $ht->addUser($v->login, $this->getSVNPass($v)); + $members = array_merge((array)$mem['members'], (array)$mem['owners'], + (array)$mem['authorized']); + foreach($members as $user) { + if ($ht->userExists($user->login)) { + $ht->changePasswd($user->login, $this->getSvnPass($user)); + } else { + $ht->addUser($user->login, $this->getSvnPass($user)); + } } $ht->save(); } /** * Generate the dav_svn.authz file + * + * We rebuild the complete file each time. This is just to be sure + * not to bork the rights when trying to just edit part of the + * file. */ - function SyncAccess(){ + function SyncAccess() + { $authz_file = Pluf::f('idf_plugin_syncsvn_authz_file'); - $access_owners = Pluf::f('idf_plugin_syncsvn_access_owners'); - $access_members = Pluf::f('idf_plugin_syncsvn_access_members'); - $access_all = Pluf::f('idf_plugin_syncsvn_access_all'); - $access_all_pivate = Pluf::f('idf_plugin_syncsvn_access_all_pivate'); - - $projects = Pluf::factory('IDF_Project')->getList(); - - $fcontent = ""; - - // for each project - foreach($projects as $project){ - + $access_owners = Pluf::f('idf_plugin_syncsvn_access_owners', 'rw'); + $access_members = Pluf::f('idf_plugin_syncsvn_access_members', 'rw'); + $access_extra = Pluf::f('idf_plugin_syncsvn_access_extra', 'r'); + $access_public = Pluf::f('idf_plugin_syncsvn_access_public', 'r'); + $access_public_priv = Pluf::f('idf_plugin_syncsvn_access_private', ''); + if (!file_exists($authz_file) or !is_writable($authz_file)) { + return false; + } + $fcontent = ''; + foreach (Pluf::factory('IDF_Project')->getList() as $project) { $conf = new IDF_Conf(); $conf->setProject($project); - - if ($conf->getVal('scm', "") == "svn"){ - - $mem = $project->getMembershipData(); - $members = $mem['members']; - $owners = $mem['owners']; - - // [shortname:/] - $fcontent .= "[".$project->shortname.":/]\n"; - - // login = rw - foreach($owners as $v){ - $fcontent .= $v->login." = ".$access_owners."\n"; - } - // login = rw - foreach($members as $v){ - $fcontent .= $v->login." = ".$access_members."\n"; - } - - // access for all users - if ($project->private == true){ - $fcontent .= "* = ".$access_all_pivate."\n"; - }else{ - $fcontent .= "* = ".$access_all."\n"; + if ($conf->getVal('scm') != 'svn' or + strlen($conf->getVal('svn_remote_url')) > 0) { + continue; + } + $mem = $project->getMembershipData(); + // [shortname:/] + $fcontent .= '['.$project->shortname.':/]'."\n"; + foreach ($mem['owners'] as $v) { + $fcontent .= $v->login.' = '.$access_owners."\n"; + } + foreach ($mem['members'] as $v) { + $fcontent .= $v->login.' = '.$access_members."\n"; + } + // access for all users + if ($project->private == true) { + foreach ($mem['authorized'] as $v) { + $fcontent .= $v->login.' = '.$access_extra."\n"; } - - $fcontent .= "\n"; - } //end if SVN + $fcontent .= '* = '.$access_public_priv."\n"; + } else { + $fcontent .= '* = '.$access_public."\n"; + } + $fcontent .= "\n"; } - file_put_contents($authz_file, $fcontent, LOCK_EX); - - return 0; + return true; } } - -?> diff --git a/src/IDF/relations.php b/src/IDF/relations.php index 9f38880..652a2d3 100644 --- a/src/IDF/relations.php +++ b/src/IDF/relations.php @@ -38,4 +38,15 @@ $m['IDF_Review'] = array('relate_to' => array('IDF_Project', 'Pluf_User', 'IDF_T $m['IDF_Review_Patch'] = array('relate_to' => array('IDF_Review', 'Pluf_User')); $m['IDF_Review_FileComment'] = array('relate_to' => array('IDF_Review_Patch', 'Pluf_User')); + +# -- Standard plugins, they will run only if configured -- +# +# Subversion synchronization +Pluf_Signal::connect('IDF_Project::membershipsUpdated', + array('IDF_Plugin_SyncSvn', 'entry')); +Pluf_Signal::connect('IDF_Project::created', + array('IDF_Plugin_SyncSvn', 'entry')); +Pluf_Signal::connect('Pluf_User::passwordUpdated', + array('IDF_Plugin_SyncSvn', 'entry')); + return $m;