Added private projects.

It is now possible to create private projects. To mark a project as
private, you simply go in the Administer > Tabs Access menu and select
"Private project". Only project members and owners together with the
extra authorized users will be able to access the project. The project
will not appear in the list of projects for not authorized users.
This commit is contained in:
Loic d'Anterroches 2008-11-21 13:19:02 +01:00
parent 80b9e2ff78
commit 0e725bea26
9 changed files with 262 additions and 10 deletions

View File

@ -28,9 +28,13 @@
class IDF_Form_TabsConf extends Pluf_Form class IDF_Form_TabsConf extends Pluf_Form
{ {
public $conf = null; public $conf = null;
public $project = null;
public function initFields($extra=array()) public function initFields($extra=array())
{ {
$this->conf = $extra['conf']; $this->conf = $extra['conf'];
$this->project = $extra['project'];
$ak = array('downloads_access_rights' => __('Downloads'), $ak = array('downloads_access_rights' => __('Downloads'),
'source_access_rights' => __('Source'), 'source_access_rights' => __('Source'),
'issues_access_rights' => __('Issues'),); 'issues_access_rights' => __('Issues'),);
@ -51,7 +55,58 @@ class IDF_Form_TabsConf extends Pluf_Form
'widget' => 'Pluf_Form_Widget_SelectInput', 'widget' => 'Pluf_Form_Widget_SelectInput',
)); ));
} }
$this->fields['private_project'] = new Pluf_Form_Field_Boolean(
array('required' => false,
'label' => __('Private project'),
'initial' => $this->project->private,
'widget' => 'Pluf_Form_Widget_CheckboxInput',
));
$this->fields['authorized_users'] = new Pluf_Form_Field_Varchar(
array('required' => false,
'label' => __('Extra authorized users'),
'widget_attrs' => array('rows' => 7,
'cols' => 40),
'widget' => 'Pluf_Form_Widget_TextareaInput',
));
}
public function save($commit=true)
{
if (!$this->isValid()) {
throw new Exception(__('Cannot save the model from an invalid form.'));
}
// remove all the permissions
$perm = Pluf_Permission::getFromString('IDF.project-authorized-user');
if ($perm == false) {
// We do not have this perm for the moment in the system,
// so create it.
$perm = new Pluf_Permission();
$perm->name = 'Project authorized users';
$perm->code_name = 'project-authorized-user';
$perm->description = 'Permission given to users allowed to access a project.';
$perm->application = 'IDF';
$perm->create();
}
$cm = $this->project->getMembershipData();
$guser = new Pluf_User();
foreach ($cm['authorized'] as $user) {
Pluf_RowPermission::remove($user, $this->project, $perm);
}
if ($this->cleaned_data['private_project']) {
foreach (preg_split("/\015\012|\015|\012|\,/", $this->cleaned_data['authorized_users'], -1, PREG_SPLIT_NO_EMPTY) as $login) {
$sql = new Pluf_SQL('login=%s', array(trim($login)));
$users = $guser->getList(array('filter'=>$sql->gen()));
if ($users->count() == 1) {
Pluf_RowPermission::add($users[0], $this->project, $perm);
}
}
$this->project->private = 1;
} else {
$this->project->private = 0;
}
$this->project->update();
} }
} }

View File

@ -0,0 +1,54 @@
<?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 ***** */
/**
* Add the private column for the project.
*/
function IDF_Migrations_6PrivateProject_up($params=null)
{
$table = Pluf::factory('IDF_Project')->getSqlTable();
$sql = array();
$sql['PostgreSQL'] = 'ALTER TABLE '.$table.' ADD COLUMN "private" INTEGER DEFAULT 0';
$sql['MySQL'] = 'ALTER TABLE '.$table.' ADD COLUMN `private` INTEGER DEFAULT 0';
$db = Pluf::db();
$engine = Pluf::f('db_engine');
if (!isset($sql[$engine])) {
throw new Exception('SQLite complex migration not supported.');
}
$db->execute($sql[$engine]);
}
function IDF_Migrations_6PrivateProject_down($params=null)
{
$table = Pluf::factory('IDF_Project')->getSqlTable();
$sql = array();
$sql['PostgreSQL'] = 'ALTER TABLE '.$table.' DROP COLUMN "private"';
$sql['MySQL'] = 'ALTER TABLE '.$table.' DROP COLUMN `private`';
$db = Pluf::db();
$engine = Pluf::f('db_engine');
if (!isset($sql[$engine])) {
throw new Exception('SQLite complex migration not supported.');
}
$db->execute($sql[$engine]);
}

View File

