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}