diff --git a/.codecov.yml b/.codecov.yml index 16577fe9e..eb38c5c5d 100644 --- a/.codecov.yml +++ b/.codecov.yml @@ -12,14 +12,6 @@ coverage: patch: yes changes: no -parsers: - gcov: - branch_detection: - conditional: yes - loop: yes - method: no - macro: no - comment: layout: "header, diff, changes, sunburst, uncovered" behavior: default diff --git a/.travis.yml b/.travis.yml index 987b44c67..36fa82c18 100644 --- a/.travis.yml +++ b/.travis.yml @@ -36,4 +36,4 @@ after_failure: - cat ../../data/nextcloud.log after_success: - - cd build && bash <(curl -s https://codecov.io/bash) -t 49bdd1ee-6ef5-47b9-b80f-825b51515ce9 + - cd build && bash <(curl -s https://codecov.io/bash) -t 49bdd1ee-6ef5-47b9-b80f-825b51515ce9 -x fix diff --git a/lib/AppInfo/Application.php b/lib/AppInfo/Application.php index be4c95f34..6928b3335 100644 --- a/lib/AppInfo/Application.php +++ b/lib/AppInfo/Application.php @@ -40,19 +40,15 @@ class Application extends App { $container = $this->getContainer(); $server = $container->getServer(); - // This is currently unused $container->registerService('SharingMiddleware', function ($container) use ($server) { return new SharingMiddleware( $container, $server->getRequest(), $server->getUserSession(), $container->query('ControllerMethodReflector'), - $container->query('OCP\IGroupManager'), - $container->query('OCA\Deck\Db\AclMapper'), - $container->query('OCA\Deck\Service\BoardService') + $container->query('OCA\Deck\Service\PermissionService') ); }); - /** @noinspection PhpMethodOrClassCallIsNotCaseSensitiveInspection */ $container->registerMiddleware('SharingMiddleware'); } diff --git a/lib/Controller/BoardController.php b/lib/Controller/BoardController.php index 005139cee..59089673a 100644 --- a/lib/Controller/BoardController.php +++ b/lib/Controller/BoardController.php @@ -26,6 +26,7 @@ namespace OCA\Deck\Controller; use OCA\Deck\Db\Acl; use OCA\Deck\Service\BoardService; +use OCA\Deck\Service\PermissionService; use OCP\IRequest; use OCP\AppFramework\Controller; @@ -38,6 +39,7 @@ class BoardController extends Controller { private $boardService; private $userManager; private $groupManager; + private $permissionService; private $userInfo; public function __construct($appName, @@ -45,12 +47,14 @@ class BoardController extends Controller { IUserManager $userManager, IGroupManager $groupManager, BoardService $boardService, + PermissionService $permissionService, $userId) { parent::__construct($appName, $request); $this->userId = $userId; $this->userManager = $userManager; $this->groupManager = $groupManager; $this->boardService = $boardService; + $this->permissionService = $permissionService; $this->userInfo = $this->getBoardPrerequisites(); } @@ -123,6 +127,7 @@ class BoardController extends Controller { * @internal param $userId */ public function getUserPermissions($boardId) { + $this->permissionService->getPermissions($boardId); $board = $this->boardService->find($boardId); if ($this->userId === $board->getOwner()) { return [ diff --git a/lib/Db/Acl.php b/lib/Db/Acl.php index 40becddae..afb55666b 100644 --- a/lib/Db/Acl.php +++ b/lib/Db/Acl.php @@ -50,6 +50,9 @@ class Acl extends Entity implements JsonSerializable { $this->addType('permissionManage', 'boolean'); $this->addType('owner', 'boolean'); $this->addRelation('owner'); + $this->setPermissionWrite(false); + $this->setPermissionInvite(false); + $this->setPermissionManage(false); } public function getPermission($permission) { diff --git a/lib/Db/BoardMapper.php b/lib/Db/BoardMapper.php index a0f66d21d..5438031b3 100644 --- a/lib/Db/BoardMapper.php +++ b/lib/Db/BoardMapper.php @@ -5,20 +5,20 @@ * @author Julius Härtl * * @license GNU AGPL version 3 or any later version - * + * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as * published by the Free Software Foundation, either version 3 of the * License, or (at your option) any later version. - * + * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. - * + * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . - * + * */ namespace OCA\Deck\Db; @@ -28,16 +28,16 @@ use OCP\IDb; class BoardMapper extends DeckMapper implements IPermissionMapper { - private $labelMapper; + private $labelMapper; private $aclMapper; private $stackMapper; - public function __construct(IDb $db, LabelMapper $labelMapper, AclMapper $aclMapper, StackMapper $stackMapper) { - parent::__construct($db, 'deck_boards', '\OCA\Deck\Db\Board'); - $this->labelMapper = $labelMapper; - $this->aclMapper = $aclMapper; + public function __construct(IDb $db, LabelMapper $labelMapper, AclMapper $aclMapper, StackMapper $stackMapper) { + parent::__construct($db, 'deck_boards', '\OCA\Deck\Db\Board'); + $this->labelMapper = $labelMapper; + $this->aclMapper = $aclMapper; $this->stackMapper = $stackMapper; - } + } /** @@ -46,41 +46,46 @@ class BoardMapper extends DeckMapper implements IPermissionMapper { * @param bool $withAcl * @return \OCP\AppFramework\Db\Entity if not found */ - public function find($id, $withLabels=false, $withAcl=false) { - $sql = 'SELECT id, title, owner, color, archived FROM `*PREFIX*deck_boards` ' . - 'WHERE `id` = ?'; - $board = $this->findEntity($sql, [$id]); + public function find($id, $withLabels = false, $withAcl = false) { + $sql = 'SELECT id, title, owner, color, archived FROM `*PREFIX*deck_boards` ' . + 'WHERE `id` = ?'; + $board = $this->findEntity($sql, [$id]); - // Add labels - $labels = $this->labelMapper->findAll($id); - $board->setLabels($labels); + // Add labels + if ($withLabels) { + $labels = $this->labelMapper->findAll($id); + $board->setLabels($labels); + } - // Add acl - $acl = $this->aclMapper->findAll($id); - $board->setAcl($acl); - - return $board; - } + // Add acl + if ($withAcl) { + $acl = $this->aclMapper->findAll($id); + $board->setAcl($acl); + } - /** - * Find all boards for a given user - * @param $userId - * @param null $limit - * @param null $offset - * @return array - */ - public function findAllByUser($userId, $limit=null, $offset=null) { - $sql = 'SELECT id, title, owner, color, archived, 0 as shared FROM oc_deck_boards WHERE owner = ? UNION ' . - 'SELECT boards.id, title, owner, color, archived, 1 as shared FROM oc_deck_boards as boards ' . - 'JOIN oc_deck_board_acl as acl ON boards.id=acl.board_id WHERE acl.participant=? AND acl.type=\'user\' AND boards.owner != ?'; - $entries = $this->findEntities($sql, [$userId, $userId, $userId], $limit, $offset); - /* @var Board $entry */ - foreach ($entries as $entry) { - $acl = $this->aclMapper->findAll($entry->id); - $entry->setAcl($acl); - } - return $entries; - } + return $board; + } + + /** + * Find all boards for a given user + * + * @param $userId + * @param null $limit + * @param null $offset + * @return array + */ + public function findAllByUser($userId, $limit = null, $offset = null) { + $sql = 'SELECT id, title, owner, color, archived, 0 as shared FROM oc_deck_boards WHERE owner = ? UNION ' . + 'SELECT boards.id, title, owner, color, archived, 1 as shared FROM oc_deck_boards as boards ' . + 'JOIN oc_deck_board_acl as acl ON boards.id=acl.board_id WHERE acl.participant=? AND acl.type=\'user\' AND boards.owner != ?'; + $entries = $this->findEntities($sql, [$userId, $userId, $userId], $limit, $offset); + /* @var Board $entry */ + foreach ($entries as $entry) { + $acl = $this->aclMapper->findAll($entry->id); + $entry->setAcl($acl); + } + return $entries; + } /** * Find all boards for a given user @@ -91,31 +96,31 @@ class BoardMapper extends DeckMapper implements IPermissionMapper { * @param null $offset * @return array */ - public function findAllByGroups($userId, $groups, $limit=null, $offset=null) { - if(count($groups)<=0) { - return []; - } - $sql = 'SELECT boards.id, title, owner, color, archived, 2 as shared FROM oc_deck_boards as boards ' . - 'INNER JOIN oc_deck_board_acl as acl ON boards.id=acl.board_id WHERE owner != ? AND type=\'group\' AND ('; - for($i=0;$i1 && $i 1 && $i < count($groups) - 1) { $sql .= ' OR '; } } - $sql .= ');'; - $entries = $this->findEntities($sql, array_merge([$userId], $groups), $limit, $offset); - /* @var Board $entry */ - foreach ($entries as $entry) { - $acl = $this->aclMapper->findAll($entry->id); - $entry->setAcl($acl); - } - return $entries; - } + $sql .= ');'; + $entries = $this->findEntities($sql, array_merge([$userId], $groups), $limit, $offset); + /* @var Board $entry */ + foreach ($entries as $entry) { + $acl = $this->aclMapper->findAll($entry->id); + $entry->setAcl($acl); + } + return $entries; + } - public function delete(/** @noinspection PhpUnnecessaryFullyQualifiedNameInspection */ + public function delete(/** @noinspection PhpUnnecessaryFullyQualifiedNameInspection */ \OCP\AppFramework\Db\Entity $entity) { - // delete acl + // delete acl $acl = $this->aclMapper->findAll($entity->getId()); foreach ($acl as $item) { $this->aclMapper->delete($item); @@ -132,17 +137,17 @@ class BoardMapper extends DeckMapper implements IPermissionMapper { $this->labelMapper->delete($label); } - return parent::delete($entity); - } + return parent::delete($entity); + } - public function isOwner($userId, $boardId) { - $board = $this->find($boardId); - return ($board->getOwner() === $userId); - } + public function isOwner($userId, $boardId) { + $board = $this->find($boardId); + return ($board->getOwner() === $userId); + } - public function findBoardId($id) { - return $id; - } + public function findBoardId($id) { + return $id; + } } \ No newline at end of file diff --git a/lib/Middleware/SharingMiddleware.php b/lib/Middleware/SharingMiddleware.php index 9dc40bb3d..85e866f60 100644 --- a/lib/Middleware/SharingMiddleware.php +++ b/lib/Middleware/SharingMiddleware.php @@ -33,7 +33,8 @@ use OCA\Deck\Db\AclMapper; use OCA\Deck\NoPermissionException; use OCA\Deck\NotFoundException; -use OCA\Deck\Service\BoardService; +use OCA\Deck\Service\PermissionService; +use OCA\Deck\StatusException; use \OCP\AppFramework\Middleware; use OCP\IContainer; use OCP\IGroupManager; @@ -50,9 +51,7 @@ class SharingMiddleware extends Middleware { private $request; private $userSession; private $reflector; - private $groupManager; - private $aclMapper; - private $boardService; + private $permissionService; public function __construct( @@ -60,17 +59,12 @@ class SharingMiddleware extends Middleware { IRequest $request, IUserSession $userSession, ControllerMethodReflector $reflector, - IGroupManager $groupManager, - AclMapper $aclMapper, - BoardService $boardService - ) { + PermissionService $permissionService) { $this->container = $container; $this->request = $request; $this->userSession = $userSession; $this->reflector = $reflector; - $this->aclMapper = $aclMapper; - $this->groupManager = $groupManager; - $this->boardService = $boardService; + $this->permissionService = $permissionService; } /** @@ -91,7 +85,6 @@ class SharingMiddleware extends Middleware { * @throws NoPermissionException */ public function beforeController($controller, $methodName) { - $userId = null; if ($this->userSession->getUser()) { $userId = $this->userSession->getUser()->getUID(); @@ -99,7 +92,25 @@ class SharingMiddleware extends Middleware { $method = $this->request->getMethod(); $params = $this->request->getParams(); $this->checkPermissions($userId, $controller, $method, $params, $methodName); + } + /** + * Return JSON error response if the user has no sufficient permission + * + * @param \OCP\AppFramework\Controller $controller + * @param string $methodName + * @param \Exception $exception + * @return JSONResponse + * @throws \Exception + */ + public function afterException($controller, $methodName, \Exception $exception) { + if ($exception instanceof StatusException) { + return new JSONResponse([ + "status" => $exception->getStatus(), + "message" => $exception->getMessage() + ], $exception->getStatus()); + } + throw $exception; } /** @@ -117,8 +128,7 @@ class SharingMiddleware extends Middleware { private function checkPermissions($userId, $controller, $method, $params, $methodName) { // no permission checks needed for plain html page or RequireNoPermission - if ( - $controller instanceof PageController || + if ($controller instanceof PageController || $this->reflector->hasAnnotation('RequireNoPermission') ) { return true; @@ -167,23 +177,28 @@ class SharingMiddleware extends Middleware { throw new \Exception("No mappers specified for permission checks"); } + $boardId = $mapper->findBoardId($id); + if(!$boardId) { + throw new NotFoundException("Entity not found"); + } + if ($this->reflector->hasAnnotation('RequireReadPermission')) { - if (!$this->checkMapperPermission(Acl::PERMISSION_READ, $userId, $mapper, $id)) { + if (!$this->permissionService->getPermission($boardId, Acl::PERMISSION_READ)) { throw new NoPermissionException("User " . $userId . " has no permission to read.", $controller, $methodName); } } if ($this->reflector->hasAnnotation('RequireEditPermission')) { - if (!$this->checkMapperPermission(Acl::PERMISSION_EDIT, $userId, $mapper, $id)) { + if (!$this->permissionService->getPermission($boardId, Acl::PERMISSION_EDIT)) { throw new NoPermissionException("User " . $userId . " has no permission to edit.", $controller, $methodName); } } if ($this->reflector->hasAnnotation('RequireSharePermission')) { - if (!$this->checkMapperPermission(Acl::PERMISSION_SHARE, $userId, $mapper, $id)) { + if (!$this->permissionService->getPermission($boardId, Acl::PERMISSION_SHARE)) { throw new NoPermissionException("User " . $userId . " has no permission to share.", $controller, $methodName); } } if ($this->reflector->hasAnnotation('RequireManagePermission')) { - if (!$this->checkMapperPermission(Acl::PERMISSION_MANAGE, $userId, $mapper, $id)) { + if (!$this->permissionService->getPermission($boardId, Acl::PERMISSION_MANAGE)) { throw new NoPermissionException("User " . $userId . " has no permission to manage.", $controller, $methodName); } } @@ -192,53 +207,4 @@ class SharingMiddleware extends Middleware { } - /** - * Check if $userId is authorized for $permission on board related to $mapper with $id - * - * @param $permission - * @param $userId - * @param $mapper - * @param $id - * @return bool - * @throws NotFoundException - */ - public function checkMapperPermission($permission, $userId, $mapper, $id) { - // check if current user is owner - if ($mapper->isOwner($userId, $id)) { - return true; - } - // find related board - $boardId = $mapper->findBoardId($id); - if(!$boardId) { - throw new NotFoundException("Entity not found"); - } - return $this->boardService->getPermission($boardId, $userId, $permission); - } - - /** - * Return JSON error response if the user has no sufficient permission - * - * @param \OCP\AppFramework\Controller $controller - * @param string $methodName - * @param \Exception $exception - * @return JSONResponse - * @throws \Exception - */ - public function afterException($controller, $methodName, \Exception $exception) { - if (is_a($exception, '\OCA\Deck\NoPermissionException')) { - return new JSONResponse([ - "status" => 401, - "message" => $exception->getMessage() - ], 401); - } - if (is_a($exception, '\OCA\Deck\NotFoundException')) { - return new JSONResponse([ - "status" => 404, - "message" => $exception->getMessage() - ], 404); - } - throw $exception; - } - - } \ No newline at end of file diff --git a/lib/NoPermissionException.php b/lib/NoPermissionException.php index f48e979fd..bc7abdc36 100644 --- a/lib/NoPermissionException.php +++ b/lib/NoPermissionException.php @@ -24,7 +24,7 @@ namespace OCA\Deck; -class NoPermissionException extends \Exception { +class NoPermissionException extends StatusException { public function __construct($message, $controller=null, $method=null) { parent::__construct($message); @@ -32,4 +32,8 @@ class NoPermissionException extends \Exception { $this->message = get_class($controller) . "#" . $method . ": " . $message; } } + + public function getStatus() { + return 403; + } } \ No newline at end of file diff --git a/lib/NotFoundException.php b/lib/NotFoundException.php index 11a6b12ae..15dc11953 100644 --- a/lib/NotFoundException.php +++ b/lib/NotFoundException.php @@ -24,9 +24,13 @@ namespace OCA\Deck; -class NotFoundException extends \Exception { +class NotFoundException extends StatusException { public function __construct($message="") { parent::__construct($message); } + + public function getStatus() { + return 404; + } } \ No newline at end of file diff --git a/lib/Service/BoardService.php b/lib/Service/BoardService.php index dc8237cf7..56af36c24 100644 --- a/lib/Service/BoardService.php +++ b/lib/Service/BoardService.php @@ -68,7 +68,7 @@ class BoardService { } public function find($boardId) { - $board = $this->boardMapper->find($boardId); + $board = $this->boardMapper->find($boardId, true, true); return $board; } diff --git a/lib/Service/CardService.php b/lib/Service/CardService.php index 27dc81247..6b4bc8af8 100644 --- a/lib/Service/CardService.php +++ b/lib/Service/CardService.php @@ -97,7 +97,7 @@ class CardService { $this->cardMapper->update($card); } // FIXME: return reordered cards without an additional db query - //$cards = $this->cardMapper->findAll($stackId); + $cards = $this->cardMapper->findAll($stackId); return $cards; } diff --git a/lib/Service/PermissionService.php b/lib/Service/PermissionService.php new file mode 100644 index 000000000..307d396a8 --- /dev/null +++ b/lib/Service/PermissionService.php @@ -0,0 +1,123 @@ + + * + * @author Julius Härtl + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +namespace OCA\Deck\Service; + +use OCA\Deck\Db\Acl; +use OCA\Deck\Db\AclMapper; +use \OCA\Deck\Db\BoardMapper; +use OCP\IGroupManager; +use OCP\ILogger; + + + +class PermissionService { + + private $boardMapper; + private $aclMapper; + private $logger; + private $userId; + + public function __construct( + ILogger $logger, + AclMapper $aclMapper, + BoardMapper $boardMapper, + IGroupManager $groupManager, + $userId + ) { + $this->aclMapper = $aclMapper; + $this->boardMapper = $boardMapper; + $this->logger = $logger; + $this->groupManager = $groupManager; + $this->userId = $userId; + } + + /** + * Get current user permissions for a board + * + * @param $boardId + * @return bool|array + */ + public function getPermissions($boardId) { + $owner = $this->userIsBoardOwner($boardId); + $acls = $this->aclMapper->findAll($boardId); + return [ + Acl::PERMISSION_READ => $owner || $this->userCan($acls, Acl::PERMISSION_READ), + Acl::PERMISSION_EDIT => $owner || $this->userCan($acls, Acl::PERMISSION_READ), + Acl::PERMISSION_MANAGE => $owner || $this->userCan($acls, Acl::PERMISSION_MANAGE), + Acl::PERMISSION_SHARE => $owner || $this->userCan($acls, Acl::PERMISSION_SHARE), + ]; + } + + /** + * Check if the current user has specified permissions on a board + * + * @param $boardId + * @param $permission + * @return bool + */ + public function getPermission($boardId, $permission) { + if ($this->userIsBoardOwner($boardId)) { + return true; + } + $acls = $this->aclMapper->findAll($boardId); + return $this->userCan($acls, $permission); + } + + /** + * @param $boardId + * @return bool + */ + public function userIsBoardOwner($boardId) { + $board = $this->boardMapper->find($boardId); + if ($board && $this->userId === $board->getOwner()) { + return true; + } else { + return false; + } + } + + /** + * Check if permission matches the acl rules for current user and groups + * + * @param Acl[] $acls + * @param $permission + * @return bool + */ + public function userCan($acls, $permission) { + // check for users + foreach ($acls as $acl) { + if ($acl->getType() === "user" && $acl->getParticipant() === $this->userId) { + return $acl->getPermission($permission); + } + } + // check for groups + $hasGroupPermission = false; + foreach ($acls as $acl) { + if (!$hasGroupPermission && $acl->getType() === "group" && $this->groupManager->isInGroup($this->userId, $acl->getParticipant())) { + $hasGroupPermission = $acl->getPermission($permission); + } + } + return $hasGroupPermission; + } +} \ No newline at end of file diff --git a/lib/StatusException.php b/lib/StatusException.php new file mode 100644 index 000000000..b74d21b51 --- /dev/null +++ b/lib/StatusException.php @@ -0,0 +1,36 @@ + + * + * @author Julius Härtl + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +namespace OCA\Deck; + + +abstract class StatusException extends \Exception { + + public function __construct($message) { + parent::__construct($message); + } + + public function getStatus() { + return 500; + } +} \ No newline at end of file diff --git a/tests/unit/Middleware/SharingMiddlewareTest.php b/tests/unit/Middleware/SharingMiddlewareTest.php index 38b0e88d0..d5351f3fa 100644 --- a/tests/unit/Middleware/SharingMiddlewareTest.php +++ b/tests/unit/Middleware/SharingMiddlewareTest.php @@ -26,14 +26,18 @@ namespace OCA\Deck\Middleware; use OC\AppFramework\DependencyInjection\DIContainer; use OC\AppFramework\Utility\ControllerMethodReflector; use OC\AppFramework\Utility\SimpleContainer; +use OCA\Deck\Db\DeckMapper; +use OCA\Deck\Db\IPermissionMapper; use OCA\Deck\NoPermissionException; use OCA\Deck\NotFoundException; use OCA\Deck\Service\BoardService; +use OCA\Deck\Service\PermissionService; use OCP\AppFramework\Controller; use OCP\AppFramework\Http\JSONResponse; use OCP\IContainer; use OCP\IGroupManager; use OCP\IRequest; +use OCP\IUser; use OCP\IUserSession; use OCA\Deck\Db\AclMapper; @@ -44,45 +48,88 @@ class SharingMiddlewareTest extends \PHPUnit_Framework_TestCase { private $request; private $userSession; private $reflector; - private $groupManager; - private $aclMapper; - private $boardService; + private $permissionService; public function setUp() { - $this->container = new SimpleContainer(); + $this->container = $this->getMockBuilder(IContainer::class) + ->disableOriginalConstructor()->getMock(); $this->request = $this->getMockBuilder(IRequest::class) ->disableOriginalConstructor()->getMock(); $this->userSession = $this->getMockBuilder(IUserSession::class) ->disableOriginalConstructor()->getMock(); - $this->reflector = $this->getMockBuilder(ControllerMethodReflector::class) - ->disableOriginalConstructor()->getMock(); - $this->groupManager = $this->getMockBuilder(IGroupManager::class) - ->disableOriginalConstructor()->getMock(); - $this->aclMapper = $this->getMockBuilder(AclMapper::class) - ->disableOriginalConstructor()->getMock(); - $this->boardService = $this->getMockBuilder(BoardService::class) + $this->reflector = new ControllerMethodReflector(); + //$this->getMockBuilder(ControllerMethodReflector::class) + // ->disableOriginalConstructor()->getMock(); + $this->permissionService = $this->getMockBuilder(PermissionService::class) ->disableOriginalConstructor()->getMock(); $this->sharingMiddleware = new SharingMiddleware( $this->container, $this->request, $this->userSession, $this->reflector, - $this->groupManager, - $this->aclMapper, - $this->boardService + $this->permissionService ); } - public function testBeforeController() { - $controller = $this->getMockBuilder(Controller::class) + public function dataBeforeController() { + return [ + ['GET', '\OCA\Deck\Controller\PageController', 'index', null, true], + ['GET', '\OCA\Deck\Controller\BoardController', 'index', null, true], + ['GET', '\OCA\Deck\Controller\BoardController', 'read', true, true], + ['GET', '\OCA\Deck\Controller\BoardController', 'read', false, true, NoPermissionException::class], + ['GET', '\OCA\Deck\Controller\CardController', 'read', false, true, NoPermissionException::class], + ['POST', '\OCA\Deck\Controller\CardController', 'reorder', false, true, NoPermissionException::class], + ]; + } + + /** + * @dataProvider dataBeforeController + * @param $controllerClass + * @param $methodName + */ + public function testBeforeController($method, $controllerClass, $methodName, $getPermission, $success, $exception=null) { + $controller = $this->getMockBuilder($controllerClass) ->disableOriginalConstructor()->getMock(); - $methodName = ''; + $mapper = $this->getMockBuilder(IPermissionMapper::class) + ->disableOriginalConstructor()->getMock(); + $mapper->expects($this->any())->method('findBoardId')->willReturn(123); + $mapper->expects($this->any())->method('isOwner')->willReturn(false); + $user = $this->getMockBuilder(IUser::class) + ->disableOriginalConstructor()->getMock(); + $user->expects($this->once())->method('getUID')->willReturn('user1'); + $this->reflector->reflect($controller, $methodName); + + $this->container->expects($this->any()) + ->method('query')->willReturn($mapper); + $this->userSession->expects($this->exactly(2))->method('getUser')->willReturn($user); + $this->request->expects($this->once())->method('getMethod')->willReturn($method); + if($getPermission) { + $this->permissionService->expects($this->any())->method('getPermission')->willReturn($getPermission); + } + + if($success) { + $this->sharingMiddleware->beforeController($controller, $methodName); + } else { + try { + $this->sharingMiddleware->beforeController($controller, $methodName); + } catch (\Exception $e) { + $this->assertInstanceOf($exception, $e); + } + } + + } + + public function setUpPermissions() { + $this->permissionService->expects($this->once()) + ->method('getPermission') + ->with(123, Acl::PERMISSION_READ) + ->willReturn(true); } public function dataAfterException() { return [ - [new NoPermissionException('No permission'), 401, 'No permission'], + [new NoPermissionException('No permission'), 403, 'No permission'], [new NotFoundException('Not found'), 404, 'Not found'] ]; } @@ -96,7 +143,14 @@ class SharingMiddlewareTest extends \PHPUnit_Framework_TestCase { "message" => $message ], $status); $this->assertEquals($expected, $result); + } + public function testAfterExceptionFail() { + try { + $result = $this->sharingMiddleware->afterException('Foo', 'bar', new \Exception('failed hard')); + } catch (\Exception $e) { + $this->assertEquals('failed hard', $e->getMessage()); + } } } \ No newline at end of file diff --git a/tests/unit/Service/PermissionServiceTest.php b/tests/unit/Service/PermissionServiceTest.php new file mode 100644 index 000000000..a87f612a2 --- /dev/null +++ b/tests/unit/Service/PermissionServiceTest.php @@ -0,0 +1,187 @@ + + * + * @author Julius Härtl + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +namespace OCA\Deck\Service; + +use OCA\Deck\Db\Acl; +use OCA\Deck\Db\AclMapper; +use OCA\Deck\Db\Board; +use OCA\Deck\Db\BoardMapper; +use OCP\IGroupManager; +use OCP\ILogger; +use PHPUnit_Framework_TestCase; + +class PermissionServiceTest extends \PHPUnit_Framework_TestCase { + + private $service; + private $logger; + private $aclMapper; + private $boardMapper; + private $groupManager; + private $userId = 'admin'; + + public function setUp() { + $this->logger = $this->request = $this->getMockBuilder(ILogger::class) + ->disableOriginalConstructor() + ->getMock(); + $this->aclMapper = $this->getMockBuilder(AclMapper::class) + ->disableOriginalConstructor()->getMock(); + $this->boardMapper = $this->getMockBuilder(BoardMapper::class) + ->disableOriginalConstructor()->getMock(); + $this->groupManager = $this->getMockBuilder(IGroupManager::class) + ->disableOriginalConstructor()->getMock(); + + $this->service = new PermissionService( + $this->logger, + $this->aclMapper, + $this->boardMapper, + $this->groupManager, + 'admin' + ); + } + + + public function testGetPermissionsOwner() { + $board = new Board(); + $board->setOwner('admin'); + $this->boardMapper->expects($this->once())->method('find')->with(123)->willReturn($board); + $this->aclMapper->expects($this->once()) + ->method('findAll') + ->willReturn(null); + $expected = [ + Acl::PERMISSION_READ => true, + Acl::PERMISSION_EDIT => true, + Acl::PERMISSION_MANAGE => true, + Acl::PERMISSION_SHARE => true, + ]; + $this->assertEquals($expected, $this->service->getPermissions(123)); + } + public function testGetPermissionsAcl() { + $board = new Board(); + $board->setOwner('admin'); + $this->boardMapper->expects($this->once())->method('find')->with(123)->willReturn($board); + $aclUser = new Acl(); + $aclUser->setType('user'); + $aclUser->setParticipant('admin'); + $aclUser->setPermissionWrite(true); + $aclUser->setPermissionInvite(true); + $aclUser->setPermissionManage(true); + $this->aclMapper->expects($this->once()) + ->method('findAll') + ->willReturn([$aclUser]); + $expected = [ + Acl::PERMISSION_READ => true, + Acl::PERMISSION_EDIT => true, + Acl::PERMISSION_MANAGE => true, + Acl::PERMISSION_SHARE => true, + ]; + $this->assertEquals($expected, $this->service->getPermissions(123)); + } + public function testGetPermissionsAclNo() { + $board = new Board(); + $board->setOwner('user1'); + $this->boardMapper->expects($this->once())->method('find')->with(123)->willReturn($board); + $this->aclMapper->expects($this->once()) + ->method('findAll') + ->willReturn([]); + $expected = [ + Acl::PERMISSION_READ => false, + Acl::PERMISSION_EDIT => false, + Acl::PERMISSION_MANAGE => false, + Acl::PERMISSION_SHARE => false, + ]; + $this->assertEquals($expected, $this->service->getPermissions(123)); + } + + public function testGetPermission() { + $board = new Board(); + $board->setOwner('admin'); + $this->boardMapper->expects($this->exactly(4))->method('find')->with(123)->willReturn($board); + $this->assertEquals(true, $this->service->getPermission(123, Acl::PERMISSION_READ)); + $this->assertEquals(true, $this->service->getPermission(123, Acl::PERMISSION_EDIT)); + $this->assertEquals(true, $this->service->getPermission(123, Acl::PERMISSION_MANAGE)); + $this->assertEquals(true, $this->service->getPermission(123, Acl::PERMISSION_SHARE)); + } + + public function testGetPermissionFail() { + $board = new Board(); + $board->setOwner('user1'); + $this->boardMapper->expects($this->exactly(4))->method('find')->with(234)->willReturn($board); + $this->aclMapper->expects($this->exactly(4))->method('findAll')->willReturn([]); + $this->assertEquals(false, $this->service->getPermission(234, Acl::PERMISSION_READ)); + $this->assertEquals(false, $this->service->getPermission(234, Acl::PERMISSION_EDIT)); + $this->assertEquals(false, $this->service->getPermission(234, Acl::PERMISSION_MANAGE)); + $this->assertEquals(false, $this->service->getPermission(234, Acl::PERMISSION_SHARE)); + } + + public function testUserIsBoardOwner() { + $board = new Board(); + $board->setOwner('admin'); + $this->boardMapper->expects($this->at(0))->method('find')->with(123)->willReturn($board); + $this->assertEquals(true, $this->service->userIsBoardOwner(123)); + $board = new Board(); + $board->setOwner('user1'); + $this->boardMapper->expects($this->at(0))->method('find')->with(234)->willReturn($board); + $this->assertEquals(false, $this->service->userIsBoardOwner(234)); + } + + public function testUserIsBoardOwnerNull() { + $this->boardMapper->expects($this->once())->method('find')->willReturn(null); + $this->assertEquals(false, $this->service->userIsBoardOwner(123)); + } + + public function dataTestUserCan() { + return [ + // participant permissions type + ['admin', false, false, false, 'user', true, false, false, false], + ['admin', true, false, false, 'user', true, true, false, false], + ['admin', true, true, false, 'user', true, true, true, false], + ['admin', true, true, false, 'user', true, true, true, false], + ['admin', true, true, true, 'user', true, true, true, true], + ['user1', false, false, false, 'user', false, false, false, false] + ]; + } + /** @dataProvider dataTestUserCan */ + public function testUserCan($participant, $edit, $share, $manage, $type, $canRead, $canEdit, $canShare, $canManage) { + $aclUser = new Acl(); + $aclUser->setType($type); + $aclUser->setParticipant($participant); + $aclUser->setPermissionWrite($edit); + $aclUser->setPermissionInvite($share); + $aclUser->setPermissionManage($manage); + $acls = [ + $aclUser + ]; + $this->assertEquals($canRead, $this->service->userCan($acls, Acl::PERMISSION_READ)); + $this->assertEquals($canEdit, $this->service->userCan($acls, Acl::PERMISSION_EDIT)); + $this->assertEquals($canShare, $this->service->userCan($acls, Acl::PERMISSION_SHARE)); + $this->assertEquals($canManage, $this->service->userCan($acls, Acl::PERMISSION_MANAGE)); + + + } + + public function testUserCanFail() { + $this->assertFalse($this->service->userCan([], Acl::PERMISSION_EDIT)); + } + +} \ No newline at end of file diff --git a/tests/unit/controller/BoardControllerTest.php b/tests/unit/controller/BoardControllerTest.php index dd863c3b3..e0feabd21 100644 --- a/tests/unit/controller/BoardControllerTest.php +++ b/tests/unit/controller/BoardControllerTest.php @@ -32,6 +32,7 @@ class BoardControllerTest extends \PHPUnit_Framework_TestCase { private $userManager; private $groupManager; private $boardService; + private $permissionService; private $userId = 'user'; public function setUp() { @@ -55,6 +56,10 @@ class BoardControllerTest extends \PHPUnit_Framework_TestCase { '\OCA\Deck\Service\BoardService') ->disableOriginalConstructor() ->getMock(); + $this->permissionService = $this->getMockBuilder( + '\OCA\Deck\Service\PermissionService') + ->disableOriginalConstructor() + ->getMock(); $this->groupManager->method('getUserGroupIds') ->willReturn(['admin', 'group1', 'group2']); @@ -68,6 +73,7 @@ class BoardControllerTest extends \PHPUnit_Framework_TestCase { $this->userManager, $this->groupManager, $this->boardService, + $this->permissionService, $this->userId ); }