diff --git a/src/IDF/Form/TabsConf.php b/src/IDF/Form/TabsConf.php index fd961c7..a8e6613 100644 --- a/src/IDF/Form/TabsConf.php +++ b/src/IDF/Form/TabsConf.php @@ -28,9 +28,13 @@ class IDF_Form_TabsConf extends Pluf_Form { public $conf = null; + public $project = null; + public function initFields($extra=array()) { $this->conf = $extra['conf']; + $this->project = $extra['project']; + $ak = array('downloads_access_rights' => __('Downloads'), 'source_access_rights' => __('Source'), 'issues_access_rights' => __('Issues'),); @@ -51,7 +55,58 @@ class IDF_Form_TabsConf extends Pluf_Form '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(); } } + diff --git a/src/IDF/Migrations/6PrivateProject.php b/src/IDF/Migrations/6PrivateProject.php new file mode 100644 index 0000000..8db71c3 --- /dev/null +++ b/src/IDF/Migrations/6PrivateProject.php @@ -0,0 +1,54 @@ +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]); +} \ No newline at end of file diff --git a/src/IDF/Migrations/Install.php b/src/IDF/Migrations/Install.php index 8d6a9ad..a6de18d 100644 --- a/src/IDF/Migrations/Install.php +++ b/src/IDF/Migrations/Install.php @@ -38,6 +38,7 @@ function IDF_Migrations_Install_setup($params=null) 'IDF_Upload', 'IDF_Search_Occ', 'IDF_IssueFile', + 'IDF_Timeline', ); $db = Pluf::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'); if ($perm) $perm->delete(); $models = array( + 'IDF_Timeline', + 'IDF_IssueFile', 'IDF_Search_Occ', 'IDF_Upload', 'IDF_Conf', diff --git a/src/IDF/Precondition.php b/src/IDF/Precondition.php index 5944100..686f7ad 100644 --- a/src/IDF/Precondition.php +++ b/src/IDF/Precondition.php @@ -24,6 +24,26 @@ 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. * * @param Pluf_HTTP_Request @@ -98,16 +118,28 @@ class IDF_Precondition static public function accessSource($request) { + $res = self::baseAccess($request); + if (true !== $res) { + return $res; + } return self::accessTabGeneric($request, 'source_access_rights'); } static public function accessIssues($request) { + $res = self::baseAccess($request); + if (true !== $res) { + return $res; + } return self::accessTabGeneric($request, 'issues_access_rights'); } static public function accessDownloads($request) { + $res = self::baseAccess($request); + if (true !== $res) { + return $res; + } return self::accessTabGeneric($request, 'downloads_access_rights'); } } \ No newline at end of file diff --git a/src/IDF/Project.php b/src/IDF/Project.php index 1485ce6..9f76b1b 100644 --- a/src/IDF/Project.php +++ b/src/IDF/Project.php @@ -70,8 +70,14 @@ class IDF_Project extends Pluf_Model 'verbose' => __('description'), '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. * - * 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 * 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'); $operm = Pluf_Permission::getFromString('IDF.project-owner'); + $aperm = Pluf_Permission::getFromString('IDF.project-authorized-user'); $grow = new Pluf_RowPermission(); $db =& Pluf::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; } } + $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') { + $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)); + return new Pluf_Template_ContextVars(array('members' => $members, 'owners' => $owners, 'authorized' => $authorized)); } else { return array('members' => implode("\n", (array) $members), - 'owners' => implode("\n", (array) $owners)); + 'owners' => implode("\n", (array) $owners), + 'authorized' => implode("\n", (array) $authorized), + ); } } diff --git a/src/IDF/Views.php b/src/IDF/Views.php index b7a2e8b..b1b8ab4 100644 --- a/src/IDF/Views.php +++ b/src/IDF/Views.php @@ -33,10 +33,13 @@ class IDF_Views { /** * List all the projects managed by InDefero. + * + * Only the public projects are listed or the private with correct + * rights. */ public function index($request, $match) { - $projects = Pluf::factory('IDF_Project')->getList(); + $projects = self::getProjects($request->user); return Pluf_Shortcuts_RenderToResponse('idf/index.html', array('page_title' => __('Projects'), 'projects' => $projects), @@ -171,7 +174,7 @@ class IDF_Views public function faq($request, $match) { $title = __('Here to Help You!'); - $projects = Pluf::factory('IDF_Project')->getList(); + $projects = self::getProjects($request->user); return Pluf_Shortcuts_RenderToResponse('idf/faq.html', array( 'page_title' => $title, @@ -180,4 +183,42 @@ class IDF_Views $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)); + } } \ No newline at end of file diff --git a/src/IDF/Views/Project.php b/src/IDF/Views/Project.php index f166527..fa6b31f 100644 --- a/src/IDF/Views/Project.php +++ b/src/IDF/Views/Project.php @@ -34,6 +34,7 @@ class IDF_Views_Project /** * Home page of a project. */ + public $home_precond = array('IDF_Precondition::baseAccess'); public function home($request, $match) { $prj = $request->project; @@ -57,6 +58,7 @@ class IDF_Views_Project /** * Timeline of the project. */ + public $timeline_precond = array('IDF_Precondition::baseAccess'); public function timeline($request, $match) { $prj = $request->project; @@ -272,6 +274,7 @@ class IDF_Views_Project $prj = $request->project; $title = sprintf(__('%s Tabs Access Rights'), (string) $prj); $extra = array( + 'project' => $prj, 'conf' => $request->conf, ); if ($request->method == 'POST') { @@ -280,6 +283,7 @@ class IDF_Views_Project foreach ($form->cleaned_data as $key=>$val) { $request->conf->setVal($key, $val); } + $form->save(); // Save the authorized users. $request->user->setMessage(__('The project tabs access rights have been saved.')); $url = Pluf_HTTP_URL_urlForView('IDF_Views_Project::adminTabs', array($prj->shortname)); @@ -288,13 +292,16 @@ class IDF_Views_Project } else { $params = array(); $keys = array('downloads_access_rights', 'source_access_rights', - 'issues_access_rights'); + 'issues_access_rights', 'private_project'); foreach ($keys as $key) { $_val = $request->conf->getVal($key, false); if ($_val !== false) { $params[$key] = $_val; } } + // Add the authorized users. + $md = $prj->getMembershipData('string'); + $params['authorized_users'] = $md['authorized']; if (count($params) == 0) { $params = null; //Nothing in the db, so new form. } diff --git a/src/IDF/conf/idf.php-dist b/src/IDF/conf/idf.php-dist index 46b2614..79c7857 100644 --- a/src/IDF/conf/idf.php-dist +++ b/src/IDF/conf/idf.php-dist @@ -122,9 +122,15 @@ $cfg['db_password'] = ''; $cfg['db_server'] = ''; $cfg['db_version'] = ''; $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_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. -- $cfg['pear_path'] = '/usr/share/php'; diff --git a/src/IDF/templates/idf/admin/tabs.html b/src/IDF/templates/idf/admin/tabs.html index c768035..8c42c33 100644 --- a/src/IDF/templates/idf/admin/tabs.html +++ b/src/IDF/templates/idf/admin/tabs.html @@ -1,5 +1,5 @@ {extends "idf/admin/base.html"} -{block docclass}yui-t1{assign $inTabs = true}{/block} +{block docclass}yui-t3{assign $inTabs = true}{/block} {block body} {if $form.errors}
@@ -30,7 +30,21 @@ - +{if $form.f.private_project.errors}{$form.f.private_project.fieldErrors}{/if} +{$form.f.private_project|unsafe} + +{$form.f.private_project.labelTag} + + +  +{$form.f.authorized_users.labelTag}:
+{if $form.f.authorized_users.errors}{$form.f.authorized_users.fieldErrors}{/if} +{$form.f.authorized_users|unsafe} + + + +  + @@ -41,5 +55,24 @@

{trans 'Instructions:'}

{blocktrans}You can configure here the project tabs access rights.{/blocktrans}

+

{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}

+

{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}

{/block} + +{block javascript}{literal} + +{/literal}{/block}