Added application and project roles validation for API procedure calls

This commit is contained in:
Frederic Guillot 2016-06-26 10:25:13 -04:00
parent 922e0fb6de
commit 4a230d331e
No known key found for this signature in database
GPG Key ID: 92D77191BA7FBC99
79 changed files with 1772 additions and 761 deletions

View File

@ -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

View File

@ -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));
}
}
}

View File

@ -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));
}
}
}

View File

@ -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));
}
}
}

View File

@ -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));
}
}
}

View File

@ -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');
}
}
}

View File

@ -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');
}
}
}

View File

@ -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));
}
}
}

View File

@ -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));
}
}
}

View File

@ -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));
}
}
}

View File

@ -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));
}
}
}

View File

@ -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');
}
}
}

View File

@ -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);
}
}

View File

@ -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,

View File

@ -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()
{

View File

@ -1,30 +1,25 @@
<?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)
@ -82,4 +77,10 @@ abstract class BaseApi extends Base
return $values;
}
protected function getClassName()
{
$reflection = new ReflectionClass(get_called_class());
return $reflection->getShortName();
}
}

View File

@ -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())

View File

@ -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,

View File

@ -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);
}
}

View File

@ -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,

View File

@ -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)
{

View File

@ -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 = '')
{

View File

@ -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

View File

@ -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()
{

View File

@ -1,55 +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);
}
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);
}
}

View File

@ -0,0 +1,106 @@
<?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 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);
}
}

View File

@ -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,

View File

@ -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);
}
}

View File

@ -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);
}
}

View File

@ -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;
/**
* Task File API controller
*
* @package Kanboard\Api
* @package Kanboard\Api\Procedure
* @author Frederic Guillot
*/
class TaskFileApi 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);
@ -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);
}
}

View File

@ -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);
}
}

View File

@ -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) {

View File

@ -1,6 +1,6 @@
<?php
namespace Kanboard\Api;
namespace Kanboard\Api\Procedure;
use LogicException;
use Kanboard\Core\Security\Role;
@ -11,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 BaseApi
class UserProcedure extends BaseProcedure
{
public function getUser($user_id)
{

View File

@ -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($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);
}
}

View File

@ -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);
}
}

View File

@ -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

View File

@ -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
*

View File

@ -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
*

View File

@ -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
*

View File

@ -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
*

View File

@ -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
*

View File

@ -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
*

View File

@ -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
*

View File

@ -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;
}

View File

@ -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;
}
}

View File

@ -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",

261
composer.lock generated
View File

@ -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": [],

View File

@ -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`.

View File

@ -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

View File

@ -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"
}
```

View File

@ -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**

View File

