From 4a230d331ec220fc32a48525afb308af0d9787fa Mon Sep 17 00:00:00 2001 From: Frederic Guillot Date: Sun, 26 Jun 2016 10:25:13 -0400 Subject: [PATCH] Added application and project roles validation for API procedure calls --- ChangeLog | 4 + app/Api/Authorization/ActionAuthorization.php | 19 ++ .../Authorization/CategoryAuthorization.php | 19 ++ app/Api/Authorization/ColumnAuthorization.php | 19 ++ .../Authorization/CommentAuthorization.php | 19 ++ .../Authorization/ProcedureAuthorization.php | 32 ++ .../Authorization/ProjectAuthorization.php | 35 ++ .../Authorization/SubtaskAuthorization.php | 19 ++ app/Api/Authorization/TaskAuthorization.php | 19 ++ .../Authorization/TaskFileAuthorization.php | 19 ++ .../Authorization/TaskLinkAuthorization.php | 19 ++ app/Api/Authorization/UserAuthorization.php | 22 ++ ...eware.php => AuthenticationMiddleware.php} | 59 +--- .../ActionProcedure.php} | 12 +- .../AppProcedure.php} | 8 +- .../BaseProcedure.php} | 29 +- .../BoardProcedure.php} | 11 +- .../CategoryProcedure.php} | 16 +- .../ColumnProcedure.php} | 15 +- .../CommentProcedure.php} | 16 +- .../GroupMemberProcedure.php} | 8 +- .../GroupProcedure.php} | 8 +- .../LinkProcedure.php} | 8 +- .../{MeApi.php => Procedure/MeProcedure.php} | 6 +- .../ProjectPermissionProcedure.php} | 22 +- app/Api/Procedure/ProjectProcedure.php | 106 ++++++ .../SubtaskProcedure.php} | 16 +- .../SubtaskTimeTrackingProcedure.php | 39 +++ .../SwimlaneProcedure.php} | 21 +- .../TaskFileProcedure.php} | 17 +- .../TaskLinkProcedure.php} | 14 +- .../TaskProcedure.php} | 34 +- .../UserProcedure.php} | 6 +- app/Api/ProjectApi.php | 87 ----- app/Api/SubtaskTimeTrackingApi.php | 34 -- app/Core/Base.php | 4 + app/Model/ActionModel.php | 12 + app/Model/CategoryModel.php | 12 + app/Model/ColumnModel.php | 12 + app/Model/CommentModel.php | 16 + app/Model/SubtaskModel.php | 16 + app/Model/TaskFileModel.php | 16 + app/Model/TaskLinkModel.php | 16 + app/ServiceProvider/ApiProvider.php | 83 ++--- .../AuthenticationProvider.php | 57 ++++ composer.json | 2 +- composer.lock | 261 +++++++++++---- doc/api-authentication.markdown | 53 ++- doc/api-json-rpc.markdown | 12 +- ...api-project-permission-procedures.markdown | 33 ++ doc/api-project-procedures.markdown | 4 + ...ActionTest.php => ActionProcedureTest.php} | 4 +- .../{AppTest.php => AppProcedureTest.php} | 4 +- ...egrationTest.php => BaseProcedureTest.php} | 2 +- .../{BoardTest.php => BoardProcedureTest.php} | 4 +- ...goryTest.php => CategoryProcedureTest.php} | 4 +- ...ColumnTest.php => ColumnProcedureTest.php} | 4 +- ...mmentTest.php => CommentProcedureTest.php} | 4 +- ...rTest.php => GroupMemberProcedureTest.php} | 4 +- .../{GroupTest.php => GroupProcedureTest.php} | 4 +- .../{LinkTest.php => LinkProcedureTest.php} | 4 +- .../{MeTest.php => MeProcedureTest.php} | 9 +- ...kTest.php => OverdueTaskProcedureTest.php} | 4 +- .../ProcedureAuthorizationTest.php | 306 ++++++++++++++++++ ...php => ProjectPermissionProcedureTest.php} | 4 +- ...ojectTest.php => ProjectProcedureTest.php} | 4 +- ...btaskTest.php => SubtaskProcedureTest.php} | 4 +- ...laneTest.php => SwimlaneProcedureTest.php} | 4 +- ...FileTest.php => TaskFileProcedureTest.php} | 4 +- ...LinkTest.php => TaskLinkProcedureTest.php} | 4 +- .../{TaskTest.php => TaskProcedureTest.php} | 4 +- .../{UserTest.php => UserProcedureTest.php} | 4 +- .../{ActionTest.php => ActionModelTest.php} | 20 +- ...CategoryTest.php => CategoryModelTest.php} | 14 +- tests/units/Model/CommentTest.php | 86 ++--- .../{SubtaskTest.php => SubtaskModelTest.php} | 156 ++++----- ...TaskFileTest.php => TaskFileModelTest.php} | 15 +- tests/units/Model/TaskLinkModelTest.php | 211 ++++++++++++ tests/units/Model/TaskLinkTest.php | 196 ----------- 79 files changed, 1772 insertions(+), 761 deletions(-) create mode 100644 app/Api/Authorization/ActionAuthorization.php create mode 100644 app/Api/Authorization/CategoryAuthorization.php create mode 100644 app/Api/Authorization/ColumnAuthorization.php create mode 100644 app/Api/Authorization/CommentAuthorization.php create mode 100644 app/Api/Authorization/ProcedureAuthorization.php create mode 100644 app/Api/Authorization/ProjectAuthorization.php create mode 100644 app/Api/Authorization/SubtaskAuthorization.php create mode 100644 app/Api/Authorization/TaskAuthorization.php create mode 100644 app/Api/Authorization/TaskFileAuthorization.php create mode 100644 app/Api/Authorization/TaskLinkAuthorization.php create mode 100644 app/Api/Authorization/UserAuthorization.php rename app/Api/Middleware/{AuthenticationApiMiddleware.php => AuthenticationMiddleware.php} (53%) rename app/Api/{ActionApi.php => Procedure/ActionProcedure.php} (77%) rename app/Api/{AppApi.php => Procedure/AppProcedure.php} (87%) rename app/Api/{BaseApi.php => Procedure/BaseProcedure.php} (74%) rename app/Api/{BoardApi.php => Procedure/BoardProcedure.php} (55%) rename app/Api/{CategoryApi.php => Procedure/CategoryProcedure.php} (56%) rename app/Api/{ColumnApi.php => Procedure/ColumnProcedure.php} (51%) rename app/Api/{CommentApi.php => Procedure/CommentProcedure.php} (58%) rename app/Api/{GroupMemberApi.php => Procedure/GroupMemberProcedure.php} (86%) rename app/Api/{GroupApi.php => Procedure/GroupProcedure.php} (89%) rename app/Api/{LinkApi.php => Procedure/LinkProcedure.php} (95%) rename app/Api/{MeApi.php => Procedure/MeProcedure.php} (95%) rename app/Api/{ProjectPermissionApi.php => Procedure/ProjectPermissionProcedure.php} (50%) create mode 100644 app/Api/Procedure/ProjectProcedure.php rename app/Api/{SubtaskApi.php => Procedure/SubtaskProcedure.php} (67%) create mode 100644 app/Api/Procedure/SubtaskTimeTrackingProcedure.php rename app/Api/{SwimlaneApi.php => Procedure/SwimlaneProcedure.php} (56%) rename app/Api/{TaskFileApi.php => Procedure/TaskFileProcedure.php} (56%) rename app/Api/{TaskLinkApi.php => Procedure/TaskLinkProcedure.php} (69%) rename app/Api/{TaskApi.php => Procedure/TaskProcedure.php} (75%) rename app/Api/{UserApi.php => Procedure/UserProcedure.php} (96%) delete mode 100644 app/Api/ProjectApi.php delete mode 100644 app/Api/SubtaskTimeTrackingApi.php rename tests/integration/{ActionTest.php => ActionProcedureTest.php} (95%) rename tests/integration/{AppTest.php => AppProcedureTest.php} (94%) rename tests/integration/{BaseIntegrationTest.php => BaseProcedureTest.php} (98%) rename tests/integration/{BoardTest.php => BoardProcedureTest.php} (85%) rename tests/integration/{CategoryTest.php => CategoryProcedureTest.php} (95%) rename tests/integration/{ColumnTest.php => ColumnProcedureTest.php} (96%) rename tests/integration/{CommentTest.php => CommentProcedureTest.php} (94%) rename tests/integration/{GroupMemberTest.php => GroupMemberProcedureTest.php} (93%) rename tests/integration/{GroupTest.php => GroupProcedureTest.php} (93%) rename tests/integration/{LinkTest.php => LinkProcedureTest.php} (95%) rename tests/integration/{MeTest.php => MeProcedureTest.php} (83%) rename tests/integration/{OverdueTaskTest.php => OverdueTaskProcedureTest.php} (92%) create mode 100644 tests/integration/ProcedureAuthorizationTest.php rename tests/integration/{ProjectPermissionTest.php => ProjectPermissionProcedureTest.php} (96%) rename tests/integration/{ProjectTest.php => ProjectProcedureTest.php} (96%) rename tests/integration/{SubtaskTest.php => SubtaskProcedureTest.php} (94%) rename tests/integration/{SwimlaneTest.php => SwimlaneProcedureTest.php} (96%) rename tests/integration/{TaskFileTest.php => TaskFileProcedureTest.php} (94%) rename tests/integration/{TaskLinkTest.php => TaskLinkProcedureTest.php} (95%) rename tests/integration/{TaskTest.php => TaskProcedureTest.php} (94%) rename tests/integration/{UserTest.php => UserProcedureTest.php} (94%) rename tests/units/Model/{ActionTest.php => ActionModelTest.php} (96%) rename tests/units/Model/{CategoryTest.php => CategoryModelTest.php} (94%) rename tests/units/Model/{SubtaskTest.php => SubtaskModelTest.php} (66%) rename tests/units/Model/{TaskFileTest.php => TaskFileModelTest.php} (96%) create mode 100644 tests/units/Model/TaskLinkModelTest.php delete mode 100644 tests/units/Model/TaskLinkTest.php diff --git a/ChangeLog b/ChangeLog index b86fea57d..42af8ee3a 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,6 +1,10 @@ Version 1.0.31 (unreleased) -------------- +New features: + +* Added application and project roles validation for API procedure calls + Improvements: * Rewrite integration tests to run with Docker containers diff --git a/app/Api/Authorization/ActionAuthorization.php b/app/Api/Authorization/ActionAuthorization.php new file mode 100644 index 000000000..4b41ad82b --- /dev/null +++ b/app/Api/Authorization/ActionAuthorization.php @@ -0,0 +1,19 @@ +userSession->isLogged()) { + $this->checkProjectPermission($class, $method, $this->actionModel->getProjectId($action_id)); + } + } +} diff --git a/app/Api/Authorization/CategoryAuthorization.php b/app/Api/Authorization/CategoryAuthorization.php new file mode 100644 index 000000000..f17265a22 --- /dev/null +++ b/app/Api/Authorization/CategoryAuthorization.php @@ -0,0 +1,19 @@ +userSession->isLogged()) { + $this->checkProjectPermission($class, $method, $this->categoryModel->getProjectId($category_id)); + } + } +} diff --git a/app/Api/Authorization/ColumnAuthorization.php b/app/Api/Authorization/ColumnAuthorization.php new file mode 100644 index 000000000..37aecda26 --- /dev/null +++ b/app/Api/Authorization/ColumnAuthorization.php @@ -0,0 +1,19 @@ +userSession->isLogged()) { + $this->checkProjectPermission($class, $method, $this->columnModel->getProjectId($column_id)); + } + } +} diff --git a/app/Api/Authorization/CommentAuthorization.php b/app/Api/Authorization/CommentAuthorization.php new file mode 100644 index 000000000..ed15512ea --- /dev/null +++ b/app/Api/Authorization/CommentAuthorization.php @@ -0,0 +1,19 @@ +userSession->isLogged()) { + $this->checkProjectPermission($class, $method, $this->commentModel->getProjectId($comment_id)); + } + } +} diff --git a/app/Api/Authorization/ProcedureAuthorization.php b/app/Api/Authorization/ProcedureAuthorization.php new file mode 100644 index 000000000..070a63710 --- /dev/null +++ b/app/Api/Authorization/ProcedureAuthorization.php @@ -0,0 +1,32 @@ +userSession->isLogged() && in_array($procedure, $this->userSpecificProcedures)) { + throw new AccessDeniedException('This procedure is not available with the API credentials'); + } + } +} diff --git a/app/Api/Authorization/ProjectAuthorization.php b/app/Api/Authorization/ProjectAuthorization.php new file mode 100644 index 000000000..21ecf3115 --- /dev/null +++ b/app/Api/Authorization/ProjectAuthorization.php @@ -0,0 +1,35 @@ +userSession->isLogged()) { + $this->checkProjectPermission($class, $method, $project_id); + } + } + + protected function checkProjectPermission($class, $method, $project_id) + { + if (empty($project_id)) { + throw new AccessDeniedException('Project not found'); + } + + $role = $this->projectUserRoleModel->getUserRole($project_id, $this->userSession->getId()); + + if (! $this->apiProjectAuthorization->isAllowed($class, $method, $role)) { + throw new AccessDeniedException('Project access denied'); + } + } +} diff --git a/app/Api/Authorization/SubtaskAuthorization.php b/app/Api/Authorization/SubtaskAuthorization.php new file mode 100644 index 000000000..fcb579292 --- /dev/null +++ b/app/Api/Authorization/SubtaskAuthorization.php @@ -0,0 +1,19 @@ +userSession->isLogged()) { + $this->checkProjectPermission($class, $method, $this->subtaskModel->getProjectId($subtask_id)); + } + } +} diff --git a/app/Api/Authorization/TaskAuthorization.php b/app/Api/Authorization/TaskAuthorization.php new file mode 100644 index 000000000..db93b76b8 --- /dev/null +++ b/app/Api/Authorization/TaskAuthorization.php @@ -0,0 +1,19 @@ +userSession->isLogged()) { + $this->checkProjectPermission($class, $method, $this->taskFinderModel->getProjectId($category_id)); + } + } +} diff --git a/app/Api/Authorization/TaskFileAuthorization.php b/app/Api/Authorization/TaskFileAuthorization.php new file mode 100644 index 000000000..e40783eb0 --- /dev/null +++ b/app/Api/Authorization/TaskFileAuthorization.php @@ -0,0 +1,19 @@ +userSession->isLogged()) { + $this->checkProjectPermission($class, $method, $this->taskFileModel->getProjectId($file_id)); + } + } +} diff --git a/app/Api/Authorization/TaskLinkAuthorization.php b/app/Api/Authorization/TaskLinkAuthorization.php new file mode 100644 index 000000000..2f5fc8d5f --- /dev/null +++ b/app/Api/Authorization/TaskLinkAuthorization.php @@ -0,0 +1,19 @@ +userSession->isLogged()) { + $this->checkProjectPermission($class, $method, $this->taskLinkModel->getProjectId($task_link_id)); + } + } +} diff --git a/app/Api/Authorization/UserAuthorization.php b/app/Api/Authorization/UserAuthorization.php new file mode 100644 index 000000000..3fd6865cc --- /dev/null +++ b/app/Api/Authorization/UserAuthorization.php @@ -0,0 +1,22 @@ +userSession->isLogged() && ! $this->apiAuthorization->isAllowed($class, $method, $this->userSession->getRole())) { + throw new AccessDeniedException('You are not allowed to access to this resource'); + } + } +} diff --git a/app/Api/Middleware/AuthenticationApiMiddleware.php b/app/Api/Middleware/AuthenticationMiddleware.php similarity index 53% rename from app/Api/Middleware/AuthenticationApiMiddleware.php rename to app/Api/Middleware/AuthenticationMiddleware.php index b16e10b81..8e3095931 100644 --- a/app/Api/Middleware/AuthenticationApiMiddleware.php +++ b/app/Api/Middleware/AuthenticationMiddleware.php @@ -13,46 +13,8 @@ use Kanboard\Core\Base; * @package Kanboard\Api\Middleware * @author Frederic Guillot */ -class AuthenticationApiMiddleware extends Base implements MiddlewareInterface +class AuthenticationMiddleware extends Base implements MiddlewareInterface { - private $user_allowed_procedures = array( - 'getMe', - 'getMyDashboard', - 'getMyActivityStream', - 'createMyPrivateProject', - 'getMyProjectsList', - 'getMyProjects', - 'getMyOverdueTasks', - ); - - private $both_allowed_procedures = array( - 'getTimezone', - 'getVersion', - 'getDefaultTaskColor', - 'getDefaultTaskColors', - 'getColorList', - 'getProjectById', - 'getSubTask', - 'getTask', - 'getTaskByReference', - 'getTimeSpent', - 'getAllTasks', - 'getAllSubTasks', - 'hasTimer', - 'logStartTime', - 'logEndTime', - 'openTask', - 'closeTask', - 'moveTaskPosition', - 'createTask', - 'createSubtask', - 'updateTask', - 'getBoard', - 'getProjectActivity', - 'getOverdueTasksByProject', - 'searchTasks', - ); - /** * Execute Middleware * @@ -68,11 +30,8 @@ class AuthenticationApiMiddleware extends Base implements MiddlewareInterface $this->dispatcher->dispatch('app.bootstrap'); if ($this->isUserAuthenticated($username, $password)) { - $this->checkProcedurePermission(true, $procedureName); $this->userSession->initialize($this->userModel->getByUsername($username)); - } elseif ($this->isAppAuthenticated($username, $password)) { - $this->checkProcedurePermission(false, $procedureName); - } else { + } elseif (! $this->isAppAuthenticated($username, $password)) { $this->logger->error('API authentication failure for '.$username); throw new AuthenticationFailureException('Wrong credentials'); } @@ -120,18 +79,4 @@ class AuthenticationApiMiddleware extends Base implements MiddlewareInterface return $this->configModel->get('api_token'); } - - public function checkProcedurePermission($is_user, $procedure) - { - $is_both_procedure = in_array($procedure, $this->both_allowed_procedures); - $is_user_procedure = in_array($procedure, $this->user_allowed_procedures); - - if ($is_user && ! $is_both_procedure && ! $is_user_procedure) { - throw new AccessDeniedException('Permission denied'); - } elseif (! $is_user && ! $is_both_procedure && $is_user_procedure) { - throw new AccessDeniedException('Permission denied'); - } - - $this->logger->debug('API call: '.$procedure); - } } diff --git a/app/Api/ActionApi.php b/app/Api/Procedure/ActionProcedure.php similarity index 77% rename from app/Api/ActionApi.php rename to app/Api/Procedure/ActionProcedure.php index 116742d8f..4043dbb93 100644 --- a/app/Api/ActionApi.php +++ b/app/Api/Procedure/ActionProcedure.php @@ -1,16 +1,17 @@ container)->check($this->getClassName(), 'removeAction', $action_id); return $this->actionModel->remove($action_id); } public function getActions($project_id) { + ProjectAuthorization::getInstance($this->container)->check($this->getClassName(), 'getActions', $project_id); return $this->actionModel->getAllByProject($project_id); } public function createAction($project_id, $event_name, $action_name, array $params) { + ProjectAuthorization::getInstance($this->container)->check($this->getClassName(), 'createAction', $project_id); $values = array( 'project_id' => $project_id, 'event_name' => $event_name, diff --git a/app/Api/AppApi.php b/app/Api/Procedure/AppProcedure.php similarity index 87% rename from app/Api/AppApi.php rename to app/Api/Procedure/AppProcedure.php index 637de5c57..60af4a607 100644 --- a/app/Api/AppApi.php +++ b/app/Api/Procedure/AppProcedure.php @@ -1,16 +1,14 @@ userSession->isLogged() && ! $this->projectPermissionModel->isUserAllowed($project_id, $this->userSession->getId())) { - throw new AccessDeniedException('Permission denied'); - } - } - - public function checkTaskPermission($task_id) - { - if ($this->userSession->isLogged()) { - $this->checkProjectPermission($this->taskFinderModel->getProjectId($task_id)); - } + ProcedureAuthorization::getInstance($this->container)->check($procedure); + UserAuthorization::getInstance($this->container)->check($this->getClassName(), $procedure); } protected function formatTask($task) @@ -82,4 +77,10 @@ abstract class BaseApi extends Base return $values; } + + protected function getClassName() + { + $reflection = new ReflectionClass(get_called_class()); + return $reflection->getShortName(); + } } diff --git a/app/Api/BoardApi.php b/app/Api/Procedure/BoardProcedure.php similarity index 55% rename from app/Api/BoardApi.php rename to app/Api/Procedure/BoardProcedure.php index 70f21c0e4..674b54668 100644 --- a/app/Api/BoardApi.php +++ b/app/Api/Procedure/BoardProcedure.php @@ -1,21 +1,22 @@ checkProjectPermission($project_id); - + ProjectAuthorization::getInstance($this->container)->check($this->getClassName(), 'getBoard', $project_id); + return BoardFormatter::getInstance($this->container) ->withProjectId($project_id) ->withQuery($this->taskFinderModel->getExtendedQuery()) diff --git a/app/Api/CategoryApi.php b/app/Api/Procedure/CategoryProcedure.php similarity index 56% rename from app/Api/CategoryApi.php rename to app/Api/Procedure/CategoryProcedure.php index c56cfb35b..3ebbd9088 100644 --- a/app/Api/CategoryApi.php +++ b/app/Api/Procedure/CategoryProcedure.php @@ -1,34 +1,40 @@ container)->check($this->getClassName(), 'getCategory', $category_id); return $this->categoryModel->getById($category_id); } public function getAllCategories($project_id) { + ProjectAuthorization::getInstance($this->container)->check($this->getClassName(), 'getAllCategories', $project_id); return $this->categoryModel->getAll($project_id); } public function removeCategory($category_id) { + CategoryAuthorization::getInstance($this->container)->check($this->getClassName(), 'removeCategory', $category_id); return $this->categoryModel->remove($category_id); } public function createCategory($project_id, $name) { + ProjectAuthorization::getInstance($this->container)->check($this->getClassName(), 'createCategory', $project_id); + $values = array( 'project_id' => $project_id, 'name' => $name, @@ -40,6 +46,8 @@ class CategoryApi extends Base public function updateCategory($id, $name) { + CategoryAuthorization::getInstance($this->container)->check($this->getClassName(), 'updateCategory', $id); + $values = array( 'id' => $id, 'name' => $name, diff --git a/app/Api/ColumnApi.php b/app/Api/Procedure/ColumnProcedure.php similarity index 51% rename from app/Api/ColumnApi.php rename to app/Api/Procedure/ColumnProcedure.php index aa4026f6c..ab9d173bc 100644 --- a/app/Api/ColumnApi.php +++ b/app/Api/Procedure/ColumnProcedure.php @@ -1,42 +1,51 @@ container)->check($this->getClassName(), 'getColumns', $project_id); return $this->columnModel->getAll($project_id); } public function getColumn($column_id) { + ColumnAuthorization::getInstance($this->container)->check($this->getClassName(), 'getColumn', $column_id); return $this->columnModel->getById($column_id); } public function updateColumn($column_id, $title, $task_limit = 0, $description = '') { + ColumnAuthorization::getInstance($this->container)->check($this->getClassName(), 'updateColumn', $column_id); return $this->columnModel->update($column_id, $title, $task_limit, $description); } public function addColumn($project_id, $title, $task_limit = 0, $description = '') { + ProjectAuthorization::getInstance($this->container)->check($this->getClassName(), 'addColumn', $project_id); return $this->columnModel->create($project_id, $title, $task_limit, $description); } public function removeColumn($column_id) { + ColumnAuthorization::getInstance($this->container)->check($this->getClassName(), 'removeColumn', $column_id); return $this->columnModel->remove($column_id); } public function changeColumnPosition($project_id, $column_id, $position) { + ProjectAuthorization::getInstance($this->container)->check($this->getClassName(), 'changeColumnPosition', $project_id); return $this->columnModel->changePosition($project_id, $column_id, $position); } } diff --git a/app/Api/CommentApi.php b/app/Api/Procedure/CommentProcedure.php similarity index 58% rename from app/Api/CommentApi.php rename to app/Api/Procedure/CommentProcedure.php index 8358efee4..019a49bb5 100644 --- a/app/Api/CommentApi.php +++ b/app/Api/Procedure/CommentProcedure.php @@ -1,34 +1,40 @@ container)->check($this->getClassName(), 'getComment', $comment_id); return $this->commentModel->getById($comment_id); } public function getAllComments($task_id) { + TaskAuthorization::getInstance($this->container)->check($this->getClassName(), 'getAllComments', $task_id); return $this->commentModel->getAll($task_id); } public function removeComment($comment_id) { + CommentAuthorization::getInstance($this->container)->check($this->getClassName(), 'removeComment', $comment_id); return $this->commentModel->remove($comment_id); } public function createComment($task_id, $user_id, $content, $reference = '') { + TaskAuthorization::getInstance($this->container)->check($this->getClassName(), 'createComment', $task_id); + $values = array( 'task_id' => $task_id, 'user_id' => $user_id, @@ -43,6 +49,8 @@ class CommentApi extends Base public function updateComment($id, $content) { + CommentAuthorization::getInstance($this->container)->check($this->getClassName(), 'updateComment', $id); + $values = array( 'id' => $id, 'comment' => $content, diff --git a/app/Api/GroupMemberApi.php b/app/Api/Procedure/GroupMemberProcedure.php similarity index 86% rename from app/Api/GroupMemberApi.php rename to app/Api/Procedure/GroupMemberProcedure.php index e09f6975a..081d6ac80 100644 --- a/app/Api/GroupMemberApi.php +++ b/app/Api/Procedure/GroupMemberProcedure.php @@ -1,16 +1,14 @@ container)->check($this->getClassName(), 'getProjectUsers', $project_id); return $this->projectUserRoleModel->getAllUsers($project_id); } public function getAssignableUsers($project_id, $prepend_unassigned = false) { + ProjectAuthorization::getInstance($this->container)->check($this->getClassName(), 'getAssignableUsers', $project_id); return $this->projectUserRoleModel->getAssignableUsersList($project_id, $prepend_unassigned); } public function addProjectUser($project_id, $user_id, $role = Role::PROJECT_MEMBER) { + ProjectAuthorization::getInstance($this->container)->check($this->getClassName(), 'addProjectUser', $project_id); return $this->projectUserRoleModel->addUser($project_id, $user_id, $role); } public function addProjectGroup($project_id, $group_id, $role = Role::PROJECT_MEMBER) { + ProjectAuthorization::getInstance($this->container)->check($this->getClassName(), 'addProjectGroup', $project_id); return $this->projectGroupRoleModel->addGroup($project_id, $group_id, $role); } public function removeProjectUser($project_id, $user_id) { + ProjectAuthorization::getInstance($this->container)->check($this->getClassName(), 'removeProjectUser', $project_id); return $this->projectUserRoleModel->removeUser($project_id, $user_id); } public function removeProjectGroup($project_id, $group_id) { + ProjectAuthorization::getInstance($this->container)->check($this->getClassName(), 'removeProjectGroup', $project_id); return $this->projectGroupRoleModel->removeGroup($project_id, $group_id); } public function changeProjectUserRole($project_id, $user_id, $role) { + ProjectAuthorization::getInstance($this->container)->check($this->getClassName(), 'changeProjectUserRole', $project_id); return $this->projectUserRoleModel->changeUserRole($project_id, $user_id, $role); } public function changeProjectGroupRole($project_id, $group_id, $role) { + ProjectAuthorization::getInstance($this->container)->check($this->getClassName(), 'changeProjectGroupRole', $project_id); return $this->projectGroupRoleModel->changeGroupRole($project_id, $group_id, $role); } + + public function getProjectUserRole($project_id, $user_id) + { + ProjectAuthorization::getInstance($this->container)->check($this->getClassName(), 'getProjectUserRole', $project_id); + return $this->projectUserRoleModel->getUserRole($project_id, $user_id); + } } diff --git a/app/Api/Procedure/ProjectProcedure.php b/app/Api/Procedure/ProjectProcedure.php new file mode 100644 index 000000000..9187f2219 --- /dev/null +++ b/app/Api/Procedure/ProjectProcedure.php @@ -0,0 +1,106 @@ +container)->check($this->getClassName(), 'getProjectById', $project_id); + return $this->formatProject($this->projectModel->getById($project_id)); + } + + public function getProjectByName($name) + { + $project = $this->projectModel->getByName($name); + ProjectAuthorization::getInstance($this->container)->check($this->getClassName(), 'getProjectByName', $project['id']); + return $this->formatProject($project); + } + + public function getAllProjects() + { + return $this->formatProjects($this->projectModel->getAll()); + } + + public function removeProject($project_id) + { + ProjectAuthorization::getInstance($this->container)->check($this->getClassName(), 'removeProject', $project_id); + return $this->projectModel->remove($project_id); + } + + public function enableProject($project_id) + { + ProjectAuthorization::getInstance($this->container)->check($this->getClassName(), 'enableProject', $project_id); + return $this->projectModel->enable($project_id); + } + + public function disableProject($project_id) + { + ProjectAuthorization::getInstance($this->container)->check($this->getClassName(), 'disableProject', $project_id); + return $this->projectModel->disable($project_id); + } + + public function enableProjectPublicAccess($project_id) + { + ProjectAuthorization::getInstance($this->container)->check($this->getClassName(), 'enableProjectPublicAccess', $project_id); + return $this->projectModel->enablePublicAccess($project_id); + } + + public function disableProjectPublicAccess($project_id) + { + ProjectAuthorization::getInstance($this->container)->check($this->getClassName(), 'disableProjectPublicAccess', $project_id); + return $this->projectModel->disablePublicAccess($project_id); + } + + public function getProjectActivities(array $project_ids) + { + foreach ($project_ids as $project_id) { + ProjectAuthorization::getInstance($this->container) + ->check($this->getClassName(), 'getProjectActivities', $project_id); + } + + return $this->helper->projectActivity->getProjectsEvents($project_ids); + } + + public function getProjectActivity($project_id) + { + ProjectAuthorization::getInstance($this->container)->check($this->getClassName(), 'getProjectActivity', $project_id); + return $this->helper->projectActivity->getProjectEvents($project_id); + } + + public function createProject($name, $description = null, $owner_id = 0, $identifier = null) + { + $values = array( + 'name' => $name, + 'description' => $description, + 'identifier' => $identifier, + ); + + list($valid, ) = $this->projectValidator->validateCreation($values); + return $valid ? $this->projectModel->create($values, $owner_id, $this->userSession->isLogged()) : false; + } + + public function updateProject($project_id, $name, $description = null, $owner_id = null, $identifier = null) + { + ProjectAuthorization::getInstance($this->container)->check($this->getClassName(), 'updateProject', $project_id); + + $values = $this->filterValues(array( + 'id' => $project_id, + 'name' => $name, + 'description' => $description, + 'owner_id' => $owner_id, + 'identifier' => $identifier, + )); + + list($valid, ) = $this->projectValidator->validateModification($values); + return $valid && $this->projectModel->update($values); + } +} diff --git a/app/Api/SubtaskApi.php b/app/Api/Procedure/SubtaskProcedure.php similarity index 67% rename from app/Api/SubtaskApi.php rename to app/Api/Procedure/SubtaskProcedure.php index 5764ff7d2..e24009121 100644 --- a/app/Api/SubtaskApi.php +++ b/app/Api/Procedure/SubtaskProcedure.php @@ -1,34 +1,40 @@ container)->check($this->getClassName(), 'getSubtask', $subtask_id); return $this->subtaskModel->getById($subtask_id); } public function getAllSubtasks($task_id) { + TaskAuthorization::getInstance($this->container)->check($this->getClassName(), 'getAllSubtasks', $task_id); return $this->subtaskModel->getAll($task_id); } public function removeSubtask($subtask_id) { + SubtaskAuthorization::getInstance($this->container)->check($this->getClassName(), 'removeSubtask', $subtask_id); return $this->subtaskModel->remove($subtask_id); } public function createSubtask($task_id, $title, $user_id = 0, $time_estimated = 0, $time_spent = 0, $status = 0) { + TaskAuthorization::getInstance($this->container)->check($this->getClassName(), 'createSubtask', $task_id); + $values = array( 'title' => $title, 'task_id' => $task_id, @@ -44,6 +50,8 @@ class SubtaskApi extends Base public function updateSubtask($id, $task_id, $title = null, $user_id = null, $time_estimated = null, $time_spent = null, $status = null) { + TaskAuthorization::getInstance($this->container)->check($this->getClassName(), 'updateSubtask', $task_id); + $values = array( 'id' => $id, 'task_id' => $task_id, diff --git a/app/Api/Procedure/SubtaskTimeTrackingProcedure.php b/app/Api/Procedure/SubtaskTimeTrackingProcedure.php new file mode 100644 index 000000000..5d1988d61 --- /dev/null +++ b/app/Api/Procedure/SubtaskTimeTrackingProcedure.php @@ -0,0 +1,39 @@ +container)->check($this->getClassName(), 'hasSubtaskTimer', $subtask_id); + return $this->subtaskTimeTrackingModel->hasTimer($subtask_id, $user_id); + } + + public function logSubtaskStartTime($subtask_id, $user_id) + { + SubtaskAuthorization::getInstance($this->container)->check($this->getClassName(), 'logSubtaskStartTime', $subtask_id); + return $this->subtaskTimeTrackingModel->logStartTime($subtask_id, $user_id); + } + + public function logSubtaskEndTime($subtask_id,$user_id) + { + SubtaskAuthorization::getInstance($this->container)->check($this->getClassName(), 'logSubtaskEndTime', $subtask_id); + return $this->subtaskTimeTrackingModel->logEndTime($subtask_id, $user_id); + } + + public function getSubtaskTimeSpent($subtask_id,$user_id) + { + SubtaskAuthorization::getInstance($this->container)->check($this->getClassName(), 'getSubtaskTimeSpent', $subtask_id); + return $this->subtaskTimeTrackingModel->getTimeSpent($subtask_id, $user_id); + } +} diff --git a/app/Api/SwimlaneApi.php b/app/Api/Procedure/SwimlaneProcedure.php similarity index 56% rename from app/Api/SwimlaneApi.php rename to app/Api/Procedure/SwimlaneProcedure.php index c3c56a716..9b7d181d8 100644 --- a/app/Api/SwimlaneApi.php +++ b/app/Api/Procedure/SwimlaneProcedure.php @@ -1,34 +1,39 @@ container)->check($this->getClassName(), 'getActiveSwimlanes', $project_id); return $this->swimlaneModel->getSwimlanes($project_id); } public function getAllSwimlanes($project_id) { + ProjectAuthorization::getInstance($this->container)->check($this->getClassName(), 'getAllSwimlanes', $project_id); return $this->swimlaneModel->getAll($project_id); } public function getSwimlaneById($swimlane_id) { - return $this->swimlaneModel->getById($swimlane_id); + $swimlane = $this->swimlaneModel->getById($swimlane_id); + ProjectAuthorization::getInstance($this->container)->check($this->getClassName(), 'getSwimlaneById', $swimlane['project_id']); + return $swimlane; } public function getSwimlaneByName($project_id, $name) { + ProjectAuthorization::getInstance($this->container)->check($this->getClassName(), 'getSwimlaneByName', $project_id); return $this->swimlaneModel->getByName($project_id, $name); } @@ -39,11 +44,13 @@ class SwimlaneApi extends Base public function getDefaultSwimlane($project_id) { + ProjectAuthorization::getInstance($this->container)->check($this->getClassName(), 'getDefaultSwimlane', $project_id); return $this->swimlaneModel->getDefault($project_id); } public function addSwimlane($project_id, $name, $description = '') { + ProjectAuthorization::getInstance($this->container)->check($this->getClassName(), 'addSwimlane', $project_id); return $this->swimlaneModel->create(array('project_id' => $project_id, 'name' => $name, 'description' => $description)); } @@ -60,21 +67,25 @@ class SwimlaneApi extends Base public function removeSwimlane($project_id, $swimlane_id) { + ProjectAuthorization::getInstance($this->container)->check($this->getClassName(), 'removeSwimlane', $project_id); return $this->swimlaneModel->remove($project_id, $swimlane_id); } public function disableSwimlane($project_id, $swimlane_id) { + ProjectAuthorization::getInstance($this->container)->check($this->getClassName(), 'disableSwimlane', $project_id); return $this->swimlaneModel->disable($project_id, $swimlane_id); } public function enableSwimlane($project_id, $swimlane_id) { + ProjectAuthorization::getInstance($this->container)->check($this->getClassName(), 'enableSwimlane', $project_id); return $this->swimlaneModel->enable($project_id, $swimlane_id); } public function changeSwimlanePosition($project_id, $swimlane_id, $position) { + ProjectAuthorization::getInstance($this->container)->check($this->getClassName(), 'changeSwimlanePosition', $project_id); return $this->swimlaneModel->changePosition($project_id, $swimlane_id, $position); } } diff --git a/app/Api/TaskFileApi.php b/app/Api/Procedure/TaskFileProcedure.php similarity index 56% rename from app/Api/TaskFileApi.php rename to app/Api/Procedure/TaskFileProcedure.php index 7b27477cb..5aa7ea0b3 100644 --- a/app/Api/TaskFileApi.php +++ b/app/Api/Procedure/TaskFileProcedure.php @@ -1,29 +1,36 @@ container)->check($this->getClassName(), 'getTaskFile', $file_id); return $this->taskFileModel->getById($file_id); } public function getAllTaskFiles($task_id) { + TaskAuthorization::getInstance($this->container)->check($this->getClassName(), 'getAllTaskFiles', $task_id); return $this->taskFileModel->getAll($task_id); } public function downloadTaskFile($file_id) { + TaskFileAuthorization::getInstance($this->container)->check($this->getClassName(), 'downloadTaskFile', $file_id); + try { $file = $this->taskFileModel->getById($file_id); @@ -39,6 +46,8 @@ class TaskFileApi extends BaseApi public function createTaskFile($project_id, $task_id, $filename, $blob) { + ProjectAuthorization::getInstance($this->container)->check($this->getClassName(), 'createTaskFile', $project_id); + try { return $this->taskFileModel->uploadContent($task_id, $filename, $blob); } catch (ObjectStorageException $e) { @@ -49,11 +58,13 @@ class TaskFileApi extends BaseApi public function removeTaskFile($file_id) { + TaskFileAuthorization::getInstance($this->container)->check($this->getClassName(), 'removeTaskFile', $file_id); return $this->taskFileModel->remove($file_id); } public function removeAllTaskFiles($task_id) { + TaskAuthorization::getInstance($this->container)->check($this->getClassName(), 'removeAllTaskFiles', $task_id); return $this->taskFileModel->removeAll($task_id); } } diff --git a/app/Api/TaskLinkApi.php b/app/Api/Procedure/TaskLinkProcedure.php similarity index 69% rename from app/Api/TaskLinkApi.php rename to app/Api/Procedure/TaskLinkProcedure.php index bb809133b..375266fb3 100644 --- a/app/Api/TaskLinkApi.php +++ b/app/Api/Procedure/TaskLinkProcedure.php @@ -1,16 +1,17 @@ container)->check($this->getClassName(), 'getTaskLinkById', $task_link_id); return $this->taskLinkModel->getById($task_link_id); } @@ -33,6 +35,7 @@ class TaskLinkApi extends Base */ public function getAllTaskLinks($task_id) { + TaskAuthorization::getInstance($this->container)->check($this->getClassName(), 'getAllTaskLinks', $task_id); return $this->taskLinkModel->getAll($task_id); } @@ -47,6 +50,7 @@ class TaskLinkApi extends Base */ public function createTaskLink($task_id, $opposite_task_id, $link_id) { + TaskAuthorization::getInstance($this->container)->check($this->getClassName(), 'createTaskLink', $task_id); return $this->taskLinkModel->create($task_id, $opposite_task_id, $link_id); } @@ -62,6 +66,7 @@ class TaskLinkApi extends Base */ public function updateTaskLink($task_link_id, $task_id, $opposite_task_id, $link_id) { + TaskAuthorization::getInstance($this->container)->check($this->getClassName(), 'updateTaskLink', $task_id); return $this->taskLinkModel->update($task_link_id, $task_id, $opposite_task_id, $link_id); } @@ -74,6 +79,7 @@ class TaskLinkApi extends Base */ public function removeTaskLink($task_link_id) { + TaskLinkAuthorization::getInstance($this->container)->check($this->getClassName(), 'removeTaskLink', $task_link_id); return $this->taskLinkModel->remove($task_link_id); } } diff --git a/app/Api/TaskApi.php b/app/Api/Procedure/TaskProcedure.php similarity index 75% rename from app/Api/TaskApi.php rename to app/Api/Procedure/TaskProcedure.php index 523bfaa0a..2d29a4ef3 100644 --- a/app/Api/TaskApi.php +++ b/app/Api/Procedure/TaskProcedure.php @@ -1,39 +1,41 @@ checkProjectPermission($project_id); + ProjectAuthorization::getInstance($this->container)->check($this->getClassName(), 'searchTasks', $project_id); return $this->taskLexer->build($query)->withFilter(new TaskProjectFilter($project_id))->toArray(); } public function getTask($task_id) { - $this->checkTaskPermission($task_id); + TaskAuthorization::getInstance($this->container)->check($this->getClassName(), 'getTask', $task_id); return $this->formatTask($this->taskFinderModel->getById($task_id)); } public function getTaskByReference($project_id, $reference) { - $this->checkProjectPermission($project_id); + ProjectAuthorization::getInstance($this->container)->check($this->getClassName(), 'getTaskByReference', $project_id); return $this->formatTask($this->taskFinderModel->getByReference($project_id, $reference)); } public function getAllTasks($project_id, $status_id = TaskModel::STATUS_OPEN) { - $this->checkProjectPermission($project_id); + ProjectAuthorization::getInstance($this->container)->check($this->getClassName(), 'getAllTasks', $project_id); return $this->formatTasks($this->taskFinderModel->getAll($project_id, $status_id)); } @@ -44,40 +46,43 @@ class TaskApi extends BaseApi public function getOverdueTasksByProject($project_id) { - $this->checkProjectPermission($project_id); + ProjectAuthorization::getInstance($this->container)->check($this->getClassName(), 'getOverdueTasksByProject', $project_id); return $this->taskFinderModel->getOverdueTasksByProject($project_id); } public function openTask($task_id) { - $this->checkTaskPermission($task_id); + TaskAuthorization::getInstance($this->container)->check($this->getClassName(), 'openTask', $task_id); return $this->taskStatusModel->open($task_id); } public function closeTask($task_id) { - $this->checkTaskPermission($task_id); + TaskAuthorization::getInstance($this->container)->check($this->getClassName(), 'closeTask', $task_id); return $this->taskStatusModel->close($task_id); } public function removeTask($task_id) { + TaskAuthorization::getInstance($this->container)->check($this->getClassName(), 'removeTask', $task_id); return $this->taskModel->remove($task_id); } public function moveTaskPosition($project_id, $task_id, $column_id, $position, $swimlane_id = 0) { - $this->checkProjectPermission($project_id); + ProjectAuthorization::getInstance($this->container)->check($this->getClassName(), 'moveTaskPosition', $project_id); return $this->taskPositionModel->movePosition($project_id, $task_id, $column_id, $position, $swimlane_id); } public function moveTaskToProject($task_id, $project_id, $swimlane_id = null, $column_id = null, $category_id = null, $owner_id = null) { + ProjectAuthorization::getInstance($this->container)->check($this->getClassName(), 'moveTaskToProject', $project_id); return $this->taskDuplicationModel->moveToProject($task_id, $project_id, $swimlane_id, $column_id, $category_id, $owner_id); } public function duplicateTaskToProject($task_id, $project_id, $swimlane_id = null, $column_id = null, $category_id = null, $owner_id = null) { + ProjectAuthorization::getInstance($this->container)->check($this->getClassName(), 'duplicateTaskToProject', $project_id); return $this->taskDuplicationModel->duplicateToProject($task_id, $project_id, $swimlane_id, $column_id, $category_id, $owner_id); } @@ -86,8 +91,8 @@ class TaskApi extends BaseApi $recurrence_status = 0, $recurrence_trigger = 0, $recurrence_factor = 0, $recurrence_timeframe = 0, $recurrence_basedate = 0, $reference = '') { - $this->checkProjectPermission($project_id); - + ProjectAuthorization::getInstance($this->container)->check($this->getClassName(), 'createTask', $project_id); + if ($owner_id !== 0 && ! $this->projectPermissionModel->isAssignable($project_id, $owner_id)) { return false; } @@ -127,8 +132,7 @@ class TaskApi extends BaseApi $recurrence_status = null, $recurrence_trigger = null, $recurrence_factor = null, $recurrence_timeframe = null, $recurrence_basedate = null, $reference = null) { - $this->checkTaskPermission($id); - + TaskAuthorization::getInstance($this->container)->check($this->getClassName(), 'updateTask', $id); $project_id = $this->taskFinderModel->getProjectId($id); if ($project_id === 0) { diff --git a/app/Api/UserApi.php b/app/Api/Procedure/UserProcedure.php similarity index 96% rename from app/Api/UserApi.php rename to app/Api/Procedure/UserProcedure.php index 6cb9df1c3..145f85bfa 100644 --- a/app/Api/UserApi.php +++ b/app/Api/Procedure/UserProcedure.php @@ -1,6 +1,6 @@ checkProjectPermission($project_id); - return $this->formatProject($this->projectModel->getById($project_id)); - } - - public function getProjectByName($name) - { - return $this->formatProject($this->projectModel->getByName($name)); - } - - public function getAllProjects() - { - return $this->formatProjects($this->projectModel->getAll()); - } - - public function removeProject($project_id) - { - return $this->projectModel->remove($project_id); - } - - public function enableProject($project_id) - { - return $this->projectModel->enable($project_id); - } - - public function disableProject($project_id) - { - return $this->projectModel->disable($project_id); - } - - public function enableProjectPublicAccess($project_id) - { - return $this->projectModel->enablePublicAccess($project_id); - } - - public function disableProjectPublicAccess($project_id) - { - return $this->projectModel->disablePublicAccess($project_id); - } - - public function getProjectActivities(array $project_ids) - { - return $this->helper->projectActivity->getProjectsEvents($project_ids); - } - - public function getProjectActivity($project_id) - { - $this->checkProjectPermission($project_id); - return $this->helper->projectActivity->getProjectEvents($project_id); - } - - public function createProject($name, $description = null) - { - $values = array( - 'name' => $name, - 'description' => $description - ); - - list($valid, ) = $this->projectValidator->validateCreation($values); - return $valid ? $this->projectModel->create($values) : false; - } - - public function updateProject($project_id, $name, $description = null) - { - $values = $this->filterValues(array( - 'id' => $project_id, - 'name' => $name, - 'description' => $description - )); - - list($valid, ) = $this->projectValidator->validateModification($values); - return $valid && $this->projectModel->update($values); - } -} diff --git a/app/Api/SubtaskTimeTrackingApi.php b/app/Api/SubtaskTimeTrackingApi.php deleted file mode 100644 index 0e700b31d..000000000 --- a/app/Api/SubtaskTimeTrackingApi.php +++ /dev/null @@ -1,34 +0,0 @@ -subtaskTimeTrackingModel->hasTimer($subtask_id,$user_id); - } - - public function logStartTime($subtask_id,$user_id) - { - return $this->subtaskTimeTrackingModel->logStartTime($subtask_id,$user_id); - } - - public function logEndTime($subtask_id,$user_id) - { - return $this->subtaskTimeTrackingModel->logEndTime($subtask_id,$user_id); - } - - public function getTimeSpent($subtask_id,$user_id) - { - return $this->subtaskTimeTrackingModel->getTimeSpent($subtask_id,$user_id); - } -} diff --git a/app/Core/Base.php b/app/Core/Base.php index e5dd6ad93..eacca65d8 100644 --- a/app/Core/Base.php +++ b/app/Core/Base.php @@ -35,8 +35,12 @@ use Pimple\Container; * @property \Kanboard\Core\Security\AuthenticationManager $authenticationManager * @property \Kanboard\Core\Security\AccessMap $applicationAccessMap * @property \Kanboard\Core\Security\AccessMap $projectAccessMap + * @property \Kanboard\Core\Security\AccessMap $apiAccessMap + * @property \Kanboard\Core\Security\AccessMap $apiProjectAccessMap * @property \Kanboard\Core\Security\Authorization $applicationAuthorization * @property \Kanboard\Core\Security\Authorization $projectAuthorization + * @property \Kanboard\Core\Security\Authorization $apiAuthorization + * @property \Kanboard\Core\Security\Authorization $apiProjectAuthorization * @property \Kanboard\Core\Security\Role $role * @property \Kanboard\Core\Security\Token $token * @property \Kanboard\Core\Session\FlashMessage $flash diff --git a/app/Model/ActionModel.php b/app/Model/ActionModel.php index 53393ed53..b5d2bd069 100644 --- a/app/Model/ActionModel.php +++ b/app/Model/ActionModel.php @@ -85,6 +85,18 @@ class ActionModel extends Base return $action; } + /** + * Get the projectId by the actionId + * + * @access public + * @param integer $action_id + * @return integer + */ + public function getProjectId($action_id) + { + return $this->db->table(self::TABLE)->eq('id', $action_id)->findOneColumn('project_id') ?: 0; + } + /** * Attach parameters to actions * diff --git a/app/Model/CategoryModel.php b/app/Model/CategoryModel.php index 62fb56112..024d00260 100644 --- a/app/Model/CategoryModel.php +++ b/app/Model/CategoryModel.php @@ -55,6 +55,18 @@ class CategoryModel extends Base return $this->db->table(self::TABLE)->eq('id', $category_id)->findOneColumn('name') ?: ''; } + /** + * Get the projectId by the category id + * + * @access public + * @param integer $category_id Category id + * @return integer + */ + public function getProjectId($category_id) + { + return $this->db->table(self::TABLE)->eq('id', $category_id)->findOneColumn('project_id') ?: 0; + } + /** * Get a category id by the category name and project id * diff --git a/app/Model/ColumnModel.php b/app/Model/ColumnModel.php index 1adac0f25..795fe692e 100644 --- a/app/Model/ColumnModel.php +++ b/app/Model/ColumnModel.php @@ -31,6 +31,18 @@ class ColumnModel extends Base return $this->db->table(self::TABLE)->eq('id', $column_id)->findOne(); } + /** + * Get projectId by the columnId + * + * @access public + * @param integer $column_id Column id + * @return integer + */ + public function getProjectId($column_id) + { + return $this->db->table(self::TABLE)->eq('id', $column_id)->findOneColumn('project_id'); + } + /** * Get the first column id for a given project * diff --git a/app/Model/CommentModel.php b/app/Model/CommentModel.php index 36e1fc480..4231f29d4 100644 --- a/app/Model/CommentModel.php +++ b/app/Model/CommentModel.php @@ -29,6 +29,22 @@ class CommentModel extends Base const EVENT_CREATE = 'comment.create'; const EVENT_USER_MENTION = 'comment.user.mention'; + /** + * Get projectId from commentId + * + * @access public + * @param integer $comment_id + * @return integer + */ + public function getProjectId($comment_id) + { + return $this->db + ->table(self::TABLE) + ->eq(self::TABLE.'.id', $comment_id) + ->join(TaskModel::TABLE, 'id', 'task_id') + ->findOneColumn(TaskModel::TABLE . '.project_id') ?: 0; + } + /** * Get all comments for a given task * diff --git a/app/Model/SubtaskModel.php b/app/Model/SubtaskModel.php index 019064ad7..a97bddbfa 100644 --- a/app/Model/SubtaskModel.php +++ b/app/Model/SubtaskModel.php @@ -51,6 +51,22 @@ class SubtaskModel extends Base const EVENT_CREATE = 'subtask.create'; const EVENT_DELETE = 'subtask.delete'; + /** + * Get projectId from subtaskId + * + * @access public + * @param integer $subtask_id + * @return integer + */ + public function getProjectId($subtask_id) + { + return $this->db + ->table(self::TABLE) + ->eq(self::TABLE.'.id', $subtask_id) + ->join(TaskModel::TABLE, 'id', 'task_id') + ->findOneColumn(TaskModel::TABLE . '.project_id') ?: 0; + } + /** * Get available status * diff --git a/app/Model/TaskFileModel.php b/app/Model/TaskFileModel.php index 24c1ad4b4..7603019a5 100644 --- a/app/Model/TaskFileModel.php +++ b/app/Model/TaskFileModel.php @@ -72,6 +72,22 @@ class TaskFileModel extends FileModel return self::EVENT_CREATE; } + /** + * Get projectId from fileId + * + * @access public + * @param integer $file_id + * @return integer + */ + public function getProjectId($file_id) + { + return $this->db + ->table(self::TABLE) + ->eq(self::TABLE.'.id', $file_id) + ->join(TaskModel::TABLE, 'id', 'task_id') + ->findOneColumn(TaskModel::TABLE . '.project_id') ?: 0; + } + /** * Handle screenshot upload * diff --git a/app/Model/TaskLinkModel.php b/app/Model/TaskLinkModel.php index 45225e350..09978eae8 100644 --- a/app/Model/TaskLinkModel.php +++ b/app/Model/TaskLinkModel.php @@ -28,6 +28,22 @@ class TaskLinkModel extends Base */ const EVENT_CREATE_UPDATE = 'tasklink.create_update'; + /** + * Get projectId from $task_link_id + * + * @access public + * @param integer $task_link_id + * @return integer + */ + public function getProjectId($task_link_id) + { + return $this->db + ->table(self::TABLE) + ->eq(self::TABLE.'.id', $task_link_id) + ->join(TaskModel::TABLE, 'id', 'task_id') + ->findOneColumn(TaskModel::TABLE . '.project_id') ?: 0; + } + /** * Get a task link * diff --git a/app/ServiceProvider/ApiProvider.php b/app/ServiceProvider/ApiProvider.php index e0312056f..f88d9b4f8 100644 --- a/app/ServiceProvider/ApiProvider.php +++ b/app/ServiceProvider/ApiProvider.php @@ -3,26 +3,26 @@ namespace Kanboard\ServiceProvider; use JsonRPC\Server; -use Kanboard\Api\ActionApi; -use Kanboard\Api\AppApi; -use Kanboard\Api\BoardApi; -use Kanboard\Api\CategoryApi; -use Kanboard\Api\ColumnApi; -use Kanboard\Api\CommentApi; -use Kanboard\Api\TaskFileApi; -use Kanboard\Api\GroupApi; -use Kanboard\Api\GroupMemberApi; -use Kanboard\Api\LinkApi; -use Kanboard\Api\MeApi; -use Kanboard\Api\Middleware\AuthenticationApiMiddleware; -use Kanboard\Api\ProjectApi; -use Kanboard\Api\ProjectPermissionApi; -use Kanboard\Api\SubtaskApi; -use Kanboard\Api\SubtaskTimeTrackingApi; -use Kanboard\Api\SwimlaneApi; -use Kanboard\Api\TaskApi; -use Kanboard\Api\TaskLinkApi; -use Kanboard\Api\UserApi; +use Kanboard\Api\Procedure\ActionProcedure; +use Kanboard\Api\Procedure\AppProcedure; +use Kanboard\Api\Procedure\BoardProcedure; +use Kanboard\Api\Procedure\CategoryProcedure; +use Kanboard\Api\Procedure\ColumnProcedure; +use Kanboard\Api\Procedure\CommentProcedure; +use Kanboard\Api\Procedure\TaskFileProcedure; +use Kanboard\Api\Procedure\GroupProcedure; +use Kanboard\Api\Procedure\GroupMemberProcedure; +use Kanboard\Api\Procedure\LinkProcedure; +use Kanboard\Api\Procedure\MeProcedure; +use Kanboard\Api\Middleware\AuthenticationMiddleware; +use Kanboard\Api\Procedure\ProjectProcedure; +use Kanboard\Api\Procedure\ProjectPermissionProcedure; +use Kanboard\Api\Procedure\SubtaskProcedure; +use Kanboard\Api\Procedure\SubtaskTimeTrackingProcedure; +use Kanboard\Api\Procedure\SwimlaneProcedure; +use Kanboard\Api\Procedure\TaskProcedure; +use Kanboard\Api\Procedure\TaskLinkProcedure; +use Kanboard\Api\Procedure\UserProcedure; use Pimple\Container; use Pimple\ServiceProviderInterface; @@ -45,31 +45,32 @@ class ApiProvider implements ServiceProviderInterface $server = new Server(); $server->setAuthenticationHeader(API_AUTHENTICATION_HEADER); $server->getMiddlewareHandler() - ->withMiddleware(new AuthenticationApiMiddleware($container)) + ->withMiddleware(new AuthenticationMiddleware($container)) ; $server->getProcedureHandler() - ->withObject(new MeApi($container)) - ->withObject(new ActionApi($container)) - ->withObject(new AppApi($container)) - ->withObject(new BoardApi($container)) - ->withObject(new ColumnApi($container)) - ->withObject(new CategoryApi($container)) - ->withObject(new CommentApi($container)) - ->withObject(new TaskFileApi($container)) - ->withObject(new LinkApi($container)) - ->withObject(new ProjectApi($container)) - ->withObject(new ProjectPermissionApi($container)) - ->withObject(new SubtaskApi($container)) - ->withObject(new SubtaskTimeTrackingApi($container)) - ->withObject(new SwimlaneApi($container)) - ->withObject(new TaskApi($container)) - ->withObject(new TaskLinkApi($container)) - ->withObject(new UserApi($container)) - ->withObject(new GroupApi($container)) - ->withObject(new GroupMemberApi($container)) + ->withObject(new MeProcedure($container)) + ->withObject(new ActionProcedure($container)) + ->withObject(new AppProcedure($container)) + ->withObject(new BoardProcedure($container)) + ->withObject(new ColumnProcedure($container)) + ->withObject(new CategoryProcedure($container)) + ->withObject(new CommentProcedure($container)) + ->withObject(new TaskFileProcedure($container)) + ->withObject(new LinkProcedure($container)) + ->withObject(new ProjectProcedure($container)) + ->withObject(new ProjectPermissionProcedure($container)) + ->withObject(new SubtaskProcedure($container)) + ->withObject(new SubtaskTimeTrackingProcedure($container)) + ->withObject(new SwimlaneProcedure($container)) + ->withObject(new TaskProcedure($container)) + ->withObject(new TaskLinkProcedure($container)) + ->withObject(new UserProcedure($container)) + ->withObject(new GroupProcedure($container)) + ->withObject(new GroupMemberProcedure($container)) + ->withBeforeMethod('beforeProcedure') ; - + $container['api'] = $server; return $container; } diff --git a/app/ServiceProvider/AuthenticationProvider.php b/app/ServiceProvider/AuthenticationProvider.php index 84e4354d9..751fe5144 100644 --- a/app/ServiceProvider/AuthenticationProvider.php +++ b/app/ServiceProvider/AuthenticationProvider.php @@ -46,9 +46,13 @@ class AuthenticationProvider implements ServiceProviderInterface $container['projectAccessMap'] = $this->getProjectAccessMap(); $container['applicationAccessMap'] = $this->getApplicationAccessMap(); + $container['apiAccessMap'] = $this->getApiAccessMap(); + $container['apiProjectAccessMap'] = $this->getApiProjectAccessMap(); $container['projectAuthorization'] = new Authorization($container['projectAccessMap']); $container['applicationAuthorization'] = new Authorization($container['applicationAccessMap']); + $container['apiAuthorization'] = new Authorization($container['apiAccessMap']); + $container['apiProjectAuthorization'] = new Authorization($container['apiProjectAccessMap']); return $container; } @@ -151,4 +155,57 @@ class AuthenticationProvider implements ServiceProviderInterface return $acl; } + + /** + * Get ACL for the API + * + * @access public + * @return AccessMap + */ + public function getApiAccessMap() + { + $acl = new AccessMap; + $acl->setDefaultRole(Role::APP_USER); + $acl->setRoleHierarchy(Role::APP_ADMIN, array(Role::APP_MANAGER, Role::APP_USER, Role::APP_PUBLIC)); + $acl->setRoleHierarchy(Role::APP_MANAGER, array(Role::APP_USER, Role::APP_PUBLIC)); + + $acl->add('UserProcedure', '*', Role::APP_ADMIN); + $acl->add('GroupMemberProcedure', '*', Role::APP_ADMIN); + $acl->add('GroupProcedure', '*', Role::APP_ADMIN); + $acl->add('LinkProcedure', '*', Role::APP_ADMIN); + $acl->add('TaskProcedure', array('getOverdueTasks'), Role::APP_ADMIN); + $acl->add('ProjectProcedure', array('getAllProjects'), Role::APP_ADMIN); + $acl->add('ProjectProcedure', array('createProject'), Role::APP_MANAGER); + + return $acl; + } + + /** + * Get ACL for the API + * + * @access public + * @return AccessMap + */ + public function getApiProjectAccessMap() + { + $acl = new AccessMap; + $acl->setDefaultRole(Role::PROJECT_VIEWER); + $acl->setRoleHierarchy(Role::PROJECT_MANAGER, array(Role::PROJECT_MEMBER, Role::PROJECT_VIEWER)); + $acl->setRoleHierarchy(Role::PROJECT_MEMBER, array(Role::PROJECT_VIEWER)); + + $acl->add('ActionProcedure', array('removeAction', 'getActions', 'createAction'), Role::PROJECT_MANAGER); + $acl->add('CategoryProcedure', '*', Role::PROJECT_MANAGER); + $acl->add('ColumnProcedure', '*', Role::PROJECT_MANAGER); + $acl->add('CommentProcedure', array('removeComment', 'createComment', 'updateComment'), Role::PROJECT_MEMBER); + $acl->add('ProjectPermissionProcedure', '*', Role::PROJECT_MANAGER); + $acl->add('ProjectProcedure', array('updateProject', 'removeProject', 'enableProject', 'disableProject', 'enableProjectPublicAccess', 'disableProjectPublicAccess'), Role::PROJECT_MANAGER); + $acl->add('SubtaskProcedure', '*', Role::PROJECT_MEMBER); + $acl->add('SubtaskTimeTrackingProcedure', '*', Role::PROJECT_MEMBER); + $acl->add('SwimlaneProcedure', '*', Role::PROJECT_MANAGER); + $acl->add('TaskFileProcedure', '*', Role::PROJECT_MEMBER); + $acl->add('TaskLinkProcedure', '*', Role::PROJECT_MEMBER); + $acl->add('TaskProcedure', '*', Role::PROJECT_MEMBER); + + return $acl; + } } diff --git a/composer.json b/composer.json index 85fdb5adf..bcac020eb 100644 --- a/composer.json +++ b/composer.json @@ -26,7 +26,7 @@ "christian-riesen/otp" : "1.4", "eluceo/ical": "0.8.0", "erusev/parsedown" : "1.6.0", - "fguillot/json-rpc" : "1.2.0", + "fguillot/json-rpc" : "1.2.1", "fguillot/picodb" : "1.0.12", "fguillot/simpleLogger" : "1.0.1", "fguillot/simple-validator" : "1.0.0", diff --git a/composer.lock b/composer.lock index e0177ed56..e6a72582b 100644 --- a/composer.lock +++ b/composer.lock @@ -4,8 +4,8 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file", "This file is @generated automatically" ], - "hash": "2de2026649db7bc41653bef80f974c6a", - "content-hash": "ea8ef74f1f1cf53b9f96b7609d756873", + "hash": "283af0b856598f5bc3d8ee0b226959e5", + "content-hash": "18c0bbff5406ceb8b567d9655de26746", "packages": [ { "name": "christian-riesen/base32", @@ -203,16 +203,16 @@ }, { "name": "fguillot/json-rpc", - "version": "v1.2.0", + "version": "v1.2.1", "source": { "type": "git", "url": "https://github.com/fguillot/JsonRPC.git", - "reference": "b002320b10aa1eeb7aee83f7b703cd6a6e99ff78" + "reference": "d491bb549bfa11aff4c37abcea2ffb28c9523f69" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/fguillot/JsonRPC/zipball/b002320b10aa1eeb7aee83f7b703cd6a6e99ff78", - "reference": "b002320b10aa1eeb7aee83f7b703cd6a6e99ff78", + "url": "https://api.github.com/repos/fguillot/JsonRPC/zipball/d491bb549bfa11aff4c37abcea2ffb28c9523f69", + "reference": "d491bb549bfa11aff4c37abcea2ffb28c9523f69", "shasum": "" }, "require": { @@ -238,7 +238,7 @@ ], "description": "Simple Json-RPC client/server library that just works", "homepage": "https://github.com/fguillot/JsonRPC", - "time": "2016-05-29 13:06:36" + "time": "2016-06-25 23:11:10" }, { "name": "fguillot/picodb", @@ -682,16 +682,16 @@ }, { "name": "symfony/console", - "version": "v2.8.6", + "version": "v2.8.7", "source": { "type": "git", "url": "https://github.com/symfony/console.git", - "reference": "48221d3de4dc22d2cd57c97e8b9361821da86609" + "reference": "5ac8bc9aa77bb2edf06af3a1bb6bc1020d23acd3" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/console/zipball/48221d3de4dc22d2cd57c97e8b9361821da86609", - "reference": "48221d3de4dc22d2cd57c97e8b9361821da86609", + "url": "https://api.github.com/repos/symfony/console/zipball/5ac8bc9aa77bb2edf06af3a1bb6bc1020d23acd3", + "reference": "5ac8bc9aa77bb2edf06af3a1bb6bc1020d23acd3", "shasum": "" }, "require": { @@ -738,20 +738,20 @@ ], "description": "Symfony Console Component", "homepage": "https://symfony.com", - "time": "2016-04-26 12:00:47" + "time": "2016-06-06 15:06:25" }, { "name": "symfony/event-dispatcher", - "version": "v2.8.6", + "version": "v2.8.7", "source": { "type": "git", "url": "https://github.com/symfony/event-dispatcher.git", - "reference": "a158f13992a3147d466af7a23b564ac719a4ddd8" + "reference": "2a6b8713f8bdb582058cfda463527f195b066110" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/a158f13992a3147d466af7a23b564ac719a4ddd8", - "reference": "a158f13992a3147d466af7a23b564ac719a4ddd8", + "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/2a6b8713f8bdb582058cfda463527f195b066110", + "reference": "2a6b8713f8bdb582058cfda463527f195b066110", "shasum": "" }, "require": { @@ -798,7 +798,7 @@ ], "description": "Symfony EventDispatcher Component", "homepage": "https://symfony.com", - "time": "2016-05-03 18:59:18" + "time": "2016-06-06 11:11:27" }, { "name": "symfony/polyfill-mbstring", @@ -916,38 +916,87 @@ "time": "2015-06-14 21:17:01" }, { - "name": "phpdocumentor/reflection-docblock", - "version": "2.0.4", + "name": "phpdocumentor/reflection-common", + "version": "1.0", "source": { "type": "git", - "url": "https://github.com/phpDocumentor/ReflectionDocBlock.git", - "reference": "d68dbdc53dc358a816f00b300704702b2eaff7b8" + "url": "https://github.com/phpDocumentor/ReflectionCommon.git", + "reference": "144c307535e82c8fdcaacbcfc1d6d8eeb896687c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/d68dbdc53dc358a816f00b300704702b2eaff7b8", - "reference": "d68dbdc53dc358a816f00b300704702b2eaff7b8", + "url": "https://api.github.com/repos/phpDocumentor/ReflectionCommon/zipball/144c307535e82c8fdcaacbcfc1d6d8eeb896687c", + "reference": "144c307535e82c8fdcaacbcfc1d6d8eeb896687c", "shasum": "" }, "require": { - "php": ">=5.3.3" + "php": ">=5.5" }, "require-dev": { - "phpunit/phpunit": "~4.0" - }, - "suggest": { - "dflydev/markdown": "~1.0", - "erusev/parsedown": "~1.0" + "phpunit/phpunit": "^4.6" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "2.0.x-dev" + "dev-master": "1.0.x-dev" } }, "autoload": { - "psr-0": { - "phpDocumentor": [ + "psr-4": { + "phpDocumentor\\Reflection\\": [ + "src" + ] + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jaap van Otterdijk", + "email": "opensource@ijaap.nl" + } + ], + "description": "Common reflection classes used by phpdocumentor to reflect the code structure", + "homepage": "http://www.phpdoc.org", + "keywords": [ + "FQSEN", + "phpDocumentor", + "phpdoc", + "reflection", + "static analysis" + ], + "time": "2015-12-27 11:43:31" + }, + { + "name": "phpdocumentor/reflection-docblock", + "version": "3.1.0", + "source": { + "type": "git", + "url": "https://github.com/phpDocumentor/ReflectionDocBlock.git", + "reference": "9270140b940ff02e58ec577c237274e92cd40cdd" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/9270140b940ff02e58ec577c237274e92cd40cdd", + "reference": "9270140b940ff02e58ec577c237274e92cd40cdd", + "shasum": "" + }, + "require": { + "php": ">=5.5", + "phpdocumentor/reflection-common": "^1.0@dev", + "phpdocumentor/type-resolver": "^0.2.0", + "webmozart/assert": "^1.0" + }, + "require-dev": { + "mockery/mockery": "^0.9.4", + "phpunit/phpunit": "^4.4" + }, + "type": "library", + "autoload": { + "psr-4": { + "phpDocumentor\\Reflection\\": [ "src/" ] } @@ -959,39 +1008,87 @@ "authors": [ { "name": "Mike van Riel", - "email": "mike.vanriel@naenius.com" + "email": "me@mikevanriel.com" } ], - "time": "2015-02-03 12:10:50" + "description": "With this component, a library can provide support for annotations via DocBlocks or otherwise retrieve information that is embedded in a DocBlock.", + "time": "2016-06-10 09:48:41" }, { - "name": "phpspec/prophecy", - "version": "v1.6.0", + "name": "phpdocumentor/type-resolver", + "version": "0.2", "source": { "type": "git", - "url": "https://github.com/phpspec/prophecy.git", - "reference": "3c91bdf81797d725b14cb62906f9a4ce44235972" + "url": "https://github.com/phpDocumentor/TypeResolver.git", + "reference": "b39c7a5b194f9ed7bd0dd345c751007a41862443" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpspec/prophecy/zipball/3c91bdf81797d725b14cb62906f9a4ce44235972", - "reference": "3c91bdf81797d725b14cb62906f9a4ce44235972", + "url": "https://api.github.com/repos/phpDocumentor/TypeResolver/zipball/b39c7a5b194f9ed7bd0dd345c751007a41862443", + "reference": "b39c7a5b194f9ed7bd0dd345c751007a41862443", + "shasum": "" + }, + "require": { + "php": ">=5.5", + "phpdocumentor/reflection-common": "^1.0" + }, + "require-dev": { + "mockery/mockery": "^0.9.4", + "phpunit/phpunit": "^5.2||^4.8.24" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "phpDocumentor\\Reflection\\": [ + "src/" + ] + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Mike van Riel", + "email": "me@mikevanriel.com" + } + ], + "time": "2016-06-10 07:14:17" + }, + { + "name": "phpspec/prophecy", + "version": "v1.6.1", + "source": { + "type": "git", + "url": "https://github.com/phpspec/prophecy.git", + "reference": "58a8137754bc24b25740d4281399a4a3596058e0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpspec/prophecy/zipball/58a8137754bc24b25740d4281399a4a3596058e0", + "reference": "58a8137754bc24b25740d4281399a4a3596058e0", "shasum": "" }, "require": { "doctrine/instantiator": "^1.0.2", "php": "^5.3|^7.0", - "phpdocumentor/reflection-docblock": "~2.0", - "sebastian/comparator": "~1.1", - "sebastian/recursion-context": "~1.0" + "phpdocumentor/reflection-docblock": "^2.0|^3.0.2", + "sebastian/comparator": "^1.1", + "sebastian/recursion-context": "^1.0" }, "require-dev": { - "phpspec/phpspec": "~2.0" + "phpspec/phpspec": "^2.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.5.x-dev" + "dev-master": "1.6.x-dev" } }, "autoload": { @@ -1024,7 +1121,7 @@ "spy", "stub" ], - "time": "2016-02-15 07:46:21" + "time": "2016-06-07 08:13:47" }, { "name": "phpunit/php-code-coverage", @@ -1629,16 +1726,16 @@ }, { "name": "sebastian/exporter", - "version": "1.2.1", + "version": "1.2.2", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/exporter.git", - "reference": "7ae5513327cb536431847bcc0c10edba2701064e" + "reference": "42c4c2eec485ee3e159ec9884f95b431287edde4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/7ae5513327cb536431847bcc0c10edba2701064e", - "reference": "7ae5513327cb536431847bcc0c10edba2701064e", + "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/42c4c2eec485ee3e159ec9884f95b431287edde4", + "reference": "42c4c2eec485ee3e159ec9884f95b431287edde4", "shasum": "" }, "require": { @@ -1646,12 +1743,13 @@ "sebastian/recursion-context": "~1.0" }, "require-dev": { + "ext-mbstring": "*", "phpunit/phpunit": "~4.4" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.2.x-dev" + "dev-master": "1.3.x-dev" } }, "autoload": { @@ -1691,7 +1789,7 @@ "export", "exporter" ], - "time": "2015-06-21 07:55:53" + "time": "2016-06-17 09:04:28" }, { "name": "sebastian/global-state", @@ -1834,16 +1932,16 @@ }, { "name": "symfony/stopwatch", - "version": "v2.8.6", + "version": "v2.8.7", "source": { "type": "git", "url": "https://github.com/symfony/stopwatch.git", - "reference": "9e24824b2a9a16e17ab997f61d70bc03948e434e" + "reference": "5e628055488bcc42dbace3af65be435d094e37e4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/stopwatch/zipball/9e24824b2a9a16e17ab997f61d70bc03948e434e", - "reference": "9e24824b2a9a16e17ab997f61d70bc03948e434e", + "url": "https://api.github.com/repos/symfony/stopwatch/zipball/5e628055488bcc42dbace3af65be435d094e37e4", + "reference": "5e628055488bcc42dbace3af65be435d094e37e4", "shasum": "" }, "require": { @@ -1879,7 +1977,7 @@ ], "description": "Symfony Stopwatch Component", "homepage": "https://symfony.com", - "time": "2016-03-04 07:54:35" + "time": "2016-06-06 11:11:27" }, { "name": "symfony/yaml", @@ -1927,6 +2025,55 @@ "description": "Symfony Yaml Component", "homepage": "http://symfony.com", "time": "2012-08-22 13:48:41" + }, + { + "name": "webmozart/assert", + "version": "1.0.2", + "source": { + "type": "git", + "url": "https://github.com/webmozart/assert.git", + "reference": "30eed06dd6bc88410a4ff7f77b6d22f3ce13dbde" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/webmozart/assert/zipball/30eed06dd6bc88410a4ff7f77b6d22f3ce13dbde", + "reference": "30eed06dd6bc88410a4ff7f77b6d22f3ce13dbde", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "require-dev": { + "phpunit/phpunit": "^4.6" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0-dev" + } + }, + "autoload": { + "psr-4": { + "Webmozart\\Assert\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Bernhard Schussek", + "email": "bschussek@gmail.com" + } + ], + "description": "Assertions to validate method input/output with nice error messages.", + "keywords": [ + "assert", + "check", + "validate" + ], + "time": "2015-08-24 13:29:44" } ], "aliases": [], diff --git a/doc/api-authentication.markdown b/doc/api-authentication.markdown index 962e5b1b0..3ba1e8f59 100644 --- a/doc/api-authentication.markdown +++ b/doc/api-authentication.markdown @@ -1,48 +1,26 @@ API Authentication ================== +API endpoint +------------ + +URL: `https://YOUR_SERVER/jsonrpc.php` + + Default method (HTTP Basic) --------------------------- -The API credentials are available on the settings page. - -- API end-point: `https://YOUR_SERVER/jsonrpc.php` - -If you want to use the "application api": +### Application credentials - Username: `jsonrpc` - Password: API token on the settings page -Otherwise for the "user api", just use the real username/passsword. +### User credentials + +- Use the real username and password The API use the [HTTP Basic Authentication Scheme described in the RFC2617](http://www.ietf.org/rfc/rfc2617.txt). -If there is an authentication error, you will receive the HTTP status code `401 Not Authorized`. -### Authorized User API procedures - -- getMe -- getMyDashboard -- getMyActivityStream -- createMyPrivateProject -- getMyProjectsList -- getMyProjects -- getTimezone -- getVersion -- getDefaultTaskColor -- getDefaultTaskColors -- getColorList -- getProjectById -- getTask -- getTaskByReference -- getAllTasks -- openTask -- closeTask -- moveTaskPosition -- createTask -- updateTask -- getBoard -- getProjectActivity -- getMyOverdueTasks Custom HTTP header ------------------ @@ -64,3 +42,14 @@ curl \ -d '{"jsonrpc": "2.0", "method": "getAllProjects", "id": 1}' \ http://localhost/kanboard/jsonrpc.php ``` + +Authentication error +-------------------- + +If the credentials are wrong, you will receive a `401 Not Authorized` and the corresponding JSON response. + + +Authorization error +------------------- + +If the connected user is not allowed to access to the resource, you will receive a `403 Forbidden`. diff --git a/doc/api-json-rpc.markdown b/doc/api-json-rpc.markdown index bb14b008a..0f922a7ca 100644 --- a/doc/api-json-rpc.markdown +++ b/doc/api-json-rpc.markdown @@ -8,25 +8,25 @@ There are two types of API access: ### Application API -- Access to the API with the user "jsonrpc" and the token available in settings +- Access to the API with the user "jsonrpc" and the token available on the settings page - Access to all procedures - No permission checked - There is no user session on the server +- No access to procedures that starts with "My..." (example: "getMe" or "getMyProjects") - Example of possible clients: tools to migrate/import data, create tasks from another system, etc... ### User API - Access to the API with the user credentials (username and password) -- Access to a restricted set of procedures -- The project permissions are checked +- Application role and project permissions are checked for each procedure - A user session is created on the server -- Example of possible clients: mobile/desktop application, command line utility, etc... +- Example of possible clients: native mobile/desktop application, command line utility, etc... Security -------- -- Always use HTTPS with a valid certificate -- If you make a mobile application, it's your job to store securely the user credentials on the device +- Always use HTTPS with a valid certificate (avoid clear text communication) +- If you make a mobile application, it's your responsability to store securely the user credentials on the device - After 3 authentication failure on the user api, the end-user have to unlock his account by using the login form - Two factor authentication is not yet available through the API diff --git a/doc/api-project-permission-procedures.markdown b/doc/api-project-permission-procedures.markdown index 2844ae3cb..d5e9b0664 100644 --- a/doc/api-project-permission-procedures.markdown +++ b/doc/api-project-permission-procedures.markdown @@ -272,3 +272,36 @@ Response example: "result": true } ``` + +## getProjectUserRole + +- Purpose: **Get the role of a user for a given project** +- Parameters: + - **project_id** (integer, required) + - **user_id** (integer, required) +- Result on success: **role name** +- Result on failure: **false** + +Request example: + +```json +{ + "jsonrpc": "2.0", + "method": "getProjectUserRole", + "id": 2114673298, + "params": [ + "2", + "3" + ] +} +``` + +Response example: + +```json +{ + "jsonrpc": "2.0", + "id": 2114673298, + "result": "project-viewer" +} +``` diff --git a/doc/api-project-procedures.markdown b/doc/api-project-procedures.markdown index f8e2cc5e2..3f8d33c27 100644 --- a/doc/api-project-procedures.markdown +++ b/doc/api-project-procedures.markdown @@ -7,6 +7,8 @@ API Project Procedures - Parameters: - **name** (string, required) - **description** (string, optional) + - **owner_id** (integer, optional) + - **identifier** (string, optional) - Result on success: **project_id** - Result on failure: **false** @@ -186,6 +188,8 @@ Response example: - **project_id** (integer, required) - **name** (string, required) - **description** (string, optional) + - **owner_id** (integer, optional) + - **identifier** (string, optional) - Result on success: **true** - Result on failure: **false** diff --git a/tests/integration/ActionTest.php b/tests/integration/ActionProcedureTest.php similarity index 95% rename from tests/integration/ActionTest.php rename to tests/integration/ActionProcedureTest.php index 7a5adc4af..432de3d32 100644 --- a/tests/integration/ActionTest.php +++ b/tests/integration/ActionProcedureTest.php @@ -1,8 +1,8 @@ user->getMyProjects(); $this->assertNotEmpty($projects); - $this->assertCount(1, $projects); - $this->assertEquals($this->projectName, $projects[0]['name']); - $this->assertNotEmpty($projects[0]['url']['calendar']); - $this->assertNotEmpty($projects[0]['url']['board']); - $this->assertNotEmpty($projects[0]['url']['list']); } public function assertCreateTask() diff --git a/tests/integration/OverdueTaskTest.php b/tests/integration/OverdueTaskProcedureTest.php similarity index 92% rename from tests/integration/OverdueTaskTest.php rename to tests/integration/OverdueTaskProcedureTest.php index 1dea50301..65f52301a 100644 --- a/tests/integration/OverdueTaskTest.php +++ b/tests/integration/OverdueTaskProcedureTest.php @@ -1,8 +1,8 @@ setExpectedException('JsonRPC\Exception\AccessDeniedException'); + $this->app->getMe(); + } + + public function testUserCredentialDoNotHaveAccessToAdminProcedures() + { + $this->setExpectedException('JsonRPC\Exception\AccessDeniedException'); + $this->user->getUser(1); + } + + public function testManagerCredentialDoNotHaveAccessToAdminProcedures() + { + $this->setExpectedException('JsonRPC\Exception\AccessDeniedException'); + $this->user->getAllProjects(); + } + + public function testUserCredentialDoNotHaveAccessToManagerProcedures() + { + $this->setExpectedException('JsonRPC\Exception\AccessDeniedException'); + $this->user->createProject('Team project creation are only for app managers'); + } + + public function testAppManagerCanCreateTeamProject() + { + $this->assertNotFalse($this->manager->createProject('Team project created by app manager')); + } + + public function testAdminManagerCanCreateTeamProject() + { + $projectId = $this->admin->createProject('Team project created by admin'); + $this->assertNotFalse($projectId); + + $this->setExpectedException('JsonRPC\Exception\AccessDeniedException'); + $this->assertNotNull($this->manager->getProjectById($projectId)); + } + + public function testProjectManagerCanUpdateHisProject() + { + $projectId = $this->manager->createProject(array( + 'name' => 'Team project can be updated', + 'owner_id' => $this->managerUserId, + )); + + $this->assertNotFalse($projectId); + $this->assertEquals('project-manager', $this->app->getProjectUserRole($projectId, $this->managerUserId)); + $this->assertNotNull($this->manager->getProjectById($projectId)); + + $this->assertTrue($this->manager->updateProject($projectId, 'My team project have been updated')); + } + + public function testProjectAuthorizationForbidden() + { + $projectId = $this->manager->createProject('A team project without members'); + $this->assertNotFalse($projectId); + + $this->setExpectedException('JsonRPC\Exception\AccessDeniedException'); + $this->user->getProjectById($projectId); + } + + public function testProjectAuthorizationGranted() + { + $projectId = $this->manager->createProject(array( + 'name' => 'A team project with members', + 'owner_id' => $this->managerUserId, + )); + + $this->assertNotFalse($projectId); + + $this->assertTrue($this->manager->addProjectUser($projectId, $this->userUserId)); + $this->assertNotNull($this->user->getProjectById($projectId)); + } + + public function testActionAuthorizationForbidden() + { + $projectId = $this->manager->createProject(array( + 'name' => 'Test Project', + 'owner_id' => $this->managerUserId, + )); + + $this->assertNotFalse($projectId); + + $actionId = $this->manager->createAction($projectId, 'task.move.column', '\Kanboard\Action\TaskCloseColumn', array('column_id' => 1)); + $this->assertNotFalse($actionId); + + $this->setExpectedException('JsonRPC\Exception\AccessDeniedException'); + $this->user->removeAction($projectId); + } + + public function testActionAuthorizationForbiddenBecauseNotProjectManager() + { + $projectId = $this->manager->createProject(array( + 'name' => 'Test Project', + 'owner_id' => $this->managerUserId, + )); + + $this->assertNotFalse($projectId); + + $actionId = $this->manager->createAction($projectId, 'task.move.column', '\Kanboard\Action\TaskCloseColumn', array('column_id' => 1)); + $this->assertNotFalse($actionId); + + $this->assertTrue($this->manager->addProjectUser($projectId, $this->userUserId, 'project-member')); + + $this->setExpectedException('JsonRPC\Exception\AccessDeniedException'); + $this->user->removeAction($actionId); + } + + public function testActionAuthorizationGranted() + { + $projectId = $this->manager->createProject(array( + 'name' => 'Test Project', + 'owner_id' => $this->managerUserId, + )); + + $this->assertNotFalse($projectId); + + $actionId = $this->manager->createAction($projectId, 'task.move.column', '\Kanboard\Action\TaskCloseColumn', array('column_id' => 1)); + $this->assertNotFalse($actionId); + + $this->assertTrue($this->manager->addProjectUser($projectId, $this->userUserId, 'project-manager')); + $this->assertTrue($this->user->removeAction($actionId)); + } + + public function testCategoryAuthorizationForbidden() + { + $projectId = $this->manager->createProject(array( + 'name' => 'Test Project', + 'owner_id' => $this->managerUserId, + )); + + $this->assertNotFalse($projectId); + + $categoryId = $this->manager->createCategory($projectId, 'Test'); + $this->assertNotFalse($categoryId); + + $this->setExpectedException('JsonRPC\Exception\AccessDeniedException'); + $this->user->removeCategory($categoryId); + } + + public function testCategoryAuthorizationForbiddenBecauseNotProjectManager() + { + $projectId = $this->manager->createProject(array( + 'name' => 'Test Project', + 'owner_id' => $this->managerUserId, + )); + + $this->assertNotFalse($projectId); + + $categoryId = $this->manager->createCategory($projectId, 'Test'); + $this->assertNotFalse($categoryId); + + $this->assertTrue($this->manager->addProjectUser($projectId, $this->userUserId, 'project-member')); + $this->setExpectedException('JsonRPC\Exception\AccessDeniedException'); + $this->user->removeCategory($categoryId); + } + + public function testCategoryAuthorizationGranted() + { + $projectId = $this->manager->createProject(array( + 'name' => 'Test Project', + 'owner_id' => $this->managerUserId, + )); + + $this->assertNotFalse($projectId); + + $categoryId = $this->manager->createCategory($projectId, 'Test'); + $this->assertNotFalse($categoryId); + + $this->assertTrue($this->manager->addProjectUser($projectId, $this->userUserId, 'project-manager')); + $this->assertTrue($this->user->removeCategory($categoryId)); + } + + public function testColumnAuthorizationForbidden() + { + $projectId = $this->manager->createProject(array( + 'name' => 'Test Project', + 'owner_id' => $this->managerUserId, + )); + + $this->assertNotFalse($projectId); + + $columnId = $this->manager->addColumn($projectId, 'Test'); + $this->assertNotFalse($columnId); + + $this->setExpectedException('JsonRPC\Exception\AccessDeniedException'); + $this->user->removeColumn($columnId); + } + + public function testColumnAuthorizationForbiddenBecauseNotProjectManager() + { + $projectId = $this->manager->createProject(array( + 'name' => 'Test Project', + 'owner_id' => $this->managerUserId, + )); + + $this->assertNotFalse($projectId); + + $columnId = $this->manager->addColumn($projectId, 'Test'); + $this->assertNotFalse($columnId); + + $this->assertTrue($this->manager->addProjectUser($projectId, $this->userUserId, 'project-member')); + $this->setExpectedException('JsonRPC\Exception\AccessDeniedException'); + $this->user->removeColumn($columnId); + } + + public function testColumnAuthorizationGranted() + { + $projectId = $this->manager->createProject(array( + 'name' => 'Test Project', + 'owner_id' => $this->managerUserId, + )); + + $this->assertNotFalse($projectId); + + $columnId = $this->manager->addColumn($projectId, 'Test'); + $this->assertNotFalse($columnId); + + $this->assertTrue($this->manager->addProjectUser($projectId, $this->userUserId, 'project-manager')); + $this->assertTrue($this->user->removeColumn($columnId)); + } + + public function testCommentAuthorizationForbidden() + { + $projectId = $this->manager->createProject(array( + 'name' => 'Test Project', + 'owner_id' => $this->managerUserId, + )); + + $this->assertNotFalse($projectId); + $this->assertTrue($this->manager->addProjectUser($projectId, $this->userUserId, 'project-viewer')); + + $taskId = $this->manager->createTask('My Task', $projectId); + $this->assertNotFalse($taskId); + + $commentId = $this->manager->createComment($taskId, $this->userUserId, 'My comment'); + $this->assertNotFalse($commentId); + + $this->setExpectedException('JsonRPC\Exception\AccessDeniedException'); + $this->user->updateComment($commentId, 'something else'); + } + + public function testCommentAuthorizationGranted() + { + $projectId = $this->manager->createProject(array( + 'name' => 'Test Project', + 'owner_id' => $this->managerUserId, + )); + + $this->assertNotFalse($projectId); + $this->assertTrue($this->manager->addProjectUser($projectId, $this->userUserId, 'project-member')); + + $taskId = $this->user->createTask('My Task', $projectId); + $this->assertNotFalse($taskId); + + $commentId = $this->user->createComment($taskId, $this->userUserId, 'My comment'); + $this->assertNotFalse($commentId); + + $this->assertTrue($this->user->updateComment($commentId, 'something else')); + } + + public function testSubtaskAuthorizationForbidden() + { + $projectId = $this->manager->createProject(array( + 'name' => 'Test Project', + 'owner_id' => $this->managerUserId, + )); + + $this->assertNotFalse($projectId); + $this->assertTrue($this->manager->addProjectUser($projectId, $this->userUserId, 'project-viewer')); + + $taskId = $this->manager->createTask('My Task', $projectId); + $this->assertNotFalse($taskId); + + $subtaskId = $this->manager->createSubtask($taskId, 'My subtask'); + $this->assertNotFalse($subtaskId); + + $this->setExpectedException('JsonRPC\Exception\AccessDeniedException'); + $this->user->removeSubtask($subtaskId); + } + + public function testSubtaskAuthorizationGranted() + { + $projectId = $this->manager->createProject(array( + 'name' => 'Test Project', + 'owner_id' => $this->managerUserId, + )); + + $this->assertNotFalse($projectId); + $this->assertTrue($this->manager->addProjectUser($projectId, $this->userUserId, 'project-member')); + + $taskId = $this->user->createTask('My Task', $projectId); + $this->assertNotFalse($taskId); + + $subtaskId = $this->manager->createSubtask($taskId, 'My subtask'); + $this->assertNotFalse($subtaskId); + + $this->assertTrue($this->user->removeSubtask($subtaskId)); + } +} diff --git a/tests/integration/ProjectPermissionTest.php b/tests/integration/ProjectPermissionProcedureTest.php similarity index 96% rename from tests/integration/ProjectPermissionTest.php rename to tests/integration/ProjectPermissionProcedureTest.php index 3ceda07df..74313dc4d 100644 --- a/tests/integration/ProjectPermissionTest.php +++ b/tests/integration/ProjectPermissionProcedureTest.php @@ -1,8 +1,8 @@ assertEquals(array('column_id' => 1, 'color_id' => 'red'), $action['params']); } + public function testGetProjectId() + { + $projectModel = new ProjectModel($this->container); + $actionModel = new ActionModel($this->container); + + $this->assertEquals(1, $projectModel->create(array('name' => 'test'))); + + $this->assertEquals(1, $actionModel->create(array( + 'project_id' => 1, + 'event_name' => TaskModel::EVENT_CREATE, + 'action_name' => '\Kanboard\Action\TaskAssignColorColumn', + 'params' => array('column_id' => 1, 'color_id' => 'red'), + ))); + + $this->assertEquals(1, $actionModel->getProjectId(1)); + $this->assertSame(0, $actionModel->getProjectId(42)); + } + public function testGetAll() { $projectModel = new ProjectModel($this->container); diff --git a/tests/units/Model/CategoryTest.php b/tests/units/Model/CategoryModelTest.php similarity index 94% rename from tests/units/Model/CategoryTest.php rename to tests/units/Model/CategoryModelTest.php index 1fdc51f6d..80a20af6c 100644 --- a/tests/units/Model/CategoryTest.php +++ b/tests/units/Model/CategoryModelTest.php @@ -8,7 +8,7 @@ use Kanboard\Model\TaskFinderModel; use Kanboard\Model\ProjectModel; use Kanboard\Model\CategoryModel; -class CategoryTest extends Base +class CategoryModelTest extends Base { public function testCreation() { @@ -81,6 +81,18 @@ class CategoryTest extends Base $this->assertSame(0, $categoryModel->getIdByName(1, 'Category #2')); } + public function testGetProjectId() + { + $projectModel = new ProjectModel($this->container); + $categoryModel = new CategoryModel($this->container); + + $this->assertEquals(1, $projectModel->create(array('name' => 'Project #1'))); + $this->assertEquals(1, $categoryModel->create(array('name' => 'Category #1', 'project_id' => 1, 'description' => 'test'))); + + $this->assertEquals(1, $categoryModel->getProjectId(1)); + $this->assertSame(0, $categoryModel->getProjectId(2)); + } + public function testGetList() { $projectModel = new ProjectModel($this->container); diff --git a/tests/units/Model/CommentTest.php b/tests/units/Model/CommentTest.php index 7250ae0b9..574b5a878 100644 --- a/tests/units/Model/CommentTest.php +++ b/tests/units/Model/CommentTest.php @@ -10,16 +10,16 @@ class CommentTest extends Base { public function testCreate() { - $c = new CommentModel($this->container); - $tc = new TaskCreationModel($this->container); - $p = new ProjectModel($this->container); + $commentModel = new CommentModel($this->container); + $taskCreationModel = new TaskCreationModel($this->container); + $projectModel = new ProjectModel($this->container); - $this->assertEquals(1, $p->create(array('name' => 'test1'))); - $this->assertEquals(1, $tc->create(array('title' => 'test', 'project_id' => 1, 'column_id' => 3, 'owner_id' => 1))); - $this->assertEquals(1, $c->create(array('task_id' => 1, 'comment' => 'bla bla', 'user_id' => 1))); - $this->assertEquals(2, $c->create(array('task_id' => 1, 'comment' => 'bla bla'))); + $this->assertEquals(1, $projectModel->create(array('name' => 'test1'))); + $this->assertEquals(1, $taskCreationModel->create(array('title' => 'test', 'project_id' => 1, 'column_id' => 3, 'owner_id' => 1))); + $this->assertEquals(1, $commentModel->create(array('task_id' => 1, 'comment' => 'bla bla', 'user_id' => 1))); + $this->assertEquals(2, $commentModel->create(array('task_id' => 1, 'comment' => 'bla bla'))); - $comment = $c->getById(1); + $comment = $commentModel->getById(1); $this->assertNotEmpty($comment); $this->assertEquals('bla bla', $comment['comment']); $this->assertEquals(1, $comment['task_id']); @@ -27,7 +27,7 @@ class CommentTest extends Base $this->assertEquals('admin', $comment['username']); $this->assertEquals(time(), $comment['date_creation'], '', 3); - $comment = $c->getById(2); + $comment = $commentModel->getById(2); $this->assertNotEmpty($comment); $this->assertEquals('bla bla', $comment['comment']); $this->assertEquals(1, $comment['task_id']); @@ -38,17 +38,17 @@ class CommentTest extends Base public function testGetAll() { - $c = new CommentModel($this->container); - $tc = new TaskCreationModel($this->container); - $p = new ProjectModel($this->container); + $commentModel = new CommentModel($this->container); + $taskCreationModel = new TaskCreationModel($this->container); + $projectModel = new ProjectModel($this->container); - $this->assertEquals(1, $p->create(array('name' => 'test1'))); - $this->assertEquals(1, $tc->create(array('title' => 'test', 'project_id' => 1, 'column_id' => 3, 'owner_id' => 1))); - $this->assertNotFalse($c->create(array('task_id' => 1, 'comment' => 'c1', 'user_id' => 1))); - $this->assertNotFalse($c->create(array('task_id' => 1, 'comment' => 'c2', 'user_id' => 1))); - $this->assertNotFalse($c->create(array('task_id' => 1, 'comment' => 'c3', 'user_id' => 1))); + $this->assertEquals(1, $projectModel->create(array('name' => 'test1'))); + $this->assertEquals(1, $taskCreationModel->create(array('title' => 'test', 'project_id' => 1, 'column_id' => 3, 'owner_id' => 1))); + $this->assertEquals(1, $commentModel->create(array('task_id' => 1, 'comment' => 'c1', 'user_id' => 1))); + $this->assertEquals(2, $commentModel->create(array('task_id' => 1, 'comment' => 'c2', 'user_id' => 1))); + $this->assertEquals(3, $commentModel->create(array('task_id' => 1, 'comment' => 'c3', 'user_id' => 1))); - $comments = $c->getAll(1); + $comments = $commentModel->getAll(1); $this->assertNotEmpty($comments); $this->assertEquals(3, count($comments)); @@ -56,37 +56,51 @@ class CommentTest extends Base $this->assertEquals(2, $comments[1]['id']); $this->assertEquals(3, $comments[2]['id']); - $this->assertEquals(3, $c->count(1)); + $this->assertEquals(3, $commentModel->count(1)); } public function testUpdate() { - $c = new CommentModel($this->container); - $tc = new TaskCreationModel($this->container); - $p = new ProjectModel($this->container); + $commentModel = new CommentModel($this->container); + $taskCreationModel = new TaskCreationModel($this->container); + $projectModel = new ProjectModel($this->container); - $this->assertEquals(1, $p->create(array('name' => 'test1'))); - $this->assertEquals(1, $tc->create(array('title' => 'test', 'project_id' => 1, 'column_id' => 3, 'owner_id' => 1))); - $this->assertNotFalse($c->create(array('task_id' => 1, 'comment' => 'c1', 'user_id' => 1))); - $this->assertTrue($c->update(array('id' => 1, 'comment' => 'bla'))); + $this->assertEquals(1, $projectModel->create(array('name' => 'test1'))); + $this->assertEquals(1, $taskCreationModel->create(array('title' => 'test', 'project_id' => 1, 'column_id' => 3, 'owner_id' => 1))); + $this->assertEquals(1, $commentModel->create(array('task_id' => 1, 'comment' => 'c1', 'user_id' => 1))); + $this->assertTrue($commentModel->update(array('id' => 1, 'comment' => 'bla'))); - $comment = $c->getById(1); + $comment = $commentModel->getById(1); $this->assertNotEmpty($comment); $this->assertEquals('bla', $comment['comment']); } public function validateRemove() { - $c = new CommentModel($this->container); - $tc = new TaskCreationModel($this->container); - $p = new ProjectModel($this->container); + $commentModel = new CommentModel($this->container); + $taskCreationModel = new TaskCreationModel($this->container); + $projectModel = new ProjectModel($this->container); - $this->assertEquals(1, $p->create(array('name' => 'test1'))); - $this->assertEquals(1, $tc->create(array('title' => 'test', 'project_id' => 1, 'column_id' => 3, 'owner_id' => 1))); - $this->assertTrue($c->create(array('task_id' => 1, 'comment' => 'c1', 'user_id' => 1))); + $this->assertEquals(1, $projectModel->create(array('name' => 'test1'))); + $this->assertEquals(1, $taskCreationModel->create(array('title' => 'test', 'project_id' => 1, 'column_id' => 3, 'owner_id' => 1))); + $this->assertEquals(1, $commentModel->create(array('task_id' => 1, 'comment' => 'c1', 'user_id' => 1))); - $this->assertTrue($c->remove(1)); - $this->assertFalse($c->remove(1)); - $this->assertFalse($c->remove(1111)); + $this->assertTrue($commentModel->remove(1)); + $this->assertFalse($commentModel->remove(1)); + $this->assertFalse($commentModel->remove(1111)); + } + + public function testGetProjectId() + { + $commentModel = new CommentModel($this->container); + $taskCreationModel = new TaskCreationModel($this->container); + $projectModel = new ProjectModel($this->container); + + $this->assertEquals(1, $projectModel->create(array('name' => 'test1'))); + $this->assertEquals(1, $taskCreationModel->create(array('title' => 'test', 'project_id' => 1, 'column_id' => 3, 'owner_id' => 1))); + $this->assertEquals(1, $commentModel->create(array('task_id' => 1, 'comment' => 'c1', 'user_id' => 1))); + + $this->assertEquals(1, $commentModel->getProjectId(1)); + $this->assertSame(0, $commentModel->getProjectId(2)); } } diff --git a/tests/units/Model/SubtaskTest.php b/tests/units/Model/SubtaskModelTest.php similarity index 66% rename from tests/units/Model/SubtaskTest.php rename to tests/units/Model/SubtaskModelTest.php index b65ee6092..6451189d7 100644 --- a/tests/units/Model/SubtaskTest.php +++ b/tests/units/Model/SubtaskModelTest.php @@ -5,10 +5,9 @@ require_once __DIR__.'/../Base.php'; use Kanboard\Model\TaskCreationModel; use Kanboard\Model\SubtaskModel; use Kanboard\Model\ProjectModel; -use Kanboard\Core\User\UserSession; use Kanboard\Model\TaskFinderModel; -class SubtaskTest extends Base +class SubtaskModelTest extends Base { public function onSubtaskCreated($event) { @@ -70,18 +69,18 @@ class SubtaskTest extends Base public function testCreation() { - $tc = new TaskCreationModel($this->container); - $s = new SubtaskModel($this->container); - $p = new ProjectModel($this->container); + $taskCreationModel = new TaskCreationModel($this->container); + $subtaskModel = new SubtaskModel($this->container); + $projectModel = new ProjectModel($this->container); - $this->assertEquals(1, $p->create(array('name' => 'test'))); - $this->assertEquals(1, $tc->create(array('title' => 'test 1', 'project_id' => 1))); + $this->assertEquals(1, $projectModel->create(array('name' => 'test'))); + $this->assertEquals(1, $taskCreationModel->create(array('title' => 'test 1', 'project_id' => 1))); $this->container['dispatcher']->addListener(SubtaskModel::EVENT_CREATE, array($this, 'onSubtaskCreated')); - $this->assertEquals(1, $s->create(array('title' => 'subtask #1', 'task_id' => 1))); + $this->assertEquals(1, $subtaskModel->create(array('title' => 'subtask #1', 'task_id' => 1))); - $subtask = $s->getById(1); + $subtask = $subtaskModel->getById(1); $this->assertNotEmpty($subtask); $this->assertEquals(1, $subtask['id']); $this->assertEquals(1, $subtask['task_id']); @@ -95,19 +94,19 @@ class SubtaskTest extends Base public function testModification() { - $tc = new TaskCreationModel($this->container); - $s = new SubtaskModel($this->container); - $p = new ProjectModel($this->container); + $taskCreationModel = new TaskCreationModel($this->container); + $subtaskModel = new SubtaskModel($this->container); + $projectModel = new ProjectModel($this->container); - $this->assertEquals(1, $p->create(array('name' => 'test'))); - $this->assertEquals(1, $tc->create(array('title' => 'test 1', 'project_id' => 1))); + $this->assertEquals(1, $projectModel->create(array('name' => 'test'))); + $this->assertEquals(1, $taskCreationModel->create(array('title' => 'test 1', 'project_id' => 1))); $this->container['dispatcher']->addListener(SubtaskModel::EVENT_UPDATE, array($this, 'onSubtaskUpdated')); - $this->assertEquals(1, $s->create(array('title' => 'subtask #1', 'task_id' => 1))); - $this->assertTrue($s->update(array('id' => 1, 'user_id' => 1, 'status' => SubtaskModel::STATUS_INPROGRESS))); + $this->assertEquals(1, $subtaskModel->create(array('title' => 'subtask #1', 'task_id' => 1))); + $this->assertTrue($subtaskModel->update(array('id' => 1, 'user_id' => 1, 'status' => SubtaskModel::STATUS_INPROGRESS))); - $subtask = $s->getById(1); + $subtask = $subtaskModel->getById(1); $this->assertNotEmpty($subtask); $this->assertEquals(1, $subtask['id']); $this->assertEquals(1, $subtask['task_id']); @@ -121,61 +120,61 @@ class SubtaskTest extends Base public function testRemove() { - $tc = new TaskCreationModel($this->container); - $s = new SubtaskModel($this->container); - $p = new ProjectModel($this->container); + $taskCreationModel = new TaskCreationModel($this->container); + $subtaskModel = new SubtaskModel($this->container); + $projectModel = new ProjectModel($this->container); - $this->assertEquals(1, $p->create(array('name' => 'test'))); - $this->assertEquals(1, $tc->create(array('title' => 'test 1', 'project_id' => 1))); - $this->assertEquals(1, $s->create(array('title' => 'subtask #1', 'task_id' => 1))); + $this->assertEquals(1, $projectModel->create(array('name' => 'test'))); + $this->assertEquals(1, $taskCreationModel->create(array('title' => 'test 1', 'project_id' => 1))); + $this->assertEquals(1, $subtaskModel->create(array('title' => 'subtask #1', 'task_id' => 1))); $this->container['dispatcher']->addListener(SubtaskModel::EVENT_DELETE, array($this, 'onSubtaskDeleted')); - $subtask = $s->getById(1); + $subtask = $subtaskModel->getById(1); $this->assertNotEmpty($subtask); - $this->assertTrue($s->remove(1)); + $this->assertTrue($subtaskModel->remove(1)); - $subtask = $s->getById(1); + $subtask = $subtaskModel->getById(1); $this->assertEmpty($subtask); } public function testToggleStatusWithoutSession() { - $tc = new TaskCreationModel($this->container); - $s = new SubtaskModel($this->container); - $p = new ProjectModel($this->container); + $taskCreationModel = new TaskCreationModel($this->container); + $subtaskModel = new SubtaskModel($this->container); + $projectModel = new ProjectModel($this->container); - $this->assertEquals(1, $p->create(array('name' => 'test1'))); - $this->assertEquals(1, $tc->create(array('title' => 'test 1', 'project_id' => 1))); + $this->assertEquals(1, $projectModel->create(array('name' => 'test1'))); + $this->assertEquals(1, $taskCreationModel->create(array('title' => 'test 1', 'project_id' => 1))); - $this->assertEquals(1, $s->create(array('title' => 'subtask #1', 'task_id' => 1))); + $this->assertEquals(1, $subtaskModel->create(array('title' => 'subtask #1', 'task_id' => 1))); - $subtask = $s->getById(1); + $subtask = $subtaskModel->getById(1); $this->assertNotEmpty($subtask); $this->assertEquals(SubtaskModel::STATUS_TODO, $subtask['status']); $this->assertEquals(0, $subtask['user_id']); $this->assertEquals(1, $subtask['task_id']); - $this->assertEquals(SubtaskModel::STATUS_INPROGRESS, $s->toggleStatus(1)); + $this->assertEquals(SubtaskModel::STATUS_INPROGRESS, $subtaskModel->toggleStatus(1)); - $subtask = $s->getById(1); + $subtask = $subtaskModel->getById(1); $this->assertNotEmpty($subtask); $this->assertEquals(SubtaskModel::STATUS_INPROGRESS, $subtask['status']); $this->assertEquals(0, $subtask['user_id']); $this->assertEquals(1, $subtask['task_id']); - $this->assertEquals(SubtaskModel::STATUS_DONE, $s->toggleStatus(1)); + $this->assertEquals(SubtaskModel::STATUS_DONE, $subtaskModel->toggleStatus(1)); - $subtask = $s->getById(1); + $subtask = $subtaskModel->getById(1); $this->assertNotEmpty($subtask); $this->assertEquals(SubtaskModel::STATUS_DONE, $subtask['status']); $this->assertEquals(0, $subtask['user_id']); $this->assertEquals(1, $subtask['task_id']); - $this->assertEquals(SubtaskModel::STATUS_TODO, $s->toggleStatus(1)); + $this->assertEquals(SubtaskModel::STATUS_TODO, $subtaskModel->toggleStatus(1)); - $subtask = $s->getById(1); + $subtask = $subtaskModel->getById(1); $this->assertNotEmpty($subtask); $this->assertEquals(SubtaskModel::STATUS_TODO, $subtask['status']); $this->assertEquals(0, $subtask['user_id']); @@ -184,17 +183,16 @@ class SubtaskTest extends Base public function testToggleStatusWithSession() { - $tc = new TaskCreationModel($this->container); - $s = new SubtaskModel($this->container); - $p = new ProjectModel($this->container); - $us = new UserSession($this->container); + $taskCreationModel = new TaskCreationModel($this->container); + $subtaskModel = new SubtaskModel($this->container); + $projectModel = new ProjectModel($this->container); - $this->assertEquals(1, $p->create(array('name' => 'test1'))); - $this->assertEquals(1, $tc->create(array('title' => 'test 1', 'project_id' => 1))); + $this->assertEquals(1, $projectModel->create(array('name' => 'test1'))); + $this->assertEquals(1, $taskCreationModel->create(array('title' => 'test 1', 'project_id' => 1))); - $this->assertEquals(1, $s->create(array('title' => 'subtask #1', 'task_id' => 1))); + $this->assertEquals(1, $subtaskModel->create(array('title' => 'subtask #1', 'task_id' => 1))); - $subtask = $s->getById(1); + $subtask = $subtaskModel->getById(1); $this->assertNotEmpty($subtask); $this->assertEquals(SubtaskModel::STATUS_TODO, $subtask['status']); $this->assertEquals(0, $subtask['user_id']); @@ -203,25 +201,25 @@ class SubtaskTest extends Base // Set the current logged user $this->container['sessionStorage']->user = array('id' => 1); - $this->assertEquals(SubtaskModel::STATUS_INPROGRESS, $s->toggleStatus(1)); + $this->assertEquals(SubtaskModel::STATUS_INPROGRESS, $subtaskModel->toggleStatus(1)); - $subtask = $s->getById(1); + $subtask = $subtaskModel->getById(1); $this->assertNotEmpty($subtask); $this->assertEquals(SubtaskModel::STATUS_INPROGRESS, $subtask['status']); $this->assertEquals(1, $subtask['user_id']); $this->assertEquals(1, $subtask['task_id']); - $this->assertEquals(SubtaskModel::STATUS_DONE, $s->toggleStatus(1)); + $this->assertEquals(SubtaskModel::STATUS_DONE, $subtaskModel->toggleStatus(1)); - $subtask = $s->getById(1); + $subtask = $subtaskModel->getById(1); $this->assertNotEmpty($subtask); $this->assertEquals(SubtaskModel::STATUS_DONE, $subtask['status']); $this->assertEquals(1, $subtask['user_id']); $this->assertEquals(1, $subtask['task_id']); - $this->assertEquals(SubtaskModel::STATUS_TODO, $s->toggleStatus(1)); + $this->assertEquals(SubtaskModel::STATUS_TODO, $subtaskModel->toggleStatus(1)); - $subtask = $s->getById(1); + $subtask = $subtaskModel->getById(1); $this->assertNotEmpty($subtask); $this->assertEquals(SubtaskModel::STATUS_TODO, $subtask['status']); $this->assertEquals(1, $subtask['user_id']); @@ -230,19 +228,19 @@ class SubtaskTest extends Base public function testCloseAll() { - $tc = new TaskCreationModel($this->container); - $s = new SubtaskModel($this->container); - $p = new ProjectModel($this->container); + $taskCreationModel = new TaskCreationModel($this->container); + $subtaskModel = new SubtaskModel($this->container); + $projectModel = new ProjectModel($this->container); - $this->assertEquals(1, $p->create(array('name' => 'test1'))); - $this->assertEquals(1, $tc->create(array('title' => 'test 1', 'project_id' => 1))); + $this->assertEquals(1, $projectModel->create(array('name' => 'test1'))); + $this->assertEquals(1, $taskCreationModel->create(array('title' => 'test 1', 'project_id' => 1))); - $this->assertEquals(1, $s->create(array('title' => 'subtask #1', 'task_id' => 1))); - $this->assertEquals(2, $s->create(array('title' => 'subtask #2', 'task_id' => 1))); + $this->assertEquals(1, $subtaskModel->create(array('title' => 'subtask #1', 'task_id' => 1))); + $this->assertEquals(2, $subtaskModel->create(array('title' => 'subtask #2', 'task_id' => 1))); - $this->assertTrue($s->closeAll(1)); + $this->assertTrue($subtaskModel->closeAll(1)); - $subtasks = $s->getAll(1); + $subtasks = $subtaskModel->getAll(1); $this->assertNotEmpty($subtasks); foreach ($subtasks as $subtask) { @@ -252,24 +250,24 @@ class SubtaskTest extends Base public function testDuplicate() { - $tc = new TaskCreationModel($this->container); - $s = new SubtaskModel($this->container); - $p = new ProjectModel($this->container); + $taskCreationModel = new TaskCreationModel($this->container); + $subtaskModel = new SubtaskModel($this->container); + $projectModel = new ProjectModel($this->container); // We create a project - $this->assertEquals(1, $p->create(array('name' => 'test1'))); + $this->assertEquals(1, $projectModel->create(array('name' => 'test1'))); // We create 2 tasks - $this->assertEquals(1, $tc->create(array('title' => 'test 1', 'project_id' => 1, 'column_id' => 1, 'owner_id' => 1))); - $this->assertEquals(2, $tc->create(array('title' => 'test 2', 'project_id' => 1, 'column_id' => 1, 'owner_id' => 0))); + $this->assertEquals(1, $taskCreationModel->create(array('title' => 'test 1', 'project_id' => 1, 'column_id' => 1, 'owner_id' => 1))); + $this->assertEquals(2, $taskCreationModel->create(array('title' => 'test 2', 'project_id' => 1, 'column_id' => 1, 'owner_id' => 0))); // We create many subtasks for the first task - $this->assertEquals(1, $s->create(array('title' => 'subtask #1', 'task_id' => 1, 'time_estimated' => 5, 'time_spent' => 3, 'status' => 1, 'another_subtask' => 'on'))); - $this->assertEquals(2, $s->create(array('title' => 'subtask #2', 'task_id' => 1, 'time_estimated' => '', 'time_spent' => '', 'status' => 2, 'user_id' => 1))); + $this->assertEquals(1, $subtaskModel->create(array('title' => 'subtask #1', 'task_id' => 1, 'time_estimated' => 5, 'time_spent' => 3, 'status' => 1, 'another_subtask' => 'on'))); + $this->assertEquals(2, $subtaskModel->create(array('title' => 'subtask #2', 'task_id' => 1, 'time_estimated' => '', 'time_spent' => '', 'status' => 2, 'user_id' => 1))); // We duplicate our subtasks - $this->assertTrue($s->duplicate(1, 2)); - $subtasks = $s->getAll(2); + $this->assertTrue($subtaskModel->duplicate(1, 2)); + $subtasks = $subtaskModel->getAll(2); $this->assertNotFalse($subtasks); $this->assertNotEmpty($subtasks); @@ -385,4 +383,18 @@ class SubtaskTest extends Base $this->assertEquals(2, $task['time_spent']); $this->assertEquals(3, $task['time_estimated']); } + + public function testGetProjectId() + { + $taskCreationModel = new TaskCreationModel($this->container); + $subtaskModel = new SubtaskModel($this->container); + $projectModel = new ProjectModel($this->container); + + $this->assertEquals(1, $projectModel->create(array('name' => 'test1'))); + $this->assertEquals(1, $taskCreationModel->create(array('title' => 'test 1', 'project_id' => 1))); + $this->assertEquals(1, $subtaskModel->create(array('title' => 'subtask #1', 'task_id' => 1))); + + $this->assertEquals(1, $subtaskModel->getProjectId(1)); + $this->assertEquals(0, $subtaskModel->getProjectId(2)); + } } diff --git a/tests/units/Model/TaskFileTest.php b/tests/units/Model/TaskFileModelTest.php similarity index 96% rename from tests/units/Model/TaskFileTest.php rename to tests/units/Model/TaskFileModelTest.php index 2faee95cf..de12553fa 100644 --- a/tests/units/Model/TaskFileTest.php +++ b/tests/units/Model/TaskFileModelTest.php @@ -6,7 +6,7 @@ use Kanboard\Model\TaskFileModel; use Kanboard\Model\TaskCreationModel; use Kanboard\Model\ProjectModel; -class TaskFileTest extends Base +class TaskFileModelTest extends Base { public function testCreation() { @@ -442,4 +442,17 @@ class TaskFileTest extends Base $this->assertTrue($fileModel->removeAll(1)); } + + public function testGetProjectId() + { + $projectModel = new ProjectModel($this->container); + $fileModel = new TaskFileModel($this->container); + $taskCreationModel = new TaskCreationModel($this->container); + + $this->assertEquals(1, $projectModel->create(array('name' => 'test'))); + $this->assertEquals(1, $taskCreationModel->create(array('project_id' => 1, 'title' => 'test'))); + $this->assertEquals(1, $fileModel->create(1, 'test', '/tmp/foobar', 10)); + $this->assertEquals(1, $fileModel->getProjectId(1)); + $this->assertEquals(0, $fileModel->getProjectId(2)); + } } diff --git a/tests/units/Model/TaskLinkModelTest.php b/tests/units/Model/TaskLinkModelTest.php new file mode 100644 index 000000000..78590891e --- /dev/null +++ b/tests/units/Model/TaskLinkModelTest.php @@ -0,0 +1,211 @@ +container); + $taskLinkModel = new TaskLinkModel($this->container); + $projectModel = new ProjectModel($this->container); + $taskCreationModel = new TaskCreationModel($this->container); + + $this->assertEquals(1, $projectModel->create(array('name' => 'test'))); + $this->assertEquals(1, $taskCreationModel->create(array('project_id' => 1, 'title' => 'A'))); + $this->assertEquals(2, $taskCreationModel->create(array('project_id' => 1, 'title' => 'B'))); + $this->assertEquals(3, $taskCreationModel->create(array('project_id' => 1, 'title' => 'C'))); + + $this->assertNotFalse($taskLinkModel->create(1, 2, 9)); + $this->assertNotFalse($taskLinkModel->create(1, 3, 9)); + + $task = $taskFinderModel->getExtendedQuery()->findOne(); + $this->assertNotEmpty($task); + } + + public function testCreateTaskLinkWithNoOpposite() + { + $taskLinkModel = new TaskLinkModel($this->container); + $projectModel = new ProjectModel($this->container); + $taskCreationModel = new TaskCreationModel($this->container); + + $this->assertEquals(1, $projectModel->create(array('name' => 'test'))); + $this->assertEquals(1, $taskCreationModel->create(array('project_id' => 1, 'title' => 'A'))); + $this->assertEquals(2, $taskCreationModel->create(array('project_id' => 1, 'title' => 'B'))); + $this->assertEquals(1, $taskLinkModel->create(1, 2, 1)); + + $links = $taskLinkModel->getAll(1); + $this->assertNotEmpty($links); + $this->assertCount(1, $links); + $this->assertEquals('relates to', $links[0]['label']); + $this->assertEquals('B', $links[0]['title']); + $this->assertEquals(2, $links[0]['task_id']); + $this->assertEquals(1, $links[0]['is_active']); + + $links = $taskLinkModel->getAll(2); + $this->assertNotEmpty($links); + $this->assertCount(1, $links); + $this->assertEquals('relates to', $links[0]['label']); + $this->assertEquals('A', $links[0]['title']); + $this->assertEquals(1, $links[0]['task_id']); + $this->assertEquals(1, $links[0]['is_active']); + + $task_link = $taskLinkModel->getById(1); + $this->assertNotEmpty($task_link); + $this->assertEquals(1, $task_link['id']); + $this->assertEquals(1, $task_link['task_id']); + $this->assertEquals(2, $task_link['opposite_task_id']); + $this->assertEquals(1, $task_link['link_id']); + + $opposite_task_link = $taskLinkModel->getOppositeTaskLink($task_link); + $this->assertNotEmpty($opposite_task_link); + $this->assertEquals(2, $opposite_task_link['id']); + $this->assertEquals(2, $opposite_task_link['task_id']); + $this->assertEquals(1, $opposite_task_link['opposite_task_id']); + $this->assertEquals(1, $opposite_task_link['link_id']); + } + + public function testCreateTaskLinkWithOpposite() + { + $taskLinkModel = new TaskLinkModel($this->container); + $projectModel = new ProjectModel($this->container); + $taskCreationModel = new TaskCreationModel($this->container); + + $this->assertEquals(1, $projectModel->create(array('name' => 'test'))); + $this->assertEquals(1, $taskCreationModel->create(array('project_id' => 1, 'title' => 'A'))); + $this->assertEquals(2, $taskCreationModel->create(array('project_id' => 1, 'title' => 'B'))); + $this->assertEquals(1, $taskLinkModel->create(1, 2, 2)); + + $links = $taskLinkModel->getAll(1); + $this->assertNotEmpty($links); + $this->assertCount(1, $links); + $this->assertEquals('blocks', $links[0]['label']); + $this->assertEquals('B', $links[0]['title']); + $this->assertEquals(2, $links[0]['task_id']); + $this->assertEquals(1, $links[0]['is_active']); + + $links = $taskLinkModel->getAll(2); + $this->assertNotEmpty($links); + $this->assertCount(1, $links); + $this->assertEquals('is blocked by', $links[0]['label']); + $this->assertEquals('A', $links[0]['title']); + $this->assertEquals(1, $links[0]['task_id']); + $this->assertEquals(1, $links[0]['is_active']); + + $task_link = $taskLinkModel->getById(1); + $this->assertNotEmpty($task_link); + $this->assertEquals(1, $task_link['id']); + $this->assertEquals(1, $task_link['task_id']); + $this->assertEquals(2, $task_link['opposite_task_id']); + $this->assertEquals(2, $task_link['link_id']); + + $opposite_task_link = $taskLinkModel->getOppositeTaskLink($task_link); + $this->assertNotEmpty($opposite_task_link); + $this->assertEquals(2, $opposite_task_link['id']); + $this->assertEquals(2, $opposite_task_link['task_id']); + $this->assertEquals(1, $opposite_task_link['opposite_task_id']); + $this->assertEquals(3, $opposite_task_link['link_id']); + } + + public function testGroupByLabel() + { + $taskLinkModel = new TaskLinkModel($this->container); + $projectModel = new ProjectModel($this->container); + $taskCreationModel = new TaskCreationModel($this->container); + + $this->assertEquals(1, $projectModel->create(array('name' => 'test'))); + + $this->assertEquals(1, $taskCreationModel->create(array('project_id' => 1, 'title' => 'A'))); + $this->assertEquals(2, $taskCreationModel->create(array('project_id' => 1, 'title' => 'B'))); + $this->assertEquals(3, $taskCreationModel->create(array('project_id' => 1, 'title' => 'C'))); + + $this->assertNotFalse($taskLinkModel->create(1, 2, 2)); + $this->assertNotFalse($taskLinkModel->create(1, 3, 2)); + + $links = $taskLinkModel->getAllGroupedByLabel(1); + $this->assertCount(1, $links); + $this->assertArrayHasKey('blocks', $links); + $this->assertCount(2, $links['blocks']); + $this->assertEquals('test', $links['blocks'][0]['project_name']); + $this->assertEquals('Backlog', $links['blocks'][0]['column_title']); + $this->assertEquals('blocks', $links['blocks'][0]['label']); + } + + public function testUpdate() + { + $taskLinkModel = new TaskLinkModel($this->container); + $projectModel = new ProjectModel($this->container); + $taskCreationModel = new TaskCreationModel($this->container); + + $this->assertEquals(1, $projectModel->create(array('name' => 'test1'))); + $this->assertEquals(2, $projectModel->create(array('name' => 'test2'))); + $this->assertEquals(1, $taskCreationModel->create(array('project_id' => 1, 'title' => 'A'))); + $this->assertEquals(2, $taskCreationModel->create(array('project_id' => 2, 'title' => 'B'))); + $this->assertEquals(3, $taskCreationModel->create(array('project_id' => 1, 'title' => 'C'))); + + $this->assertEquals(1, $taskLinkModel->create(1, 2, 5)); + $this->assertTrue($taskLinkModel->update(1, 1, 3, 11)); + + $links = $taskLinkModel->getAll(1); + $this->assertNotEmpty($links); + $this->assertCount(1, $links); + $this->assertEquals('is fixed by', $links[0]['label']); + $this->assertEquals('C', $links[0]['title']); + $this->assertEquals(3, $links[0]['task_id']); + + $links = $taskLinkModel->getAll(2); + $this->assertEmpty($links); + + $links = $taskLinkModel->getAll(3); + $this->assertNotEmpty($links); + $this->assertCount(1, $links); + $this->assertEquals('fixes', $links[0]['label']); + $this->assertEquals('A', $links[0]['title']); + $this->assertEquals(1, $links[0]['task_id']); + } + + public function testRemove() + { + $taskLinkModel = new TaskLinkModel($this->container); + $projectModel = new ProjectModel($this->container); + $taskCreationModel = new TaskCreationModel($this->container); + + $this->assertEquals(1, $projectModel->create(array('name' => 'test'))); + $this->assertEquals(1, $taskCreationModel->create(array('project_id' => 1, 'title' => 'A'))); + $this->assertEquals(2, $taskCreationModel->create(array('project_id' => 1, 'title' => 'B'))); + $this->assertEquals(1, $taskLinkModel->create(1, 2, 2)); + + $links = $taskLinkModel->getAll(1); + $this->assertNotEmpty($links); + $links = $taskLinkModel->getAll(2); + $this->assertNotEmpty($links); + + $this->assertTrue($taskLinkModel->remove($links[0]['id'])); + + $links = $taskLinkModel->getAll(1); + $this->assertEmpty($links); + $links = $taskLinkModel->getAll(2); + $this->assertEmpty($links); + } + + public function testGetProjectId() + { + $taskLinkModel = new TaskLinkModel($this->container); + $projectModel = new ProjectModel($this->container); + $taskCreationModel = new TaskCreationModel($this->container); + + $this->assertEquals(1, $projectModel->create(array('name' => 'test'))); + $this->assertEquals(1, $taskCreationModel->create(array('project_id' => 1, 'title' => 'A'))); + $this->assertEquals(2, $taskCreationModel->create(array('project_id' => 1, 'title' => 'B'))); + $this->assertEquals(1, $taskLinkModel->create(1, 2, 2)); + + $this->assertEquals(1, $taskLinkModel->getProjectId(1)); + $this->assertEquals(0, $taskLinkModel->getProjectId(42)); + } +} diff --git a/tests/units/Model/TaskLinkTest.php b/tests/units/Model/TaskLinkTest.php deleted file mode 100644 index bc5747312..000000000 --- a/tests/units/Model/TaskLinkTest.php +++ /dev/null @@ -1,196 +0,0 @@ -container); - $tl = new TaskLinkModel($this->container); - $p = new ProjectModel($this->container); - $tc = new TaskCreationModel($this->container); - - $this->assertEquals(1, $p->create(array('name' => 'test'))); - $this->assertEquals(1, $tc->create(array('project_id' => 1, 'title' => 'A'))); - $this->assertEquals(2, $tc->create(array('project_id' => 1, 'title' => 'B'))); - $this->assertEquals(3, $tc->create(array('project_id' => 1, 'title' => 'C'))); - - $this->assertNotFalse($tl->create(1, 2, 9)); - $this->assertNotFalse($tl->create(1, 3, 9)); - - $task = $tf->getExtendedQuery()->findOne(); - $this->assertNotEmpty($task); - } - - public function testCreateTaskLinkWithNoOpposite() - { - $tl = new TaskLinkModel($this->container); - $p = new ProjectModel($this->container); - $tc = new TaskCreationModel($this->container); - - $this->assertEquals(1, $p->create(array('name' => 'test'))); - $this->assertEquals(1, $tc->create(array('project_id' => 1, 'title' => 'A'))); - $this->assertEquals(2, $tc->create(array('project_id' => 1, 'title' => 'B'))); - $this->assertEquals(1, $tl->create(1, 2, 1)); - - $links = $tl->getAll(1); - $this->assertNotEmpty($links); - $this->assertCount(1, $links); - $this->assertEquals('relates to', $links[0]['label']); - $this->assertEquals('B', $links[0]['title']); - $this->assertEquals(2, $links[0]['task_id']); - $this->assertEquals(1, $links[0]['is_active']); - - $links = $tl->getAll(2); - $this->assertNotEmpty($links); - $this->assertCount(1, $links); - $this->assertEquals('relates to', $links[0]['label']); - $this->assertEquals('A', $links[0]['title']); - $this->assertEquals(1, $links[0]['task_id']); - $this->assertEquals(1, $links[0]['is_active']); - - $task_link = $tl->getById(1); - $this->assertNotEmpty($task_link); - $this->assertEquals(1, $task_link['id']); - $this->assertEquals(1, $task_link['task_id']); - $this->assertEquals(2, $task_link['opposite_task_id']); - $this->assertEquals(1, $task_link['link_id']); - - $opposite_task_link = $tl->getOppositeTaskLink($task_link); - $this->assertNotEmpty($opposite_task_link); - $this->assertEquals(2, $opposite_task_link['id']); - $this->assertEquals(2, $opposite_task_link['task_id']); - $this->assertEquals(1, $opposite_task_link['opposite_task_id']); - $this->assertEquals(1, $opposite_task_link['link_id']); - } - - public function testCreateTaskLinkWithOpposite() - { - $tl = new TaskLinkModel($this->container); - $p = new ProjectModel($this->container); - $tc = new TaskCreationModel($this->container); - - $this->assertEquals(1, $p->create(array('name' => 'test'))); - $this->assertEquals(1, $tc->create(array('project_id' => 1, 'title' => 'A'))); - $this->assertEquals(2, $tc->create(array('project_id' => 1, 'title' => 'B'))); - $this->assertEquals(1, $tl->create(1, 2, 2)); - - $links = $tl->getAll(1); - $this->assertNotEmpty($links); - $this->assertCount(1, $links); - $this->assertEquals('blocks', $links[0]['label']); - $this->assertEquals('B', $links[0]['title']); - $this->assertEquals(2, $links[0]['task_id']); - $this->assertEquals(1, $links[0]['is_active']); - - $links = $tl->getAll(2); - $this->assertNotEmpty($links); - $this->assertCount(1, $links); - $this->assertEquals('is blocked by', $links[0]['label']); - $this->assertEquals('A', $links[0]['title']); - $this->assertEquals(1, $links[0]['task_id']); - $this->assertEquals(1, $links[0]['is_active']); - - $task_link = $tl->getById(1); - $this->assertNotEmpty($task_link); - $this->assertEquals(1, $task_link['id']); - $this->assertEquals(1, $task_link['task_id']); - $this->assertEquals(2, $task_link['opposite_task_id']); - $this->assertEquals(2, $task_link['link_id']); - - $opposite_task_link = $tl->getOppositeTaskLink($task_link); - $this->assertNotEmpty($opposite_task_link); - $this->assertEquals(2, $opposite_task_link['id']); - $this->assertEquals(2, $opposite_task_link['task_id']); - $this->assertEquals(1, $opposite_task_link['opposite_task_id']); - $this->assertEquals(3, $opposite_task_link['link_id']); - } - - public function testGroupByLabel() - { - $tl = new TaskLinkModel($this->container); - $p = new ProjectModel($this->container); - $tc = new TaskCreationModel($this->container); - - $this->assertEquals(1, $p->create(array('name' => 'test'))); - - $this->assertEquals(1, $tc->create(array('project_id' => 1, 'title' => 'A'))); - $this->assertEquals(2, $tc->create(array('project_id' => 1, 'title' => 'B'))); - $this->assertEquals(3, $tc->create(array('project_id' => 1, 'title' => 'C'))); - - $this->assertNotFalse($tl->create(1, 2, 2)); - $this->assertNotFalse($tl->create(1, 3, 2)); - - $links = $tl->getAllGroupedByLabel(1); - $this->assertCount(1, $links); - $this->assertArrayHasKey('blocks', $links); - $this->assertCount(2, $links['blocks']); - $this->assertEquals('test', $links['blocks'][0]['project_name']); - $this->assertEquals('Backlog', $links['blocks'][0]['column_title']); - $this->assertEquals('blocks', $links['blocks'][0]['label']); - } - - public function testUpdate() - { - $tl = new TaskLinkModel($this->container); - $p = new ProjectModel($this->container); - $tc = new TaskCreationModel($this->container); - - $this->assertEquals(1, $p->create(array('name' => 'test1'))); - $this->assertEquals(2, $p->create(array('name' => 'test2'))); - $this->assertEquals(1, $tc->create(array('project_id' => 1, 'title' => 'A'))); - $this->assertEquals(2, $tc->create(array('project_id' => 2, 'title' => 'B'))); - $this->assertEquals(3, $tc->create(array('project_id' => 1, 'title' => 'C'))); - - $this->assertEquals(1, $tl->create(1, 2, 5)); - $this->assertTrue($tl->update(1, 1, 3, 11)); - - $links = $tl->getAll(1); - $this->assertNotEmpty($links); - $this->assertCount(1, $links); - $this->assertEquals('is fixed by', $links[0]['label']); - $this->assertEquals('C', $links[0]['title']); - $this->assertEquals(3, $links[0]['task_id']); - - $links = $tl->getAll(2); - $this->assertEmpty($links); - - $links = $tl->getAll(3); - $this->assertNotEmpty($links); - $this->assertCount(1, $links); - $this->assertEquals('fixes', $links[0]['label']); - $this->assertEquals('A', $links[0]['title']); - $this->assertEquals(1, $links[0]['task_id']); - } - - public function testRemove() - { - $tl = new TaskLinkModel($this->container); - $p = new ProjectModel($this->container); - $tc = new TaskCreationModel($this->container); - - $this->assertEquals(1, $p->create(array('name' => 'test'))); - $this->assertEquals(1, $tc->create(array('project_id' => 1, 'title' => 'A'))); - $this->assertEquals(2, $tc->create(array('project_id' => 1, 'title' => 'B'))); - $this->assertEquals(1, $tl->create(1, 2, 2)); - - $links = $tl->getAll(1); - $this->assertNotEmpty($links); - $links = $tl->getAll(2); - $this->assertNotEmpty($links); - - $this->assertTrue($tl->remove($links[0]['id'])); - - $links = $tl->getAll(1); - $this->assertEmpty($links); - $links = $tl->getAll(2); - $this->assertEmpty($links); - } -}