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/Middleware/SharingMiddleware.php b/lib/Middleware/SharingMiddleware.php index 9dc40bb3d..abd1a11ed 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; } /** @@ -167,23 +178,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 +208,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..4dd7d23f8 100644 --- a/lib/NoPermissionException.php +++ b/lib/NoPermissionException.php @@ -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/PermissionService.php b/lib/Service/PermissionService.php new file mode 100644 index 000000000..e1d293352 --- /dev/null +++ b/lib/Service/PermissionService.php @@ -0,0 +1,122 @@ + + * + * @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; + + 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 ($this->userId === $board->getOwner()) { + return true; + } else { + return false; + } + } + + /** + * Check if permission matches the acl rules for current user and groups + * + * @param $acls + * @param $permission + * @return bool + */ + private 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