diff options
author | Frederic Guillot <fred@kanboard.net> | 2016-09-11 16:08:03 -0400 |
---|---|---|
committer | Frederic Guillot <fred@kanboard.net> | 2016-09-11 16:08:03 -0400 |
commit | d8f6d8568396816a6bfaca1e01211384e803cf91 (patch) | |
tree | 16d735faa3f6f9aafb6c78650470e77347cae1ab | |
parent | a0227cad69aff9486fba1d7b2a19e6da97450100 (diff) |
Add project restrictions for custom roles
25 files changed, 700 insertions, 288 deletions
diff --git a/app/Controller/BoardAjaxController.php b/app/Controller/BoardAjaxController.php index cc3b846e..484ef67d 100644 --- a/app/Controller/BoardAjaxController.php +++ b/app/Controller/BoardAjaxController.php @@ -28,14 +28,8 @@ class BoardAjaxController extends BaseController } $values = $this->request->getJson(); - $canMoveTask = $this->columnMoveRestrictionModel->isAllowed( - $project_id, - $this->helper->user->getProjectUserRole($project_id), - $values['src_column_id'], - $values['dst_column_id'] - ); - if (! $canMoveTask) { + if (! $this->helper->projectRole->canMoveTask($project_id, $values['src_column_id'], $values['dst_column_id'])) { throw new AccessForbiddenException(e("You don't have the permission to move this task")); } diff --git a/app/Controller/ColumnMoveRestrictionController.php b/app/Controller/ColumnMoveRestrictionController.php index 3f1b878f..b12f6b77 100644 --- a/app/Controller/ColumnMoveRestrictionController.php +++ b/app/Controller/ColumnMoveRestrictionController.php @@ -45,14 +45,14 @@ class ColumnMoveRestrictionController extends BaseController list($valid, $errors) = $this->columnMoveRestrictionValidator->validateCreation($values); if ($valid) { - $role_id = $this->columnMoveRestrictionModel->create( + $restriction_id = $this->columnMoveRestrictionModel->create( $project['id'], $values['role_id'], $values['src_column_id'], $values['dst_column_id'] ); - if ($role_id !== false) { + if ($restriction_id !== false) { $this->flash->success(t('The column restriction has been created successfully.')); } else { $this->flash->failure(t('Unable to create this column restriction.')); diff --git a/app/Controller/ProjectRoleRestrictionController.php b/app/Controller/ProjectRoleRestrictionController.php new file mode 100644 index 00000000..4fa9b13b --- /dev/null +++ b/app/Controller/ProjectRoleRestrictionController.php @@ -0,0 +1,96 @@ +<?php + +namespace Kanboard\Controller; + +use Kanboard\Core\Controller\AccessForbiddenException; + +/** + * Class ProjectRoleRestrictionController + * + * @package Kanboard\Controller + * @author Frederic Guillot + */ +class ProjectRoleRestrictionController extends BaseController +{ + /** + * Show form to create a new project restriction + * + * @param array $values + * @param array $errors + * @throws AccessForbiddenException + */ + public function create(array $values = array(), array $errors = array()) + { + $project = $this->getProject(); + $role_id = $this->request->getIntegerParam('role_id'); + $role = $this->projectRoleModel->getById($project['id'], $role_id); + + $this->response->html($this->template->render('project_role_restriction/create', array( + 'project' => $project, + 'role' => $role, + 'values' => $values + array('project_id' => $project['id'], 'role_id' => $role['role_id']), + 'errors' => $errors, + 'restrictions' => $this->projectRoleRestrictionModel->getRules(), + ))); + } + + /** + * Save new restriction + */ + public function save() + { + $project = $this->getProject(); + $values = $this->request->getValues(); + + $restriction_id = $this->projectRoleRestrictionModel->create( + $project['id'], + $values['role_id'], + $values['rule'] + ); + + if ($restriction_id !== false) { + $this->flash->success(t('The project restriction has been created successfully.')); + } else { + $this->flash->failure(t('Unable to create this project restriction.')); + } + + $this->response->redirect($this->helper->url->to('ProjectRoleController', 'show', array('project_id' => $project['id']))); + } + + /** + * Confirm suppression + * + * @access public + */ + public function confirm() + { + $project = $this->getProject(); + $restriction_id = $this->request->getIntegerParam('restriction_id'); + + $this->response->html($this->helper->layout->project('project_role_restriction/remove', array( + 'project' => $project, + 'restriction' => $this->projectRoleRestrictionModel->getById($project['id'], $restriction_id), + 'restrictions' => $this->projectRoleRestrictionModel->getRules(), + ))); + } + + /** + * Remove a restriction + * + * @access public + */ + public function remove() + { + $project = $this->getProject(); + $this->checkCSRFParam(); + $restriction_id = $this->request->getIntegerParam('restriction_id'); + + if ($this->projectRoleRestrictionModel->remove($restriction_id)) { + $this->flash->success(t('Project restriction removed successfully.')); + } else { + $this->flash->failure(t('Unable to remove this restriction.')); + } + + $this->response->redirect($this->helper->url->to('ProjectRoleController', 'show', array('project_id' => $project['id']))); + } +} diff --git a/app/Controller/TaskSuppressionController.php b/app/Controller/TaskSuppressionController.php index 600107c9..019bd97c 100644 --- a/app/Controller/TaskSuppressionController.php +++ b/app/Controller/TaskSuppressionController.php @@ -19,7 +19,7 @@ class TaskSuppressionController extends BaseController { $task = $this->getTask(); - if (! $this->helper->user->canRemoveTask($task)) { + if (! $this->helper->projectRole->canRemoveTask($task)) { throw new AccessForbiddenException(); } @@ -37,7 +37,7 @@ class TaskSuppressionController extends BaseController $task = $this->getTask(); $this->checkCSRFParam(); - if (! $this->helper->user->canRemoveTask($task)) { + if (! $this->helper->projectRole->canRemoveTask($task)) { throw new AccessForbiddenException(); } diff --git a/app/Core/Base.php b/app/Core/Base.php index cadaef72..d6da13f2 100644 --- a/app/Core/Base.php +++ b/app/Core/Base.php @@ -91,6 +91,7 @@ use Pimple\Container; * @property \Kanboard\Model\ProjectNotificationModel $projectNotificationModel * @property \Kanboard\Model\ProjectNotificationTypeModel $projectNotificationTypeModel * @property \Kanboard\Model\ProjectRoleModel $projectRoleModel + * @property \Kanboard\Model\ProjectRoleRestrictionModel $projectRoleRestrictionModel * @property \Kanboard\Model\ProjectTaskDuplicationModel $projectTaskDuplicationModel * @property \Kanboard\Model\ProjectTaskPriorityModel $projectTaskPriorityModel * @property \Kanboard\Model\RememberMeSessionModel $rememberMeSessionModel diff --git a/app/Core/Helper.php b/app/Core/Helper.php index c98b3c5e..b5c560af 100644 --- a/app/Core/Helper.php +++ b/app/Core/Helper.php @@ -26,6 +26,7 @@ use Pimple\Container; * @property \Kanboard\Helper\UrlHelper $url * @property \Kanboard\Helper\UserHelper $user * @property \Kanboard\Helper\LayoutHelper $layout + * @property \Kanboard\Helper\ProjectRoleHelper $projectRole * @property \Kanboard\Helper\ProjectHeaderHelper $projectHeader * @property \Kanboard\Helper\ProjectActivityHelper $projectActivity * @property \Kanboard\Helper\MailHelper $mail diff --git a/app/Formatter/BoardTaskFormatter.php b/app/Formatter/BoardTaskFormatter.php index 5956ae35..cd10f77a 100644 --- a/app/Formatter/BoardTaskFormatter.php +++ b/app/Formatter/BoardTaskFormatter.php @@ -81,7 +81,7 @@ class BoardTaskFormatter extends BaseFormatter implements FormatterInterface array_merge_relation($tasks, $this->tags, 'tags', 'id'); foreach ($tasks as &$task) { - $task['is_draggable'] = $this->helper->board->isDraggable($task); + $task['is_draggable'] = $this->helper->projectRole->isDraggable($task); } return $tasks; diff --git a/app/Helper/BoardHelper.php b/app/Helper/BoardHelper.php index 9e8e78ac..f5df3db2 100644 --- a/app/Helper/BoardHelper.php +++ b/app/Helper/BoardHelper.php @@ -24,26 +24,4 @@ class BoardHelper extends Base { return $this->userMetadataCacheDecorator->get(UserMetadataModel::KEY_BOARD_COLLAPSED.$project_id, 0) == 1; } - - /** - * Return true if the task can be moved by the connected user - * - * @param array $task - * @return bool - */ - public function isDraggable(array $task) - { - if ($task['is_active'] == 1 && $this->helper->user->hasProjectAccess('BoardViewController', 'save', $task['project_id'])) { - $role = $this->helper->user->getProjectUserRole($task['project_id']); - - if ($this->role->isCustomProjectRole($role)) { - $srcColumnIds = $this->columnMoveRestrictionCacheDecorator->getAllSrcColumns($task['project_id'], $role); - return isset($srcColumnIds[$task['column_id']]); - } - - return true; - } - - return false; - } } diff --git a/app/Helper/ProjectRoleHelper.php b/app/Helper/ProjectRoleHelper.php new file mode 100644 index 00000000..34905b52 --- /dev/null +++ b/app/Helper/ProjectRoleHelper.php @@ -0,0 +1,130 @@ +<?php + +namespace Kanboard\Helper; + +use Kanboard\Core\Base; +use Kanboard\Core\Security\Role; + +/** + * Class ProjectRoleHelper + * + * @package Kanboard\Helper + * @author Frederic Guillot + */ +class ProjectRoleHelper extends Base +{ + /** + * Get project role for the current user + * + * @access public + * @param integer $project_id + * @return string + */ + public function getProjectUserRole($project_id) + { + return $this->memoryCache->proxy($this->projectUserRoleModel, 'getUserRole', $project_id, $this->userSession->getId()); + } + + /** + * Return true if the task can be moved by the connected user + * + * @param array $task + * @return bool + */ + public function isDraggable(array $task) + { + if ($task['is_active'] == 1 && $this->helper->user->hasProjectAccess('BoardViewController', 'save', $task['project_id'])) { + $role = $this->getProjectUserRole($task['project_id']); + + if ($this->role->isCustomProjectRole($role)) { + $srcColumnIds = $this->columnMoveRestrictionCacheDecorator->getAllSrcColumns($task['project_id'], $role); + return isset($srcColumnIds[$task['column_id']]); + } + + return true; + } + + return false; + } + + /** + * Check if the user can move a task + * + * @param int $project_id + * @param int $src_column_id + * @param int $dst_column_id + * @return bool|int + */ + public function canMoveTask($project_id, $src_column_id, $dst_column_id) + { + $role = $this->getProjectUserRole($project_id); + + if ($this->role->isCustomProjectRole($role)) { + return $this->columnMoveRestrictionModel->isAllowed( + $project_id, + $role, + $src_column_id, + $dst_column_id + ); + } + + return true; + } + + /** + * Return true if the user can remove a task + * + * Regular users can't remove tasks from other people + * + * @public + * @param array $task + * @return bool + */ + public function canRemoveTask(array $task) + { + if (isset($task['creator_id']) && $task['creator_id'] == $this->userSession->getId()) { + return true; + } + + if ($this->userSession->isAdmin() || $this->getProjectUserRole($task['project_id']) === Role::PROJECT_MANAGER) { + return true; + } + + return false; + } + + /** + * Check project access + * + * @param string $controller + * @param string $action + * @param integer $project_id + * @return bool + */ + public function checkProjectAccess($controller, $action, $project_id) + { + if (! $this->userSession->isLogged()) { + return false; + } + + if ($this->userSession->isAdmin()) { + return true; + } + + if (! $this->helper->user->hasAccess($controller, $action)) { + return false; + } + + $role = $this->getProjectUserRole($project_id); + + if ($this->role->isCustomProjectRole($role)) { + $restrictions = $this->projectRoleRestrictionModel->getAllByRole($project_id, $role); + $result = $this->projectRoleRestrictionModel->isAllowed($restrictions, $controller, $action); + $result = $result && $this->projectAuthorization->isAllowed($controller, $action, Role::PROJECT_MEMBER); + } else { + $result = $this->projectAuthorization->isAllowed($controller, $action, $role); + } + + return $result; + } +} diff --git a/app/Helper/UserHelper.php b/app/Helper/UserHelper.php index 17c66616..8c2567b9 100644 --- a/app/Helper/UserHelper.php +++ b/app/Helper/UserHelper.php @@ -3,7 +3,6 @@ namespace Kanboard\Helper; use Kanboard\Core\Base; -use Kanboard\Core\Security\Role; /** * User helpers @@ -133,66 +132,14 @@ class UserHelper extends Base */ public function hasProjectAccess($controller, $action, $project_id) { - if (! $this->userSession->isLogged()) { - return false; - } - - if ($this->userSession->isAdmin()) { - return true; - } - - if (! $this->hasAccess($controller, $action)) { - return false; - } - $key = 'project_access:'.$controller.$action.$project_id; $result = $this->memoryCache->get($key); if ($result === null) { - $role = $this->getProjectUserRole($project_id); - - if ($this->role->isCustomProjectRole($role)) { - $role = Role::PROJECT_MEMBER; - } - - $result = $this->projectAuthorization->isAllowed($controller, $action, $role); + $result = $this->helper->projectRole->checkProjectAccess($controller, $action, $project_id); $this->memoryCache->set($key, $result); } return $result; } - - /** - * Get project role for the current user - * - * @access public - * @param integer $project_id - * @return string - */ - public function getProjectUserRole($project_id) - { - return $this->memoryCache->proxy($this->projectUserRoleModel, 'getUserRole', $project_id, $this->userSession->getId()); - } - - /** - * Return true if the user can remove a task - * - * Regular users can't remove tasks from other people - * - * @public - * @param array $task - * @return bool - */ - public function canRemoveTask(array $task) - { - if (isset($task['creator_id']) && $task['creator_id'] == $this->userSession->getId()) { - return true; - } - - if ($this->userSession->isAdmin() || $this->getProjectUserRole($task['project_id']) === Role::PROJECT_MANAGER) { - return true; - } - - return false; - } } diff --git a/app/Model/ProjectRoleModel.php b/app/Model/ProjectRoleModel.php index 82f22806..ed86d6ed 100644 --- a/app/Model/ProjectRoleModel.php +++ b/app/Model/ProjectRoleModel.php @@ -17,7 +17,7 @@ class ProjectRoleModel extends Base /** * Get list of project roles - * + * * @param int $project_id * @return array */ @@ -70,9 +70,14 @@ class ProjectRoleModel extends Base public function getAllWithRestrictions($project_id) { $roles = $this->getAll($project_id); - $restrictions = $this->columnMoveRestrictionModel->getAll($project_id); - $restrictions = array_column_index($restrictions, 'role_id'); - array_merge_relation($roles, $restrictions, 'restrictions', 'role_id'); + + $column_restrictions = $this->columnMoveRestrictionModel->getAll($project_id); + $column_restrictions = array_column_index($column_restrictions, 'role_id'); + array_merge_relation($roles, $column_restrictions, 'column_restrictions', 'role_id'); + + $project_restrictions = $this->projectRoleRestrictionModel->getAll($project_id); + $project_restrictions = array_column_index($project_restrictions, 'role_id'); + array_merge_relation($roles, $project_restrictions, 'project_restrictions', 'role_id'); return $roles; } diff --git a/app/Model/ProjectRoleRestrictionModel.php b/app/Model/ProjectRoleRestrictionModel.php new file mode 100644 index 00000000..0411838d --- /dev/null +++ b/app/Model/ProjectRoleRestrictionModel.php @@ -0,0 +1,164 @@ +<?php + +namespace Kanboard\Model; + +use Kanboard\Core\Base; + +/** + * Class ProjectRoleRestrictionModel + * + * @package Kanboard\Model + * @author Frederic Guillot + */ +class ProjectRoleRestrictionModel extends Base +{ + const TABLE = 'project_role_has_restrictions'; + const RULE_TASK_CREATION = 'task_creation'; + + protected $ruleMapping = array( + self::RULE_TASK_CREATION => array( + array('controller' => 'TaskCreationController', 'method' => '*'), + ) + ); + + /** + * Get rules + * + * @return array + */ + public function getRules() + { + return array( + self::RULE_TASK_CREATION => t('Task creation is not permitted'), + ); + } + + /** + * Get a single restriction + * + * @param integer $project_id + * @param integer $restriction_id + * @return array|null + */ + public function getById($project_id, $restriction_id) + { + return $this->db + ->table(self::TABLE) + ->eq('project_id', $project_id) + ->eq('restriction_id', $restriction_id) + ->findOne(); + } + + /** + * Get restrictions + * + * @param int $project_id + * @return array + */ + public function getAll($project_id) + { + $rules = $this->getRules(); + $restrictions = $this->db + ->table(self::TABLE) + ->columns( + self::TABLE.'.restriction_id', + self::TABLE.'.project_id', + self::TABLE.'.role_id', + self::TABLE.'.rule' + ) + ->eq(self::TABLE.'.project_id', $project_id) + ->findAll(); + + foreach ($restrictions as &$restriction) { + $restriction['title'] = $rules[$restriction['rule']]; + } + + return $restrictions; + } + + /** + * Get restrictions + * + * @param int $project_id + * @param string $role + * @return array + */ + public function getAllByRole($project_id, $role) + { + $rules = $this->db + ->table(self::TABLE) + ->columns( + self::TABLE.'.restriction_id', + self::TABLE.'.project_id', + self::TABLE.'.role_id', + self::TABLE.'.rule', + 'pr.role' + ) + ->eq(self::TABLE.'.project_id', $project_id) + ->eq('role', $role) + ->left(ProjectRoleModel::TABLE, 'pr', 'role_id', self::TABLE, 'role_id') + ->findAll(); + + foreach ($rules as &$rule) { + $rule['acl'] = $this->ruleMapping[$rule['rule']]; + } + + return $rules; + } + + /** + * Create a new restriction + * + * @param int $project_id + * @param int $role_id + * @param string $rule + * @return bool|int + */ + public function create($project_id, $role_id, $rule) + { + return $this->db->table(self::TABLE) + ->persist(array( + 'project_id' => $project_id, + 'role_id' => $role_id, + 'rule' => $rule, + )); + } + + /** + * Remove a restriction + * + * @param integer $restriction_id + * @return bool + */ + public function remove($restriction_id) + { + return $this->db->table(self::TABLE)->eq('restriction_id', $restriction_id)->remove(); + } + + /** + * Check if the controller/method is allowed + * + * @param array $restrictions + * @param string $controller + * @param string $method + * @return bool + */ + public function isAllowed(array $restrictions, $controller, $method) + { + $controller = strtolower($controller); + $method = strtolower($method); + + foreach ($restrictions as $restriction) { + foreach ($restriction['acl'] as $acl) { + $acl['controller'] = strtolower($acl['controller']); + $acl['method'] = strtolower($acl['method']); + + if ($acl['controller'] === $controller && ($acl['method'] === '*' || $acl['method'] === $method)) { + return false; + } + } + } + + return true; + } +} diff --git a/app/Schema/Mysql.php b/app/Schema/Mysql.php index 11c7f232..0da54597 100644 --- a/app/Schema/Mysql.php +++ b/app/Schema/Mysql.php @@ -6,7 +6,22 @@ use PDO; use Kanboard\Core\Security\Token; use Kanboard\Core\Security\Role; -const VERSION = 113; +const VERSION = 114; + +function version_114(PDO $pdo) +{ + $pdo->exec(" + CREATE TABLE project_role_has_restrictions ( + restriction_id INT NOT NULL AUTO_INCREMENT, + project_id INT NOT NULL, + role_id INT NOT NULL, + rule VARCHAR(255) NOT NULL, + UNIQUE(role_id, rule), + FOREIGN KEY(project_id) REFERENCES projects(id) ON DELETE CASCADE, + FOREIGN KEY(role_id) REFERENCES project_has_roles(role_id) ON DELETE CASCADE + ) ENGINE=InnoDB CHARSET=utf8 + "); +} function version_113(PDO $pdo) { diff --git a/app/Schema/Postgres.php b/app/Schema/Postgres.php index 2d1695d0..56cd9de9 100644 --- a/app/Schema/Postgres.php +++ b/app/Schema/Postgres.php @@ -6,7 +6,22 @@ use PDO; use Kanboard\Core\Security\Token; use Kanboard\Core\Security\Role; -const VERSION = 92; +const VERSION = 93; + +function version_93(PDO $pdo) +{ + $pdo->exec(" + CREATE TABLE project_role_has_restrictions ( + restriction_id SERIAL PRIMARY KEY, + project_id INTEGER NOT NULL, + role_id INTEGER NOT NULL, + rule VARCHAR(255) NOT NULL, + UNIQUE(role_id, rule), + FOREIGN KEY(project_id) REFERENCES projects(id) ON DELETE CASCADE, + FOREIGN KEY(role_id) REFERENCES project_has_roles(role_id) ON DELETE CASCADE + ) + "); +} function version_92(PDO $pdo) { diff --git a/app/Schema/Sqlite.php b/app/Schema/Sqlite.php index ecfa93de..c952d58f 100644 --- a/app/Schema/Sqlite.php +++ b/app/Schema/Sqlite.php @@ -6,7 +6,22 @@ use Kanboard\Core\Security\Token; use Kanboard\Core\Security\Role; use PDO; -const VERSION = 104; +const VERSION = 105; + +function version_105(PDO $pdo) +{ + $pdo->exec(" + CREATE TABLE project_role_has_restrictions ( + restriction_id INTEGER PRIMARY KEY, + project_id INTEGER NOT NULL, + role_id INTEGER NOT NULL, + rule VARCHAR(255) NOT NULL, + UNIQUE(role_id, rule), + FOREIGN KEY(project_id) REFERENCES projects(id) ON DELETE CASCADE, + FOREIGN KEY(role_id) REFERENCES project_has_roles(role_id) ON DELETE CASCADE + ) + "); +} function version_104(PDO $pdo) { diff --git a/app/ServiceProvider/ClassProvider.php b/app/ServiceProvider/ClassProvider.php index 98669e6d..4841d1f0 100644 --- a/app/ServiceProvider/ClassProvider.php +++ b/app/ServiceProvider/ClassProvider.php @@ -57,6 +57,7 @@ class ClassProvider implements ServiceProviderInterface 'ProjectMetadataModel', 'ProjectGroupRoleModel', 'ProjectRoleModel', + 'ProjectRoleRestrictionModel', 'ProjectTaskDuplicationModel', 'ProjectTaskPriorityModel', 'ProjectUserRoleModel', diff --git a/app/ServiceProvider/HelperProvider.php b/app/ServiceProvider/HelperProvider.php index a909e3cf..f4c0db22 100644 --- a/app/ServiceProvider/HelperProvider.php +++ b/app/ServiceProvider/HelperProvider.php @@ -35,6 +35,7 @@ class HelperProvider implements ServiceProviderInterface $container['helper']->register('url', '\Kanboard\Helper\UrlHelper'); $container['helper']->register('user', '\Kanboard\Helper\UserHelper'); $container['helper']->register('avatar', '\Kanboard\Helper\AvatarHelper'); + $container['helper']->register('projectRole', '\Kanboard\Helper\ProjectRoleHelper'); $container['helper']->register('projectHeader', '\Kanboard\Helper\ProjectHeaderHelper'); $container['helper']->register('projectActivity', '\Kanboard\Helper\ProjectActivityHelper'); $container['helper']->register('mail', '\Kanboard\Helper\MailHelper'); diff --git a/app/Template/project_role/show.php b/app/Template/project_role/show.php index 5fbd413b..595416ac 100644 --- a/app/Template/project_role/show.php +++ b/app/Template/project_role/show.php @@ -20,6 +20,10 @@ <ul> <li> <i class="fa fa-plus fa-fw" aria-hidden="true"></i> + <?= $this->url->link(t('Add a new project restriction'), 'ProjectRoleRestrictionController', 'create', array('project_id' => $project['id'], 'role_id' => $role['role_id']), false, 'popover') ?> + </li> + <li> + <i class="fa fa-plus fa-fw" aria-hidden="true"></i> <?= $this->url->link(t('Add a new column restriction'), 'ColumnMoveRestrictionController', 'create', array('project_id' => $project['id'], 'role_id' => $role['role_id']), false, 'popover') ?> </li> <li> @@ -33,15 +37,26 @@ <?= t('Actions') ?> </th> </tr> - <?php if (empty($role['restrictions'])): ?> + <?php if (empty($role['project_restrictions']) && empty($role['column_restrictions'])): ?> <tr> <td colspan="2"><?= t('There is no restriction for this role.') ?></td> </tr> <?php else: ?> - <?php foreach ($role['restrictions'] as $restriction): ?> + <?php foreach ($role['project_restrictions'] as $restriction): ?> + <tr> + <td> + <?= $this->text->e($restriction['title']) ?> + </td> + <td> + <i class="fa fa-trash-o fa-fw" aria-hidden="true"></i> + <?= $this->url->link(t('Remove'), 'ProjectRoleRestrictionController', 'confirm', array('project_id' => $project['id'], 'restriction_id' => $restriction['restriction_id']), false, 'popover') ?> + </td> + </tr> + <?php endforeach ?> + <?php foreach ($role['column_restrictions'] as $restriction): ?> <tr> <td> - <?= t('Moving task from the column "%s" to "%s" is permitted', $restriction['src_column_title'], $restriction['dst_column_title']) ?> + <?= t('Only moving task from the column "%s" to "%s" is permitted', $restriction['src_column_title'], $restriction['dst_column_title']) ?> </td> <td> <i class="fa fa-trash-o fa-fw" aria-hidden="true"></i> diff --git a/app/Template/project_role_restriction/create.php b/app/Template/project_role_restriction/create.php new file mode 100644 index 00000000..f49eafb3 --- /dev/null +++ b/app/Template/project_role_restriction/create.php @@ -0,0 +1,19 @@ +<section id="main"> + <div class="page-header"> + <h2><?= t('New project restriction for the role "%s"', $role['role']) ?></h2> + </div> + <form class="popover-form" method="post" action="<?= $this->url->href('ProjectRoleRestrictionController', 'save', array('project_id' => $project['id'])) ?>" autocomplete="off"> + <?= $this->form->csrf() ?> + <?= $this->form->hidden('project_id', $values) ?> + <?= $this->form->hidden('role_id', $values) ?> + + <?= $this->form->label(t('Restriction'), 'rule') ?> + <?= $this->form->select('rule', $restrictions, $values, $errors) ?> + + <div class="form-actions"> + <button type="submit" class="btn btn-blue"><?= t('Save') ?></button> + <?= t('or') ?> + <?= $this->url->link(t('cancel'), 'ProjectRoleController', 'show', array(), false, 'close-popover') ?> + </div> + </form> +</section> diff --git a/app/Template/project_role_restriction/remove.php b/app/Template/project_role_restriction/remove.php new file mode 100644 index 00000000..db1148e1 --- /dev/null +++ b/app/Template/project_role_restriction/remove.php @@ -0,0 +1,14 @@ +<div class="page-header"> + <h2><?= t('Remove a project restriction') ?></h2> +</div> + +<div class="confirm"> + <p class="alert alert-info"> + <?= t('Do you really want to remove this project restriction: "%s"?', $this->text->in($restriction['rule'], $restrictions)) ?> + </p> + + <div class="form-actions"> + <?= $this->url->link(t('Yes'), 'ProjectRoleRestrictionController', 'remove', array('project_id' => $project['id'], 'restriction_id' => $restriction['restriction_id']), true, 'btn btn-red') ?> + <?= t('or') ?> <?= $this->url->link(t('cancel'), 'ProjectRoleController', 'show', array('project_id' => $project['id']), false, 'close-popover') ?> + </div> +</div> diff --git a/app/Template/task/dropdown.php b/app/Template/task/dropdown.php index 95c7a88c..19c8316e 100644 --- a/app/Template/task/dropdown.php +++ b/app/Template/task/dropdown.php @@ -43,7 +43,7 @@ <i class="fa fa-clone fa-fw"></i> <?= $this->url->link(t('Move to another project'), 'TaskDuplicationController', 'move', array('task_id' => $task['id'], 'project_id' => $task['project_id']), false, 'popover') ?> </li> - <?php if ($this->user->canRemoveTask($task)): ?> + <?php if ($this->projectRole->canRemoveTask($task)): ?> <li> <i class="fa fa-trash-o fa-fw"></i> <?= $this->url->link(t('Remove'), 'TaskSuppressionController', 'confirm', array('task_id' => $task['id'], 'project_id' => $task['project_id']), false, 'popover') ?> diff --git a/app/Template/task/sidebar.php b/app/Template/task/sidebar.php index de0750ff..827c2f39 100644 --- a/app/Template/task/sidebar.php +++ b/app/Template/task/sidebar.php @@ -93,7 +93,7 @@ <?= $this->url->link(t('Open this task'), 'TaskStatusController', 'open', array('task_id' => $task['id'], 'project_id' => $task['project_id']), false, 'popover') ?> </li> <?php endif ?> - <?php if ($this->user->canRemoveTask($task)): ?> + <?php if ($this->projectRole->canRemoveTask($task)): ?> <li> <i class="fa fa-trash-o fa-fw"></i> <?= $this->url->link(t('Remove'), 'TaskSuppressionController', 'confirm', array('task_id' => $task['id'], 'project_id' => $task['project_id'], 'redirect' => 'board'), false, 'popover') ?> diff --git a/tests/units/Helper/BoardHelperTest.php b/tests/units/Helper/BoardHelperTest.php deleted file mode 100644 index 0a44f8ab..00000000 --- a/tests/units/Helper/BoardHelperTest.php +++ /dev/null @@ -1,98 +0,0 @@ -<?php - -use Kanboard\Core\Security\Role; -use Kanboard\Helper\BoardHelper; -use Kanboard\Model\ColumnMoveRestrictionModel; -use Kanboard\Model\ProjectModel; -use Kanboard\Model\ProjectRoleModel; -use Kanboard\Model\ProjectUserRoleModel; -use Kanboard\Model\TaskCreationModel; -use Kanboard\Model\TaskFinderModel; -use Kanboard\Model\TaskStatusModel; -use Kanboard\Model\UserModel; - -require_once __DIR__.'/../Base.php'; - -class BoardHelperTest extends Base -{ - public function testIsDraggableWithProjectMember() - { - $boardHelper = new BoardHelper($this->container); - $projectModel = new ProjectModel($this->container); - $taskCreationModel = new TaskCreationModel($this->container); - $taskFinderModel = new TaskFinderModel($this->container); - $projectUserRole = new ProjectUserRoleModel($this->container); - $userModel = new UserModel($this->container); - - $this->container['sessionStorage']->user = array( - 'id' => 2, - 'role' => Role::APP_USER, - ); - - $this->assertEquals(2, $userModel->create(array('username' => 'user'))); - $this->assertEquals(1, $projectModel->create(array('name' => 'Test'))); - $this->assertTrue($projectUserRole->addUser(1, 2, Role::PROJECT_MEMBER)); - $this->assertEquals(1, $taskCreationModel->create(array('project_id' => 1, 'title' => 'test'))); - - $task = $taskFinderModel->getById(1); - $this->assertTrue($boardHelper->isDraggable($task)); - } - - public function testIsDraggableWithClosedTask() - { - $boardHelper = new BoardHelper($this->container); - $projectModel = new ProjectModel($this->container); - $taskCreationModel = new TaskCreationModel($this->container); - $taskFinderModel = new TaskFinderModel($this->container); - $taskStatusModel = new TaskStatusModel($this->container); - $projectUserRole = new ProjectUserRoleModel($this->container); - $userModel = new UserModel($this->container); - - $this->container['sessionStorage']->user = array( - 'id' => 2, - 'role' => Role::APP_USER, - ); - - $this->assertEquals(2, $userModel->create(array('username' => 'user'))); - $this->assertEquals(1, $projectModel->create(array('name' => 'Test'))); - $this->assertTrue($projectUserRole->addUser(1, 2, Role::PROJECT_MEMBER)); - $this->assertEquals(1, $taskCreationModel->create(array('project_id' => 1, 'title' => 'test'))); - $this->assertTrue($taskStatusModel->close(1)); - - $task = $taskFinderModel->getById(1); - $this->assertFalse($boardHelper->isDraggable($task)); - } - - public function testIsDraggableWithColumnRestrictions() - { - $boardHelper = new BoardHelper($this->container); - $projectModel = new ProjectModel($this->container); - $taskCreationModel = new TaskCreationModel($this->container); - $taskFinderModel = new TaskFinderModel($this->container); - $projectUserRole = new ProjectUserRoleModel($this->container); - $userModel = new UserModel($this->container); - $projectRoleModel = new ProjectRoleModel($this->container); - $columnMoveRestrictionModel = new ColumnMoveRestrictionModel($this->container); - - $this->container['sessionStorage']->user = array( - 'id' => 2, - 'role' => Role::APP_USER, - ); - - $this->assertEquals(2, $userModel->create(array('username' => 'user'))); - $this->assertEquals(1, $projectModel->create(array('name' => 'Test'))); - - $this->assertEquals(1, $projectRoleModel->create(1, 'Custom Role')); - $this->assertEquals(1, $columnMoveRestrictionModel->create(1, 1, 2, 3)); - - $this->assertTrue($projectUserRole->addUser(1, 2, 'Custom Role')); - $this->assertEquals(1, $taskCreationModel->create(array('project_id' => 1, 'title' => 'test', 'column_id' => 2))); - $this->assertEquals(2, $taskCreationModel->create(array('project_id' => 1, 'title' => 'test', 'column_id' => 3))); - - $task = $taskFinderModel->getById(1); - $this->assertTrue($boardHelper->isDraggable($task)); - - $task = $taskFinderModel->getById(2); - $this->assertFalse($boardHelper->isDraggable($task)); - } -} diff --git a/tests/units/Helper/ProjectRoleHelperTest.php b/tests/units/Helper/ProjectRoleHelperTest.php new file mode 100644 index 00000000..cae22d71 --- /dev/null +++ b/tests/units/Helper/ProjectRoleHelperTest.php @@ -0,0 +1,189 @@ +<?php + +use Kanboard\Core\Security\Role; +use Kanboard\Core\User\UserSession; +use Kanboard\Helper\ProjectRoleHelper; +use Kanboard\Model\ColumnMoveRestrictionModel; +use Kanboard\Model\ProjectModel; +use Kanboard\Model\ProjectRoleModel; +use Kanboard\Model\ProjectUserRoleModel; +use Kanboard\Model\TaskCreationModel; +use Kanboard\Model\TaskFinderModel; +use Kanboard\Model\TaskStatusModel; +use Kanboard\Model\UserModel; + +require_once __DIR__.'/../Base.php'; + +class ProjectRoleHelperTest extends Base +{ + public function testIsDraggableWithProjectMember() + { + $projectRoleHelper = new ProjectRoleHelper($this->container); + $projectModel = new ProjectModel($this->container); + $taskCreationModel = new TaskCreationModel($this->container); + $taskFinderModel = new TaskFinderModel($this->container); + $projectUserRole = new ProjectUserRoleModel($this->container); + $userModel = new UserModel($this->container); + + $this->container['sessionStorage']->user = array( + 'id' => 2, + 'role' => Role::APP_USER, + ); + + $this->assertEquals(2, $userModel->create(array('username' => 'user'))); + $this->assertEquals(1, $projectModel->create(array('name' => 'Test'))); + $this->assertTrue($projectUserRole->addUser(1, 2, Role::PROJECT_MEMBER)); + $this->assertEquals(1, $taskCreationModel->create(array('project_id' => 1, 'title' => 'test'))); + + $task = $taskFinderModel->getById(1); + $this->assertTrue($projectRoleHelper->isDraggable($task)); + } + + public function testIsDraggableWithClosedTask() + { + $projectRoleHelper = new ProjectRoleHelper($this->container); + $projectModel = new ProjectModel($this->container); + $taskCreationModel = new TaskCreationModel($this->container); + $taskFinderModel = new TaskFinderModel($this->container); + $taskStatusModel = new TaskStatusModel($this->container); + $projectUserRole = new ProjectUserRoleModel($this->container); + $userModel = new UserModel($this->container); + + $this->container['sessionStorage']->user = array( + 'id' => 2, + 'role' => Role::APP_USER, + ); + + $this->assertEquals(2, $userModel->create(array('username' => 'user'))); + $this->assertEquals(1, $projectModel->create(array('name' => 'Test'))); + $this->assertTrue($projectUserRole->addUser(1, 2, Role::PROJECT_MEMBER)); + $this->assertEquals(1, $taskCreationModel->create(array('project_id' => 1, 'title' => 'test'))); + $this->assertTrue($taskStatusModel->close(1)); + + $task = $taskFinderModel->getById(1); + $this->assertFalse($projectRoleHelper->isDraggable($task)); + } + + public function testIsDraggableWithColumnRestrictions() + { + $projectRoleHelper = new ProjectRoleHelper($this->container); + $projectModel = new ProjectModel($this->container); + $taskCreationModel = new TaskCreationModel($this->container); + $taskFinderModel = new TaskFinderModel($this->container); + $projectUserRole = new ProjectUserRoleModel($this->container); + $userModel = new UserModel($this->container); + $projectRoleModel = new ProjectRoleModel($this->container); + $columnMoveRestrictionModel = new ColumnMoveRestrictionModel($this->container); + + $this->container['sessionStorage']->user = array( + 'id' => 2, + 'role' => Role::APP_USER, + ); + + $this->assertEquals(2, $userModel->create(array('username' => 'user'))); + $this->assertEquals(1, $projectModel->create(array('name' => 'Test'))); + + $this->assertEquals(1, $projectRoleModel->create(1, 'Custom Role')); + $this->assertEquals(1, $columnMoveRestrictionModel->create(1, 1, 2, 3)); + + $this->assertTrue($projectUserRole->addUser(1, 2, 'Custom Role')); + $this->assertEquals(1, $taskCreationModel->create(array('project_id' => 1, 'title' => 'test', 'column_id' => 2))); + $this->assertEquals(2, $taskCreationModel->create(array('project_id' => 1, 'title' => 'test', 'column_id' => 3))); + + $task = $taskFinderModel->getById(1); + $this->assertTrue($projectRoleHelper->isDraggable($task)); + + $task = $taskFinderModel->getById(2); + $this->assertFalse($projectRoleHelper->isDraggable($task)); + } + + public function testCanRemoveTask() + { + $taskCreationModel = new TaskCreationModel($this->container); + $taskFinderModel = new TaskFinderModel($this->container); + $projectRoleHelper = new ProjectRoleHelper($this->container); + $projectModel = new ProjectModel($this->container); + $userModel = new UserModel($this->container); + $userSessionModel = new UserSession($this->container); + + $this->assertNotFalse($userModel->create(array('username' => 'toto', 'password' => '123456'))); + $this->assertNotFalse($userModel->create(array('username' => 'toto2', 'password' => '123456'))); + $this->assertEquals(1, $projectModel->create(array('name' => 'Project #1'))); + $this->assertEquals(1, $taskCreationModel->create(array('title' => 'TaskViewController #1', 'project_id' => 1, 'creator_id' => 1))); + $this->assertEquals(2, $taskCreationModel->create(array('title' => 'TaskViewController #2', 'project_id' => 1, 'creator_id' => 2))); + $this->assertEquals(3, $taskCreationModel->create(array('title' => 'TaskViewController #3', 'project_id' => 1, 'creator_id' => 3))); + $this->assertEquals(4, $taskCreationModel->create(array('title' => 'TaskViewController #4', 'project_id' => 1))); + + // User #1 can remove everything + $user = $userModel->getById(1); + $this->assertNotEmpty($user); + $userSessionModel->initialize($user); + + $task = $taskFinderModel->getById(1); + $this->assertNotEmpty($task); + $this->assertTrue($projectRoleHelper->canRemoveTask($task)); + + // User #2 can't remove the TaskViewController #1 + $user = $userModel->getById(2); + $this->assertNotEmpty($user); + $userSessionModel->initialize($user); + + $task = $taskFinderModel->getById(1); + $this->assertNotEmpty($task); + $this->assertFalse($projectRoleHelper->canRemoveTask($task)); + + // User #1 can remove everything + $user = $userModel->getById(1); + $this->assertNotEmpty($user); + $userSessionModel->initialize($user); + + $task = $taskFinderModel->getById(2); + $this->assertNotEmpty($task); + $this->assertTrue($projectRoleHelper->canRemoveTask($task)); + + // User #2 can remove his own TaskViewController + $user = $userModel->getById(2); + $this->assertNotEmpty($user); + $userSessionModel->initialize($user); + + $task = $taskFinderModel->getById(2); + $this->assertNotEmpty($task); + $this->assertTrue($projectRoleHelper->canRemoveTask($task)); + + // User #1 can remove everything + $user = $userModel->getById(1); + $this->assertNotEmpty($user); + $userSessionModel->initialize($user); + + $task = $taskFinderModel->getById(3); + $this->assertNotEmpty($task); + $this->assertTrue($projectRoleHelper->canRemoveTask($task)); + + // User #2 can't remove the TaskViewController #3 + $user = $userModel->getById(2); + $this->assertNotEmpty($user); + $userSessionModel->initialize($user); + + $task = $taskFinderModel->getById(3); + $this->assertNotEmpty($task); + $this->assertFalse($projectRoleHelper->canRemoveTask($task)); + + // User #1 can remove everything + $user = $userModel->getById(1); + $this->assertNotEmpty($user); + $userSessionModel->initialize($user); + + $task = $taskFinderModel->getById(4); + $this->assertNotEmpty($task); + $this->assertTrue($projectRoleHelper->canRemoveTask($task)); + + // User #2 can't remove the TaskViewController #4 + $user = $userModel->getById(2); + $this->assertNotEmpty($user); + $userSessionModel->initialize($user); + + $task = $taskFinderModel->getById(4); + $this->assertNotEmpty($task); + $this->assertFalse($projectRoleHelper->canRemoveTask($task)); + } +} diff --git a/tests/units/Helper/UserHelperTest.php b/tests/units/Helper/UserHelperTest.php index ed34c990..b66acdba 100644 --- a/tests/units/Helper/UserHelperTest.php +++ b/tests/units/Helper/UserHelperTest.php @@ -294,94 +294,4 @@ class UserHelperTest extends Base $this->assertFalse($helper->hasProjectAccess('TaskViewController', 'show', 2)); $this->assertFalse($helper->hasProjectAccess('TaskCreationController', 'save', 2)); } - - public function testCanRemoveTask() - { - $taskCreationModel = new TaskCreationModel($this->container); - $taskFinderModel = new TaskFinderModel($this->container); - $helper = new UserHelper($this->container); - $projectModel = new ProjectModel($this->container); - $userModel = new UserModel($this->container); - $userSessionModel = new UserSession($this->container); - - $this->assertNotFalse($userModel->create(array('username' => 'toto', 'password' => '123456'))); - $this->assertNotFalse($userModel->create(array('username' => 'toto2', 'password' => '123456'))); - $this->assertEquals(1, $projectModel->create(array('name' => 'Project #1'))); - $this->assertEquals(1, $taskCreationModel->create(array('title' => 'TaskViewController #1', 'project_id' => 1, 'creator_id' => 1))); - $this->assertEquals(2, $taskCreationModel->create(array('title' => 'TaskViewController #2', 'project_id' => 1, 'creator_id' => 2))); - $this->assertEquals(3, $taskCreationModel->create(array('title' => 'TaskViewController #3', 'project_id' => 1, 'creator_id' => 3))); - $this->assertEquals(4, $taskCreationModel->create(array('title' => 'TaskViewController #4', 'project_id' => 1))); - - // User #1 can remove everything - $user = $userModel->getById(1); - $this->assertNotEmpty($user); - $userSessionModel->initialize($user); - - $task = $taskFinderModel->getById(1); - $this->assertNotEmpty($task); - $this->assertTrue($helper->canRemoveTask($task)); - - // User #2 can't remove the TaskViewController #1 - $user = $userModel->getById(2); - $this->assertNotEmpty($user); - $userSessionModel->initialize($user); - - $task = $taskFinderModel->getById(1); - $this->assertNotEmpty($task); - $this->assertFalse($helper->canRemoveTask($task)); - - // User #1 can remove everything - $user = $userModel->getById(1); - $this->assertNotEmpty($user); - $userSessionModel->initialize($user); - - $task = $taskFinderModel->getById(2); - $this->assertNotEmpty($task); - $this->assertTrue($helper->canRemoveTask($task)); - - // User #2 can remove his own TaskViewController - $user = $userModel->getById(2); - $this->assertNotEmpty($user); - $userSessionModel->initialize($user); - - $task = $taskFinderModel->getById(2); - $this->assertNotEmpty($task); - $this->assertTrue($helper->canRemoveTask($task)); - - // User #1 can remove everything - $user = $userModel->getById(1); - $this->assertNotEmpty($user); - $userSessionModel->initialize($user); - - $task = $taskFinderModel->getById(3); - $this->assertNotEmpty($task); - $this->assertTrue($helper->canRemoveTask($task)); - - // User #2 can't remove the TaskViewController #3 - $user = $userModel->getById(2); - $this->assertNotEmpty($user); - $userSessionModel->initialize($user); - - $task = $taskFinderModel->getById(3); - $this->assertNotEmpty($task); - $this->assertFalse($helper->canRemoveTask($task)); - - // User #1 can remove everything - $user = $userModel->getById(1); - $this->assertNotEmpty($user); - $userSessionModel->initialize($user); - - $task = $taskFinderModel->getById(4); - $this->assertNotEmpty($task); - $this->assertTrue($helper->canRemoveTask($task)); - - // User #2 can't remove the TaskViewController #4 - $user = $userModel->getById(2); - $this->assertNotEmpty($user); - $userSessionModel->initialize($user); - - $task = $taskFinderModel->getById(4); - $this->assertNotEmpty($task); - $this->assertFalse($helper->canRemoveTask($task)); - } } |