diff options
Diffstat (limited to 'app')
45 files changed, 790 insertions, 383 deletions
diff --git a/app/Api/Authorization/ActionAuthorization.php b/app/Api/Authorization/ActionAuthorization.php new file mode 100644 index 00000000..4b41ad82 --- /dev/null +++ b/app/Api/Authorization/ActionAuthorization.php @@ -0,0 +1,19 @@ +<?php + +namespace Kanboard\Api\Authorization; + +/** + * Class ActionAuthorization + * + * @package Kanboard\Api\Authorization + * @author Frederic Guillot + */ +class ActionAuthorization extends ProjectAuthorization +{ + public function check($class, $method, $action_id) + { + if ($this->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 00000000..f17265a2 --- /dev/null +++ b/app/Api/Authorization/CategoryAuthorization.php @@ -0,0 +1,19 @@ +<?php + +namespace Kanboard\Api\Authorization; + +/** + * Class CategoryAuthorization + * + * @package Kanboard\Api\Authorization + * @author Frederic Guillot + */ +class CategoryAuthorization extends ProjectAuthorization +{ + public function check($class, $method, $category_id) + { + if ($this->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 00000000..37aecda2 --- /dev/null +++ b/app/Api/Authorization/ColumnAuthorization.php @@ -0,0 +1,19 @@ +<?php + +namespace Kanboard\Api\Authorization; + +/** + * Class ColumnAuthorization + * + * @package Kanboard\Api\Authorization + * @author Frederic Guillot + */ +class ColumnAuthorization extends ProjectAuthorization +{ + public function check($class, $method, $column_id) + { + if ($this->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 00000000..ed15512e --- /dev/null +++ b/app/Api/Authorization/CommentAuthorization.php @@ -0,0 +1,19 @@ +<?php + +namespace Kanboard\Api\Authorization; + +/** + * Class CommentAuthorization + * + * @package Kanboard\Api\Authorization + * @author Frederic Guillot + */ +class CommentAuthorization extends ProjectAuthorization +{ + public function check($class, $method, $comment_id) + { + if ($this->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 00000000..070a6371 --- /dev/null +++ b/app/Api/Authorization/ProcedureAuthorization.php @@ -0,0 +1,32 @@ +<?php + +namespace Kanboard\Api\Authorization; + +use JsonRPC\Exception\AccessDeniedException; +use Kanboard\Core\Base; + +/** + * Class ProcedureAuthorization + * + * @package Kanboard\Api\Authorization + * @author Frederic Guillot + */ +class ProcedureAuthorization extends Base +{ + private $userSpecificProcedures = array( + 'getMe', + 'getMyDashboard', + 'getMyActivityStream', + 'createMyPrivateProject', + 'getMyProjectsList', + 'getMyProjects', + 'getMyOverdueTasks', + ); + + public function check($procedure) + { + if (! $this->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 00000000..21ecf311 --- /dev/null +++ b/app/Api/Authorization/ProjectAuthorization.php @@ -0,0 +1,35 @@ +<?php + +namespace Kanboard\Api\Authorization; + +use JsonRPC\Exception\AccessDeniedException; +use Kanboard\Core\Base; + +/** + * Class ProjectAuthorization + * + * @package Kanboard\Api\Authorization + * @author Frederic Guillot + */ +class ProjectAuthorization extends Base +{ + public function check($class, $method, $project_id) + { + if ($this->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 00000000..fcb57929 --- /dev/null +++ b/app/Api/Authorization/SubtaskAuthorization.php @@ -0,0 +1,19 @@ +<?php + +namespace Kanboard\Api\Authorization; + +/** + * Class SubtaskAuthorization + * + * @package Kanboard\Api\Authorization + * @author Frederic Guillot + */ +class SubtaskAuthorization extends ProjectAuthorization +{ + public function check($class, $method, $subtask_id) + { + if ($this->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 00000000..db93b76b --- /dev/null +++ b/app/Api/Authorization/TaskAuthorization.php @@ -0,0 +1,19 @@ +<?php + +namespace Kanboard\Api\Authorization; + +/** + * Class TaskAuthorization + * + * @package Kanboard\Api\Authorization + * @author Frederic Guillot + */ +class TaskAuthorization extends ProjectAuthorization +{ + public function check($class, $method, $category_id) + { + if ($this->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 00000000..e40783eb --- /dev/null +++ b/app/Api/Authorization/TaskFileAuthorization.php @@ -0,0 +1,19 @@ +<?php + +namespace Kanboard\Api\Authorization; + +/** + * Class TaskFileAuthorization + * + * @package Kanboard\Api\Authorization + * @author Frederic Guillot + */ +class TaskFileAuthorization extends ProjectAuthorization +{ + public function check($class, $method, $file_id) + { + if ($this->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 00000000..2f5fc8d5 --- /dev/null +++ b/app/Api/Authorization/TaskLinkAuthorization.php @@ -0,0 +1,19 @@ +<?php + +namespace Kanboard\Api\Authorization; + +/** + * Class TaskLinkAuthorization + * + * @package Kanboard\Api\Authorization + * @author Frederic Guillot + */ +class TaskLinkAuthorization extends ProjectAuthorization +{ + public function check($class, $method, $task_link_id) + { + if ($this->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 00000000..3fd6865c --- /dev/null +++ b/app/Api/Authorization/UserAuthorization.php @@ -0,0 +1,22 @@ +<?php + +namespace Kanboard\Api\Authorization; + +use JsonRPC\Exception\AccessDeniedException; +use Kanboard\Core\Base; + +/** + * Class UserAuthorization + * + * @package Kanboard\Api\Authorization + * @author Frederic Guillot + */ +class UserAuthorization extends Base +{ + public function check($class, $method) + { + if ($this->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 index b16e10b8..8e309593 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 index 116742d8..4043dbb9 100644 --- a/app/Api/ActionApi.php +++ b/app/Api/Procedure/ActionProcedure.php @@ -1,16 +1,17 @@ <?php -namespace Kanboard\Api; +namespace Kanboard\Api\Procedure; -use Kanboard\Core\Base; +use Kanboard\Api\Authorization\ActionAuthorization; +use Kanboard\Api\Authorization\ProjectAuthorization; /** * Action API controller * - * @package Kanboard\Api + * @package Kanboard\Api\Procedure * @author Frederic Guillot */ -class ActionApi extends Base +class ActionProcedure extends BaseProcedure { public function getAvailableActions() { @@ -29,16 +30,19 @@ class ActionApi extends Base public function removeAction($action_id) { + ActionAuthorization::getInstance($this->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 index 637de5c5..60af4a60 100644 --- a/app/Api/AppApi.php +++ b/app/Api/Procedure/AppProcedure.php @@ -1,16 +1,14 @@ <?php -namespace Kanboard\Api; - -use Kanboard\Core\Base; +namespace Kanboard\Api\Procedure; /** * App API controller * - * @package Kanboard\Api + * @package Kanboard\Api\Procedure * @author Frederic Guillot */ -class AppApi extends Base +class AppProcedure extends BaseProcedure { public function getTimezone() { diff --git a/app/Api/BaseApi.php b/app/Api/Procedure/BaseProcedure.php index 9f69aa65..e31b3027 100644 --- a/app/Api/BaseApi.php +++ b/app/Api/Procedure/BaseProcedure.php @@ -1,30 +1,24 @@ <?php -namespace Kanboard\Api; +namespace Kanboard\Api\Procedure; -use JsonRPC\Exception\AccessDeniedException; +use Kanboard\Api\Authorization\ProcedureAuthorization; +use Kanboard\Api\Authorization\UserAuthorization; use Kanboard\Core\Base; +use ReflectionClass; /** * Base class * - * @package Kanboard\Api + * @package Kanboard\Api\Procedure * @author Frederic Guillot */ -abstract class BaseApi extends Base +abstract class BaseProcedure extends Base { - public function checkProjectPermission($project_id) + public function beforeProcedure($procedure) { - if ($this->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) @@ -71,4 +65,21 @@ abstract class BaseApi extends Base return $projects; } + + protected function filterValues(array $values) + { + foreach ($values as $key => $value) { + if (is_null($value)) { + unset($values[$key]); + } + } + + 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 index 70f21c0e..674b5466 100644 --- a/app/Api/BoardApi.php +++ b/app/Api/Procedure/BoardProcedure.php @@ -1,21 +1,22 @@ <?php -namespace Kanboard\Api; +namespace Kanboard\Api\Procedure; +use Kanboard\Api\Authorization\ProjectAuthorization; use Kanboard\Formatter\BoardFormatter; /** * Board API controller * - * @package Kanboard\Api + * @package Kanboard\Api\Procedure * @author Frederic Guillot */ -class BoardApi extends BaseApi +class BoardProcedure extends BaseProcedure { public function getBoard($project_id) { - $this->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 index c56cfb35..3ebbd908 100644 --- a/app/Api/CategoryApi.php +++ b/app/Api/Procedure/CategoryProcedure.php @@ -1,34 +1,40 @@ <?php -namespace Kanboard\Api; +namespace Kanboard\Api\Procedure; -use Kanboard\Core\Base; +use Kanboard\Api\Authorization\CategoryAuthorization; +use Kanboard\Api\Authorization\ProjectAuthorization; /** * Category API controller * - * @package Kanboard\Api + * @package Kanboard\Api\Procedure * @author Frederic Guillot */ -class CategoryApi extends Base +class CategoryProcedure extends BaseProcedure { public function getCategory($category_id) { + CategoryAuthorization::getInstance($this->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 index aa4026f6..ab9d173b 100644 --- a/app/Api/ColumnApi.php +++ b/app/Api/Procedure/ColumnProcedure.php @@ -1,42 +1,51 @@ <?php -namespace Kanboard\Api; +namespace Kanboard\Api\Procedure; + +use Kanboard\Api\Authorization\ColumnAuthorization; +use Kanboard\Api\Authorization\ProjectAuthorization; /** * Column API controller * - * @package Kanboard\Api + * @package Kanboard\Api\Procedure * @author Frederic Guillot */ -class ColumnApi extends BaseApi +class ColumnProcedure extends BaseProcedure { public function getColumns($project_id) { + ProjectAuthorization::getInstance($this->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 index 8358efee..019a49bb 100644 --- a/app/Api/CommentApi.php +++ b/app/Api/Procedure/CommentProcedure.php @@ -1,34 +1,40 @@ <?php -namespace Kanboard\Api; +namespace Kanboard\Api\Procedure; -use Kanboard\Core\Base; +use Kanboard\Api\Authorization\CommentAuthorization; +use Kanboard\Api\Authorization\TaskAuthorization; /** * Comment API controller * - * @package Kanboard\Api + * @package Kanboard\Api\Procedure * @author Frederic Guillot */ -class CommentApi extends Base +class CommentProcedure extends BaseProcedure { public function getComment($comment_id) { + CommentAuthorization::getInstance($this->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 index e09f6975..081d6ac8 100644 --- a/app/Api/GroupMemberApi.php +++ b/app/Api/Procedure/GroupMemberProcedure.php @@ -1,16 +1,14 @@ <?php -namespace Kanboard\Api; - -use Kanboard\Core\Base; +namespace Kanboard\Api\Procedure; /** * Group Member API controller * - * @package Kanboard\Api + * @package Kanboard\Api\Procedure * @author Frederic Guillot */ -class GroupMemberApi extends Base +class GroupMemberProcedure extends BaseProcedure { public function getMemberGroups($user_id) { diff --git a/app/Api/GroupApi.php b/app/Api/Procedure/GroupProcedure.php index 1701edc3..804940a2 100644 --- a/app/Api/GroupApi.php +++ b/app/Api/Procedure/GroupProcedure.php @@ -1,16 +1,14 @@ <?php -namespace Kanboard\Api; - -use Kanboard\Core\Base; +namespace Kanboard\Api\Procedure; /** * Group API controller * - * @package Kanboard\Api + * @package Kanboard\Api\Procedure * @author Frederic Guillot */ -class GroupApi extends Base +class GroupProcedure extends BaseProcedure { public function createGroup($name, $external_id = '') { diff --git a/app/Api/LinkApi.php b/app/Api/Procedure/LinkProcedure.php index d8e525e4..b4cecf3a 100644 --- a/app/Api/LinkApi.php +++ b/app/Api/Procedure/LinkProcedure.php @@ -1,16 +1,14 @@ <?php -namespace Kanboard\Api; - -use Kanboard\Core\Base; +namespace Kanboard\Api\Procedure; /** * Link API controller * - * @package Kanboard\Api + * @package Kanboard\Api\Procedure * @author Frederic Guillot */ -class LinkApi extends Base +class LinkProcedure extends BaseProcedure { /** * Get a link by id diff --git a/app/Api/MeApi.php b/app/Api/Procedure/MeProcedure.php index 497749b6..e59e6522 100644 --- a/app/Api/MeApi.php +++ b/app/Api/Procedure/MeProcedure.php @@ -1,16 +1,16 @@ <?php -namespace Kanboard\Api; +namespace Kanboard\Api\Procedure; use Kanboard\Model\SubtaskModel; /** * Me API controller * - * @package Kanboard\Api + * @package Kanboard\Api\Procedure * @author Frederic Guillot */ -class MeApi extends BaseApi +class MeProcedure extends BaseProcedure { public function getMe() { diff --git a/app/Api/ProjectPermissionApi.php b/app/Api/Procedure/ProjectPermissionProcedure.php index 703cd0f3..e22e1d62 100644 --- a/app/Api/ProjectPermissionApi.php +++ b/app/Api/Procedure/ProjectPermissionProcedure.php @@ -1,73 +1,69 @@ <?php -namespace Kanboard\Api; +namespace Kanboard\Api\Procedure; -use Kanboard\Core\Base; +use Kanboard\Api\Authorization\ProjectAuthorization; use Kanboard\Core\Security\Role; /** * Project Permission API controller * - * @package Kanboard\Api + * @package Kanboard\Api\Procedure * @author Frederic Guillot */ -class ProjectPermissionApi extends Base +class ProjectPermissionProcedure extends BaseProcedure { public function getProjectUsers($project_id) { + ProjectAuthorization::getInstance($this->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); } - // Deprecated - public function getMembers($project_id) + public function getProjectUserRole($project_id, $user_id) { - return $this->getProjectUsers($project_id); - } - - // Deprecated - public function revokeUser($project_id, $user_id) - { - return $this->removeProjectUser($project_id, $user_id); - } - - // Deprecated - public function allowUser($project_id, $user_id) - { - return $this->addProjectUser($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 00000000..a580c8d9 --- /dev/null +++ b/app/Api/Procedure/ProjectProcedure.php @@ -0,0 +1,113 @@ +<?php + +namespace Kanboard\Api\Procedure; + +use Kanboard\Api\Authorization\ProjectAuthorization; + +/** + * Project API controller + * + * @package Kanboard\Api\Procedure + * @author Frederic Guillot + */ +class ProjectProcedure extends BaseProcedure +{ + public function getProjectById($project_id) + { + ProjectAuthorization::getInstance($this->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 getProjectByIdentifier($identifier) + { + $project = $this->formatProject($this->projectModel->getByIdentifier($identifier)); + ProjectAuthorization::getInstance($this->container)->check($this->getClassName(), 'getProjectByIdentifier', $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 = $this->filterValues(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 = null, $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 index 5764ff7d..e2400912 100644 --- a/app/Api/SubtaskApi.php +++ b/app/Api/Procedure/SubtaskProcedure.php @@ -1,34 +1,40 @@ <?php -namespace Kanboard\Api; +namespace Kanboard\Api\Procedure; -use Kanboard\Core\Base; +use Kanboard\Api\Authorization\SubtaskAuthorization; +use Kanboard\Api\Authorization\TaskAuthorization; /** * Subtask API controller * - * @package Kanboard\Api + * @package Kanboard\Api\Procedure * @author Frederic Guillot */ -class SubtaskApi extends Base +class SubtaskProcedure extends BaseProcedure { public function getSubtask($subtask_id) { + SubtaskAuthorization::getInstance($this->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 00000000..5d1988d6 --- /dev/null +++ b/app/Api/Procedure/SubtaskTimeTrackingProcedure.php @@ -0,0 +1,39 @@ +<?php + +namespace Kanboard\Api\Procedure; + +use Kanboard\Api\Authorization\SubtaskAuthorization; + +/** + * Subtask Time Tracking API controller + * + * @package Kanboard\Api\Procedure + * @author Frederic Guillot + * @author Nikolaos Georgakis + */ +class SubtaskTimeTrackingProcedure extends BaseProcedure +{ + public function hasSubtaskTimer($subtask_id, $user_id) + { + SubtaskAuthorization::getInstance($this->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 index c3c56a71..9b7d181d 100644 --- a/app/Api/SwimlaneApi.php +++ b/app/Api/Procedure/SwimlaneProcedure.php @@ -1,34 +1,39 @@ <?php -namespace Kanboard\Api; +namespace Kanboard\Api\Procedure; -use Kanboard\Core\Base; +use Kanboard\Api\Authorization\ProjectAuthorization; /** * Swimlane API controller * - * @package Kanboard\Api + * @package Kanboard\Api\Procedure * @author Frederic Guillot */ -class SwimlaneApi extends Base +class SwimlaneProcedure extends BaseProcedure { public function getActiveSwimlanes($project_id) { + ProjectAuthorization::getInstance($this->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/FileApi.php b/app/Api/Procedure/TaskFileProcedure.php index 1ed3aeb9..5aa7ea0b 100644 --- a/app/Api/FileApi.php +++ b/app/Api/Procedure/TaskFileProcedure.php @@ -1,29 +1,36 @@ <?php -namespace Kanboard\Api; +namespace Kanboard\Api\Procedure; +use Kanboard\Api\Authorization\ProjectAuthorization; +use Kanboard\Api\Authorization\TaskAuthorization; +use Kanboard\Api\Authorization\TaskFileAuthorization; use Kanboard\Core\ObjectStorage\ObjectStorageException; /** - * File API controller + * Task File API controller * - * @package Kanboard\Api + * @package Kanboard\Api\Procedure * @author Frederic Guillot */ -class FileApi extends BaseApi +class TaskFileProcedure extends BaseProcedure { public function getTaskFile($file_id) { + TaskFileAuthorization::getInstance($this->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); @@ -33,12 +40,14 @@ class FileApi extends BaseApi } catch (ObjectStorageException $e) { $this->logger->error($e->getMessage()); } - + return ''; } 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,43 +58,13 @@ class FileApi 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); } - - // Deprecated procedures - - public function getFile($file_id) - { - return $this->getTaskFile($file_id); - } - - public function getAllFiles($task_id) - { - return $this->getAllTaskFiles($task_id); - } - - public function downloadFile($file_id) - { - return $this->downloadTaskFile($file_id); - } - - public function createFile($project_id, $task_id, $filename, $blob) - { - return $this->createTaskFile($project_id, $task_id, $filename, $blob); - } - - public function removeFile($file_id) - { - return $this->removeTaskFile($file_id); - } - - public function removeAllFiles($task_id) - { - return $this->removeAllTaskFiles($task_id); - } } diff --git a/app/Api/TaskLinkApi.php b/app/Api/Procedure/TaskLinkProcedure.php index bb809133..375266fb 100644 --- a/app/Api/TaskLinkApi.php +++ b/app/Api/Procedure/TaskLinkProcedure.php @@ -1,16 +1,17 @@ <?php -namespace Kanboard\Api; +namespace Kanboard\Api\Procedure; -use Kanboard\Core\Base; +use Kanboard\Api\Authorization\TaskAuthorization; +use Kanboard\Api\Authorization\TaskLinkAuthorization; /** * TaskLink API controller * - * @package Kanboard\Api + * @package Kanboard\Api\Procedure * @author Frederic Guillot */ -class TaskLinkApi extends Base +class TaskLinkProcedure extends BaseProcedure { /** * Get a task link @@ -21,6 +22,7 @@ class TaskLinkApi extends Base */ public function getTaskLinkById($task_link_id) { + TaskLinkAuthorization::getInstance($this->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 index ddb3ac54..2d29a4ef 100644 --- a/app/Api/TaskApi.php +++ b/app/Api/Procedure/TaskProcedure.php @@ -1,39 +1,41 @@ <?php -namespace Kanboard\Api; +namespace Kanboard\Api\Procedure; +use Kanboard\Api\Authorization\ProjectAuthorization; +use Kanboard\Api\Authorization\TaskAuthorization; use Kanboard\Filter\TaskProjectFilter; use Kanboard\Model\TaskModel; /** * Task API controller * - * @package Kanboard\Api + * @package Kanboard\Api\Procedure * @author Frederic Guillot */ -class TaskApi extends BaseApi +class TaskProcedure extends BaseProcedure { public function searchTasks($project_id, $query) { - $this->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) { @@ -139,7 +143,7 @@ class TaskApi extends BaseApi return false; } - $values = array( + $values = $this->filterValues(array( 'id' => $id, 'title' => $title, 'color_id' => $color_id, @@ -155,13 +159,7 @@ class TaskApi extends BaseApi 'recurrence_basedate' => $recurrence_basedate, 'reference' => $reference, 'priority' => $priority, - ); - - foreach ($values as $key => $value) { - if (is_null($value)) { - unset($values[$key]); - } - } + )); list($valid) = $this->taskValidator->validateApiModification($values); return $valid && $this->taskModificationModel->update($values); diff --git a/app/Api/UserApi.php b/app/Api/Procedure/UserProcedure.php index 88d75527..145f85bf 100644 --- a/app/Api/UserApi.php +++ b/app/Api/Procedure/UserProcedure.php @@ -1,8 +1,7 @@ <?php -namespace Kanboard\Api; +namespace Kanboard\Api\Procedure; -use Kanboard\Core\Base; use LogicException; use Kanboard\Core\Security\Role; use Kanboard\Core\Ldap\Client as LdapClient; @@ -12,10 +11,10 @@ use Kanboard\Core\Ldap\User as LdapUser; /** * User API controller * - * @package Kanboard\Api + * @package Kanboard\Api\Procedure * @author Frederic Guillot */ -class UserApi extends Base +class UserProcedure extends BaseProcedure { public function getUser($user_id) { @@ -118,19 +117,13 @@ class UserApi extends Base public function updateUser($id, $username = null, $name = null, $email = null, $role = null) { - $values = array( + $values = $this->filterValues(array( 'id' => $id, 'username' => $username, 'name' => $name, 'email' => $email, 'role' => $role, - ); - - foreach ($values as $key => $value) { - if (is_null($value)) { - unset($values[$key]); - } - } + )); list($valid, ) = $this->userValidator->validateApiModification($values); return $valid && $this->userModel->update($values); diff --git a/app/Api/ProjectApi.php b/app/Api/ProjectApi.php deleted file mode 100644 index 29a9cd79..00000000 --- a/app/Api/ProjectApi.php +++ /dev/null @@ -1,87 +0,0 @@ -<?php - -namespace Kanboard\Api; - -/** - * Project API controller - * - * @package Kanboard\Api - * @author Frederic Guillot - */ -class ProjectApi extends BaseApi -{ - public function getProjectById($project_id) - { - $this->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($id, $name, $description = null) - { - $values = array( - 'id' => $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 0e700b31..00000000 --- a/app/Api/SubtaskTimeTrackingApi.php +++ /dev/null @@ -1,34 +0,0 @@ -<?php - -namespace Kanboard\Api; - -use Kanboard\Core\Base; - -/** - * Subtask Time Tracking API controller - * - * @package api - * @author Nikolaos Georgakis - */ -class SubtaskTimeTrackingApi extends Base -{ - public function hasTimer($subtask_id,$user_id) - { - return $this->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 e5dd6ad9..eacca65d 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 53393ed5..b5d2bd06 100644 --- a/app/Model/ActionModel.php +++ b/app/Model/ActionModel.php @@ -86,6 +86,18 @@ class ActionModel extends Base } /** + * 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 * * @access private diff --git a/app/Model/CategoryModel.php b/app/Model/CategoryModel.php index 62fb5611..024d0026 100644 --- a/app/Model/CategoryModel.php +++ b/app/Model/CategoryModel.php @@ -56,6 +56,18 @@ class CategoryModel extends Base } /** + * 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 * * @access public diff --git a/app/Model/ColumnModel.php b/app/Model/ColumnModel.php index 1adac0f2..795fe692 100644 --- a/app/Model/ColumnModel.php +++ b/app/Model/ColumnModel.php @@ -32,6 +32,18 @@ class ColumnModel extends Base } /** + * 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 * * @access public diff --git a/app/Model/CommentModel.php b/app/Model/CommentModel.php index 36e1fc48..4231f29d 100644 --- a/app/Model/CommentModel.php +++ b/app/Model/CommentModel.php @@ -30,6 +30,22 @@ class CommentModel extends Base 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 * * @access public diff --git a/app/Model/SubtaskModel.php b/app/Model/SubtaskModel.php index 019064ad..a97bddbf 100644 --- a/app/Model/SubtaskModel.php +++ b/app/Model/SubtaskModel.php @@ -52,6 +52,22 @@ class SubtaskModel extends Base 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 * * @access public diff --git a/app/Model/TaskFileModel.php b/app/Model/TaskFileModel.php index 24c1ad4b..7603019a 100644 --- a/app/Model/TaskFileModel.php +++ b/app/Model/TaskFileModel.php @@ -73,6 +73,22 @@ class TaskFileModel extends FileModel } /** + * 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 * * @access public diff --git a/app/Model/TaskLinkModel.php b/app/Model/TaskLinkModel.php index 45225e35..09978eae 100644 --- a/app/Model/TaskLinkModel.php +++ b/app/Model/TaskLinkModel.php @@ -29,6 +29,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 * * @access public diff --git a/app/ServiceProvider/ApiProvider.php b/app/ServiceProvider/ApiProvider.php index 93b3c7f5..f88d9b4f 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\FileApi; -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 FileApi($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 84e4354d..751fe514 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/app/Validator/ProjectValidator.php b/app/Validator/ProjectValidator.php index 9ef59111..8c6117a4 100644 --- a/app/Validator/ProjectValidator.php +++ b/app/Validator/ProjectValidator.php @@ -28,7 +28,7 @@ class ProjectValidator extends BaseValidator new Validators\Integer('priority_start', t('This value must be an integer')), new Validators\Integer('priority_end', t('This value must be an integer')), new Validators\Integer('is_active', t('This value must be an integer')), - new Validators\Required('name', t('The project name is required')), + new Validators\NotEmpty('name', t('This field cannot be empty')), new Validators\MaxLength('name', t('The maximum length is %d characters', 50), 50), new Validators\MaxLength('identifier', t('The maximum length is %d characters', 50), 50), new Validators\MaxLength('start_date', t('The maximum length is %d characters', 10), 10), @@ -47,11 +47,15 @@ class ProjectValidator extends BaseValidator */ public function validateCreation(array $values) { + $rules = array( + new Validators\Required('name', t('The project name is required')), + ); + if (! empty($values['identifier'])) { $values['identifier'] = strtoupper($values['identifier']); } - $v = new Validator($values, $this->commonValidationRules()); + $v = new Validator($values, array_merge($this->commonValidationRules(), $rules)); return array( $v->execute(), |