@ -38,6 +38,7 @@ function IDF_Migrations_Install_setup($params=null)
'IDF_Upload', 'IDF_Upload',
'IDF_Search_Occ', 'IDF_Search_Occ',
'IDF_IssueFile', 'IDF_IssueFile',
'IDF_Timeline',
); );
$db = Pluf::db(); $db = Pluf::db();
$schema = new Pluf_DB_Schema($db); $schema = new Pluf_DB_Schema($db);
@ -67,6 +68,8 @@ function IDF_Migrations_Install_teardown($params=null)
$perm = Pluf_Permission::getFromString('IDF.project-owner'); $perm = Pluf_Permission::getFromString('IDF.project-owner');
if ($perm) $perm->delete(); if ($perm) $perm->delete();
$models = array( $models = array(
'IDF_Timeline',
'IDF_IssueFile',
'IDF_Search_Occ', 'IDF_Search_Occ',
'IDF_Upload', 'IDF_Upload',
'IDF_Conf', 'IDF_Conf',

View File

@ -23,6 +23,26 @@
class IDF_Precondition class IDF_Precondition
{ {
/**
* Check if the user has a base authorization to access a given
* tab. This used in the case of private project. You need to
* further control with the accessSource, accessIssues,
* etc. preconditions.
*
* @param Pluf_HTTP_Request
* @return mixed
*/
static public function baseAccess($request)
{
if (!$request->project->private) {
return true;
}
if ($request->user->hasPerm('IDF.project-authorized-user', $request->project)) {
return true;
}
return self::projectMemberOrOwner($request);
}
/** /**
* Check if the user is project owner. * Check if the user is project owner.
* *
@ -98,16 +118,28 @@ class IDF_Precondition
static public function accessSource($request) static public function accessSource($request)
{ {
$res = self::baseAccess($request);
if (true !== $res) {
return $res;
}
return self::accessTabGeneric($request, 'source_access_rights'); return self::accessTabGeneric($request, 'source_access_rights');
} }
static public function accessIssues($request) static public function accessIssues($request)
{ {
$res = self::baseAccess($request);
if (true !== $res) {
return $res;
}
return self::accessTabGeneric($request, 'issues_access_rights'); return self::accessTabGeneric($request, 'issues_access_rights');
} }
static public function accessDownloads($request) static public function accessDownloads($request)
{ {
$res = self::baseAccess($request);
if (true !== $res) {
return $res;
}
return self::accessTabGeneric($request, 'downloads_access_rights'); return self::accessTabGeneric($request, 'downloads_access_rights');
} }
} }

View File

@ -70,8 +70,14 @@ class IDF_Project extends Pluf_Model
'verbose' => __('description'), 'verbose' => __('description'),
'help_text' => __('The description can be extended using the markdown syntax.'), 'help_text' => __('The description can be extended using the markdown syntax.'),
), ),
'private' =>
array(
'type' => 'Pluf_DB_Field_Integer',
'blank' => false,
'verbose' => __('private'),
'default' => 0,
),
); );
$this->_a['idx'] = array( );
} }
@ -214,7 +220,7 @@ class IDF_Project extends Pluf_Model
/** /**
* Return membership data. * Return membership data.
* *
* The array has 2 keys: 'members' and 'owners'. * The array has 3 keys: 'members', 'owners' and 'authorized'.
* *
* The list of users is only taken using the row level permission * The list of users is only taken using the row level permission
* table. That is, if you set a user as administrator, he will * table. That is, if you set a user as administrator, he will
@ -228,6 +234,7 @@ class IDF_Project extends Pluf_Model
{ {
$mperm = Pluf_Permission::getFromString('IDF.project-member'); $mperm = Pluf_Permission::getFromString('IDF.project-member');
$operm = Pluf_Permission::getFromString('IDF.project-owner'); $operm = Pluf_Permission::getFromString('IDF.project-owner');
$aperm = Pluf_Permission::getFromString('IDF.project-authorized-user');
$grow = new Pluf_RowPermission(); $grow = new Pluf_RowPermission();
$db =& Pluf::db(); $db =& Pluf::db();
$false = Pluf_DB_BooleanToDb(false, $db); $false = Pluf_DB_BooleanToDb(false, $db);
@ -251,11 +258,25 @@ class IDF_Project extends Pluf_Model
$members[] = Pluf::factory('Pluf_User', $row->owner_id)->login; $members[] = Pluf::factory('Pluf_User', $row->owner_id)->login;
} }
} }
$authorized = new Pluf_Template_ContextVars(array());
if ($aperm != false) {
$sql = new Pluf_SQL('model_class=%s AND model_id=%s AND owner_class=%s AND permission=%s AND negative='.$false,
array('IDF_Project', $this->id, 'Pluf_User', $aperm->id));
foreach ($grow->getList(array('filter' => $sql->gen())) as $row) {
if ($fmt == 'objects') { if ($fmt == 'objects') {
return new Pluf_Template_ContextVars(array('members' => $members, 'owners' => $owners)); $authorized[] = Pluf::factory('Pluf_User', $row->owner_id);
} else {
$authorized[] = Pluf::factory('Pluf_User', $row->owner_id)->login;
}
}
}
if ($fmt == 'objects') {
return new Pluf_Template_ContextVars(array('members' => $members, 'owners' => $owners, 'authorized' => $authorized));
} else { } else {
return array('members' => implode("\n", (array) $members), return array('members' => implode("\n", (array) $members),
'owners' => implode("\n", (array) $owners)); 'owners' => implode("\n", (array) $owners),
'authorized' => implode("\n", (array) $authorized),
);
} }
} }

View File

@ -33,10 +33,13 @@ class IDF_Views
{ {
/** /**
* List all the projects managed by InDefero. * List all the projects managed by InDefero.
*
* Only the public projects are listed or the private with correct
* rights.
*/ */
public function index($request, $match) public function index($request, $match)
{ {
$projects = Pluf::factory('IDF_Project')->getList(); $projects = self::getProjects($request->user);
return Pluf_Shortcuts_RenderToResponse('idf/index.html', return Pluf_Shortcuts_RenderToResponse('idf/index.html',
array('page_title' => __('Projects'), array('page_title' => __('Projects'),
'projects' => $projects), 'projects' => $projects),
@ -171,7 +174,7 @@ class IDF_Views
public function faq($request, $match) public function faq($request, $match)
{ {
$title = __('Here to Help You!'); $title = __('Here to Help You!');
$projects = Pluf::factory('IDF_Project')->getList(); $projects = self::getProjects($request->user);
return Pluf_Shortcuts_RenderToResponse('idf/faq.html', return Pluf_Shortcuts_RenderToResponse('idf/faq.html',
array( array(
'page_title' => $title, 'page_title' => $title,
@ -180,4 +183,42 @@ class IDF_Views
$request); $request);
} }
/**
* Returns a list of projects accessible for the user.
*
* @param Pluf_User
* @return ArrayObject IDF_Project
*/
public static function getProjects($user)
{
$db =& Pluf::db();
$false = Pluf_DB_BooleanToDb(false, $db);
if ($user->isAnonymous()) {
$sql = sprintf('%s=%s', $db->qn('private'), $false);
return Pluf::factory('IDF_Project')->getList(array('filter'=> $sql));
}
if ($user->administrator) {
return Pluf::factory('IDF_Project')->getList();
}
// grab the list of projects where the user is admin, member
// or authorized
$perms = array(
Pluf_Permission::getFromString('IDF.project-member'),
Pluf_Permission::getFromString('IDF.project-owner'),
Pluf_Permission::getFromString('IDF.project-authorized-user')
);
$sql = new Pluf_SQL("model_class='IDF_Project' AND owner_class='Pluf_User' AND owner_id=%s AND negative=".$false, $user->id);
$rows = Pluf::factory('Pluf_RowPermission')->getList(array('filter' => $sql->gen()));
$sql = sprintf('%s=%s', $db->qn('private'), $false);
if ($rows->count() > 0) {
$ids = array();
foreach ($rows as $row) {
$ids[] = $row->model_id;
}
$sql .= sprintf(' OR id IN (%s)', implode(', ', $ids));
}
return Pluf::factory('IDF_Project')->getList(array('filter' => $sql));
}
} }

View File

@ -34,6 +34,7 @@ class IDF_Views_Project
/** /**
* Home page of a project. * Home page of a project.
*/ */
public $home_precond = array('IDF_Precondition::baseAccess');
public function home($request, $match) public function home($request, $match)
{ {
$prj = $request->project; $prj = $request->project;
@ -57,6 +58,7 @@ class IDF_Views_Project
/** /**
* Timeline of the project. * Timeline of the project.
*/ */
public $timeline_precond = array('IDF_Precondition::baseAccess');
public function timeline($request, $match) public function timeline($request, $match)
{ {
$prj = $request->project; $prj = $request->project;
@ -272,6 +274,7 @@ class IDF_Views_Project
$prj = $request->project; $prj = $request->project;
$title = sprintf(__('%s Tabs Access Rights'), (string) $prj); $title = sprintf(__('%s Tabs Access Rights'), (string) $prj);
$extra = array( $extra = array(
'project' => $prj,
'conf' => $request->conf, 'conf' => $request->conf,
); );
if ($request->method == 'POST') { if ($request->method == 'POST') {
@ -280,6 +283,7 @@ class IDF_Views_Project
foreach ($form->cleaned_data as $key=>$val) { foreach ($form->cleaned_data as $key=>$val) {
$request->conf->setVal($key, $val); $request->conf->setVal($key, $val);
} }
$form->save(); // Save the authorized users.
$request->user->setMessage(__('The project tabs access rights have been saved.')); $request->user->setMessage(__('The project tabs access rights have been saved.'));
$url = Pluf_HTTP_URL_urlForView('IDF_Views_Project::adminTabs', $url = Pluf_HTTP_URL_urlForView('IDF_Views_Project::adminTabs',
array($prj->shortname)); array($prj->shortname));
@ -288,13 +292,16 @@ class IDF_Views_Project
} else { } else {
$params = array(); $params = array();
$keys = array('downloads_access_rights', 'source_access_rights', $keys = array('downloads_access_rights', 'source_access_rights',
'issues_access_rights'); 'issues_access_rights', 'private_project');
foreach ($keys as $key) { foreach ($keys as $key) {
$_val = $request->conf->getVal($key, false); $_val = $request->conf->getVal($key, false);
if ($_val !== false) { if ($_val !== false) {
$params[$key] = $_val; $params[$key] = $_val;
} }
} }
// Add the authorized users.
$md = $prj->getMembershipData('string');
$params['authorized_users'] = $md['authorized'];
if (count($params) == 0) { if (count($params) == 0) {
$params = null; //Nothing in the db, so new form. $params = null; //Nothing in the db, so new form.
} }

View File

@ -122,9 +122,15 @@ $cfg['db_password'] = '';
$cfg['db_server'] = ''; $cfg['db_server'] = '';
$cfg['db_version'] = ''; $cfg['db_version'] = '';
$cfg['db_table_prefix'] = ''; $cfg['db_table_prefix'] = '';
// ** DO NOT USE SQLITE IN PRODUCTION **
// This is not because of problems with the quality of the SQLite
// driver or with SQLite itself, this is due to the lack of migration
// support in Pluf for SQLite, this means we cannot modify the DB
// easily once it is loaded with data.
$cfg['db_engine'] = 'PostgreSQL'; // SQLite is also well tested or MySQL $cfg['db_engine'] = 'PostgreSQL'; // SQLite is also well tested or MySQL
$cfg['db_database'] = 'website'; // put absolute path to the db if you $cfg['db_database'] = 'website'; // put absolute path to the db if you
// are using SQLite // are using SQLite.
// -- From this point you should not need to update anything. -- // -- From this point you should not need to update anything. --
$cfg['pear_path'] = '/usr/share/php'; $cfg['pear_path'] = '/usr/share/php';

View File

@ -1,5 +1,5 @@
{extends "idf/admin/base.html"} {extends "idf/admin/base.html"}
{block docclass}yui-t1{assign $inTabs = true}{/block} {block docclass}yui-t3{assign $inTabs = true}{/block}
{block body} {block body}
{if $form.errors} {if $form.errors}
<div class="px-message-error"> <div class="px-message-error">
@ -30,7 +30,21 @@
</td> </td>
</tr> </tr>
<tr> <tr>
<td colspan="2"> <th>{if $form.f.private_project.errors}{$form.f.private_project.fieldErrors}{/if}
{$form.f.private_project|unsafe}
</th>
<td>{$form.f.private_project.labelTag}</td>
</tr>
<tr id="authorized-users-row">
<td>&nbsp;</td>
<td>{$form.f.authorized_users.labelTag}:<br />
{if $form.f.authorized_users.errors}{$form.f.authorized_users.fieldErrors}{/if}
{$form.f.authorized_users|unsafe}
</td>
</tr>
<tr>
<td>&nbsp;</td>
<td>
<input type="submit" value="{trans 'Save Changes'}" name="submit" /> <input type="submit" value="{trans 'Save Changes'}" name="submit" />
</td> </td>
</tr> </tr>
@ -41,5 +55,24 @@
<div class="issue-submit-info"> <div class="issue-submit-info">
<p><strong>{trans 'Instructions:'}</strong></p> <p><strong>{trans 'Instructions:'}</strong></p>
<p>{blocktrans}You can configure here the project tabs access rights.{/blocktrans}</p> <p>{blocktrans}You can configure here the project tabs access rights.{/blocktrans}</p>
<p>{blocktrans}If you mark a project as private, only the project members and administrators, together with the extra authorized users you provide will have access to the project. You will still be able to define further access rights for the different tabs but the "Open to all" and "Signed in users" will default to authorized users only.{/blocktrans}</p>
<p>{blocktrans}Specify each person by its login. Each person must have already registered with the given login. Separate the logins with commas and/or new lines.{/blocktrans}</p>
</div> </div>
{/block} {/block}
{block javascript}{literal}
<script type="text/javascript">
$(document).ready(function(){
// If not checked, hide
if (!$("#id_private_project").is(":checked"))
$("#authorized-users-row").hide();
$("#id_private_project").click(function(){
if ($("#id_private_project").is(":checked")) {
$("#authorized-users-row").show();
} else {
$("#authorized-users-row").hide();
}
});
});
</script>
{/literal}{/block}