diff --git a/src/api/controllers/Users.php b/src/api/controllers/Users.php index c157f7e..6ede4a6 100644 --- a/src/api/controllers/Users.php +++ b/src/api/controllers/Users.php @@ -14,15 +14,19 @@ class Users extends BaseController { $userBeans = R::findAll('user'); $userIds = $this->getUserIdsByBoardAccess(Auth::GetUserId($request)); + $actor = new User($this->container, Auth::GetUserId($request)); + $isAdmin = ($actor->security_level->getValue() === SecurityLevel::Admin); + $data = []; foreach($userBeans as $bean) { $user = new User($this->container); $user->loadFromBean($bean); - if (in_array($user->id, $userIds)) { - $this->apiJson->addData($this->cleanUser($user)); + if (in_array($user->id, $userIds) || $isAdmin) { + $data[] = $this->cleanUser($user); } } + $this->apiJson->addData($data); return $this->jsonResponse($response); } @@ -92,13 +96,22 @@ class Users extends BaseController { public function updateUser($request, $response, $args) { $status = $this->secureRoute($request, $response, - SecurityLevel::Admin); + SecurityLevel::User); if ($status !== 200) { return $this->jsonResponse($response, $status); } $user = new User($this->container, (int)$args['id']); $update = new User($this->container); + $actor = new User($this->container, Auth::GetUserId($request)); + + if ($actor->id !== $user->id) { + if ($actor->security_level->getValue() === SecurityLevel::User) { + $this->apiJson->addAlert('error', 'Access restricted.'); + + return $this->jsonResponse($response, 403); + } + } $data = json_decode($request->getBody()); if (isset($data->new_password) && isset($data->old_password)) { @@ -143,7 +156,6 @@ class Users extends BaseController { $update->save(); - $actor = new User($this->container, Auth::GetUserId($request)); $this->dbLogger->logChange($this->container, $actor->id, $actor->username . ' updated user ' . $update->username, json_encode($user), json_encode($update), @@ -157,6 +169,51 @@ class Users extends BaseController { return $this->jsonResponse($response); } + public function updateUserOptions($request, $response, $args) { + $status = $this->secureRoute($request, $response, + SecurityLevel::User); + if ($status !== 200) { + return $this->jsonResponse($response, $status); + } + + $user = new User($this->container, (int)$args['id']); + $actor = new User($this->container, Auth::GetUserId($request)); + + if ($actor->id !== $user->id) { + $this->apiJson->addAlert('error', 'Access restricted.'); + + return $this->jsonResponse($response, 403); + } + + $userOpts = new UserOptions($this->container, $user->user_option_id); + $update = new UserOptions($this->container); + + $data = $request->getBody(); + $update->loadFromJson($data); + + if ($userOpts->id !== $update->id) { + $this->logger->addError('Update User Options: ', + [$userOpts, $update]); + $this->apiJson->addAlert('error', 'Error updating user options. ' . + 'Please check your entries and try again.'); + + return $this->jsonResponse($response); + } + + $update->save(); + + $this->dbLogger->logChange($this->container, $actor->id, + $actor->username . ' updated user options', + json_encode($userOpts), json_encode($update), + 'user_option', $update->id); + + $this->apiJson->setSuccess(); + $this->apiJson->addAlert('success', 'User options updated.'); + $this->apiJson->addData(json_encode($update)); + + return $this->jsonResponse($response); + } + public function removeUser($request, $response, $args) { $status = $this->secureRoute($request, $response, SecurityLevel::Admin); diff --git a/src/api/index.php b/src/api/index.php index 0fc6221..5911d59 100644 --- a/src/api/index.php +++ b/src/api/index.php @@ -46,7 +46,8 @@ $app->delete('/attachments/{id}', 'Attachments:removeAttachment'); // BoardAdmi $app->get ('/users', 'Users:getAllUsers'); // User (by board access) $app->get ('/users/{id}', 'Users:getUser'); // User (by board access) $app->post ('/users', 'Users:addUser'); // Admin -$app->post ('/users/{id}', 'Users:updateUser'); // Admin +$app->post ('/users/{id}', 'Users:updateUser'); // User (limited to self - Higher can edit any) +$app->post ('/users/{id}/opts', 'Users:updateUserOptions'); // User (limited to self) $app->delete('/users/{id}', 'Users:removeUser'); // Admin $app->post ('/login', 'Auth:login'); // Unsecured diff --git a/test/api/controllers/UsersTest.php b/test/api/controllers/UsersTest.php index a82c71c..741329b 100644 --- a/test/api/controllers/UsersTest.php +++ b/test/api/controllers/UsersTest.php @@ -27,7 +27,7 @@ class UsersTest extends PHPUnit_Framework_TestCase { $actual = $this->users->getAllUsers($request, new ResponseMock(), null); - $this->assertEquals(3, count($actual->data)); + $this->assertEquals(2, count($actual->data[1])); $res = DataMock::createUnpriviligedUser(); $this->assertEquals('success', $res->status); @@ -224,6 +224,92 @@ class UsersTest extends PHPUnit_Framework_TestCase { $actual->alerts[2]['text']); } + public function testUpdateUserForbidden() { + $this->createUser(); + + $request = new RequestMock(); + $request->header = [DataMock::getJwt(2)]; + + $args = []; + $args['id'] = 1; + + $response = $this->users->updateUser($request, + new ResponseMock(), $args); + $this->assertEquals('failure', $response->status); + $this->assertEquals('Access restricted.', + $response->alerts[0]['text']); + } + + public function testUpdateUserOptions() { + DataMock::createStandardUser(); + $user = new User(new ContainerMock(), 2); + $opts = new UserOptions(new ContainerMock); + $opts->save(); + $user->user_option_id = $opts->id; + $user->save(); + + $args = []; + $args['id'] = 2; + + $data = DataMock::getUserOptions(); + $data->id = 2; + $data->new_tasks_at_bottom = false; + + $request = new RequestMock(); + $request->payload = $data; + $request->header = [DataMock::getJwt(2)]; + + $response = $this->users->updateUserOptions($request, + new ResponseMock(), $args); + $this->assertEquals('success', $response->status); + + $args['id'] = 1; + $request->header = [DataMock::getJwt(2)]; + + $this->users = new Users(new ContainerMock()); + $response = $this->users->updateUserOptions($request, + new ResponseMock(), $args); + + $this->assertEquals('failure', $response->status); + $this->assertEquals('Access restricted.', + $response->alerts[0]['text']); + } + + public function testUpdateUserOptionsInvalid() { + DataMock::createStandardUser(); + $user = new User(new ContainerMock(), 2); + $opts = new UserOptions(new ContainerMock); + $opts->save(); + $user->user_option_id = $opts->id; + $user->save(); + + $data = DataMock::getUserOptions(); + $data->id = 2; + + DataMock::createUnpriviligedUser(); + + $args = []; + $args['id'] = 2; + + $request = new RequestMock(); + $request->payload = $data; + $request->header = [DataMock::getJwt(3)]; + + $response = $this->users->updateUserOptions($request, + new ResponseMock(), $args); + $this->assertEquals('failure', $response->status); + + $args['id'] = 2; + $data->id = 4; // No such user options + $request->payload = $data; + $request->header = [DataMock::getJwt(2)]; + $this->users = new Users(new ContainerMock()); + + $response = $this->users->updateUserOptions($request, + new ResponseMock(), $args); + $this->assertEquals('failure', $response->status); + } + public function testChangePassword() { $this->createUser();