@ -1,8 +1,8 @@
<?php
require_once __DIR__.'/BaseIntegrationTest.php';
require_once __DIR__.'/BaseProcedureTest.php';
class ActionTest extends BaseIntegrationTest
class ActionProcedureTest extends BaseProcedureTest
{
protected $projectName = 'My project to test actions';

View File

@ -1,8 +1,8 @@
<?php
require_once __DIR__.'/BaseIntegrationTest.php';
require_once __DIR__.'/BaseProcedureTest.php';
class AppTest extends BaseIntegrationTest
class AppProcedureTest extends BaseProcedureTest
{
public function testGetTimezone()
{

View File

@ -2,7 +2,7 @@
require_once __DIR__.'/../../vendor/autoload.php';
abstract class BaseIntegrationTest extends PHPUnit_Framework_TestCase
abstract class BaseProcedureTest extends PHPUnit_Framework_TestCase
{
protected $app = null;
protected $admin = null;

View File

@ -1,8 +1,8 @@
<?php
require_once __DIR__.'/BaseIntegrationTest.php';
require_once __DIR__.'/BaseProcedureTest.php';
class BoardTest extends BaseIntegrationTest
class BoardProcedureTest extends BaseProcedureTest
{
protected $projectName = 'My project to test board';

View File

@ -1,8 +1,8 @@
<?php
require_once __DIR__.'/BaseIntegrationTest.php';
require_once __DIR__.'/BaseProcedureTest.php';
class CategoryTest extends BaseIntegrationTest
class CategoryProcedureTest extends BaseProcedureTest
{
protected $projectName = 'My project to test categories';
private $categoryId = 0;

View File

@ -1,8 +1,8 @@
<?php
require_once __DIR__.'/BaseIntegrationTest.php';
require_once __DIR__.'/BaseProcedureTest.php';
class ColumnTest extends BaseIntegrationTest
class ColumnProcedureTest extends BaseProcedureTest
{
protected $projectName = 'My project to test columns';
private $columns = array();

View File

@ -1,8 +1,8 @@
<?php
require_once __DIR__.'/BaseIntegrationTest.php';
require_once __DIR__.'/BaseProcedureTest.php';
class CommentTest extends BaseIntegrationTest
class CommentProcedureTest extends BaseProcedureTest
{
protected $projectName = 'My project to test comments';
private $commentId = 0;

View File

@ -1,8 +1,8 @@
<?php
require_once __DIR__.'/BaseIntegrationTest.php';
require_once __DIR__.'/BaseProcedureTest.php';
class GroupMemberTest extends BaseIntegrationTest
class GroupMemberProcedureTest extends BaseProcedureTest
{
protected $username = 'user-group-member';
protected $groupName1 = 'My group member A';

View File

@ -1,8 +1,8 @@
<?php
require_once __DIR__.'/BaseIntegrationTest.php';
require_once __DIR__.'/BaseProcedureTest.php';
class GroupTest extends BaseIntegrationTest
class GroupProcedureTest extends BaseProcedureTest
{
public function testAll()
{

View File

@ -1,8 +1,8 @@
<?php
require_once __DIR__.'/BaseIntegrationTest.php';
require_once __DIR__.'/BaseProcedureTest.php';
class LinkTest extends BaseIntegrationTest
class LinkProcedureTest extends BaseProcedureTest
{
public function testGetAllLinks()
{

View File

@ -1,8 +1,8 @@
<?php
require_once __DIR__.'/BaseIntegrationTest.php';
require_once __DIR__.'/BaseProcedureTest.php';
class MeTest extends BaseIntegrationTest
class MeProcedureTest extends BaseProcedureTest
{
protected $projectName = 'My private project';
@ -41,11 +41,6 @@ class MeTest extends BaseIntegrationTest
{
$projects = $this->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()

View File

@ -1,8 +1,8 @@
<?php
require_once __DIR__.'/BaseIntegrationTest.php';
require_once __DIR__.'/BaseProcedureTest.php';
class OverdueTaskTest extends BaseIntegrationTest
class OverdueTaskProcedureTest extends BaseProcedureTest
{
protected $projectName = 'My project to test overdue tasks';

View File

@ -0,0 +1,306 @@
<?php
require_once __DIR__.'/BaseProcedureTest.php';
class ProcedureAuthorizationTest extends BaseProcedureTest
{
public function testApiCredentialDoNotHaveAccessToUserCredentialProcedure()
{
$this->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));
}
}

View File

@ -1,8 +1,8 @@
<?php
require_once __DIR__.'/BaseIntegrationTest.php';
require_once __DIR__.'/BaseProcedureTest.php';
class ProjectPermissionTest extends BaseIntegrationTest
class ProjectPermissionProcedureTest extends BaseProcedureTest
{
protected $projectName = 'Project with permission';
protected $username = 'user-project-permission';

View File

@ -1,8 +1,8 @@
<?php
require_once __DIR__.'/BaseIntegrationTest.php';
require_once __DIR__.'/BaseProcedureTest.php';
class ProjectTest extends BaseIntegrationTest
class ProjectProcedureTest extends BaseProcedureTest
{
protected $projectName = 'My team project';

View File

@ -1,8 +1,8 @@
<?php
require_once __DIR__.'/BaseIntegrationTest.php';
require_once __DIR__.'/BaseProcedureTest.php';
class SubtaskTest extends BaseIntegrationTest
class SubtaskProcedureTest extends BaseProcedureTest
{
protected $projectName = 'My project to test subtasks';
private $subtaskId = 0;

View File

@ -1,8 +1,8 @@
<?php
require_once __DIR__.'/BaseIntegrationTest.php';
require_once __DIR__.'/BaseProcedureTest.php';
class SwimlaneTest extends BaseIntegrationTest
class SwimlaneProcedureTest extends BaseProcedureTest
{
protected $projectName = 'My project to test swimlanes';
private $swimlaneId = 0;

View File

@ -1,8 +1,8 @@
<?php
require_once __DIR__.'/BaseIntegrationTest.php';
require_once __DIR__.'/BaseProcedureTest.php';
class TaskFileTest extends BaseIntegrationTest
class TaskFileProcedureTest extends BaseProcedureTest
{
protected $projectName = 'My project to test task files';
protected $fileId;

View File

@ -1,8 +1,8 @@
<?php
require_once __DIR__.'/BaseIntegrationTest.php';
require_once __DIR__.'/BaseProcedureTest.php';
class TaskLinkTest extends BaseIntegrationTest
class TaskLinkProcedureTest extends BaseProcedureTest
{
protected $projectName = 'My project to test task links';
protected $taskLinkId;

View File

@ -1,8 +1,8 @@
<?php
require_once __DIR__.'/BaseIntegrationTest.php';
require_once __DIR__.'/BaseProcedureTest.php';
class TaskTest extends BaseIntegrationTest
class TaskProcedureTest extends BaseProcedureTest
{
protected $projectName = 'My project to test tasks';

View File

@ -1,8 +1,8 @@
<?php
require_once __DIR__.'/BaseIntegrationTest.php';
require_once __DIR__.'/BaseProcedureTest.php';
class UserTest extends BaseIntegrationTest
class UserProcedureTest extends BaseProcedureTest
{
public function testAll()
{

View File

@ -11,7 +11,7 @@ use Kanboard\Model\CategoryModel;
use Kanboard\Model\ProjectUserRoleModel;
use Kanboard\Core\Security\Role;
class ActionTest extends Base
class ActionModelTest extends Base
{
public function testCreate()
{
@ -69,6 +69,24 @@ class ActionTest extends Base
$this->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);

View File

@ -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);

View File

@ -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));
}
}

View File

@ -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));
}
}

View File

@ -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));
}
}

View File

@ -0,0 +1,211 @@
<?php
require_once __DIR__.'/../Base.php';
use Kanboard\Model\TaskFinderModel;
use Kanboard\Model\TaskLinkModel;
use Kanboard\Model\TaskCreationModel;
use Kanboard\Model\ProjectModel;
class TaskLinkModelTest extends Base
{
// Check postgres issue: "Cardinality violation: 7 ERROR: more than one row returned by a subquery used as an expression"
public function testGetTaskWithMultipleMilestoneLink()
{
$taskFinderModel = new TaskFinderModel($this->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));
}
}

View File

@ -1,196 +0,0 @@
<?php
require_once __DIR__.'/../Base.php';
use Kanboard\Model\TaskFinderModel;
use Kanboard\Model\TaskLinkModel;
use Kanboard\Model\TaskCreationModel;
use Kanboard\Model\ProjectModel;
class TaskLinkTest extends Base
{
// Check postgres issue: "Cardinality violation: 7 ERROR: more than one row returned by a subquery used as an expression"
public function testGetTaskWithMultipleMilestoneLink()
{
$tf = new TaskFinderModel($this->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);
}
}