diff --git a/ci/phpunit/TestBase.php b/ci/phpunit/TestBase.php index 0514b8c6a..f3284af1b 100644 --- a/ci/phpunit/TestBase.php +++ b/ci/phpunit/TestBase.php @@ -9,6 +9,7 @@ use Hashtopolis\dba\models\UserFactory; use Hashtopolis\inc\utils\UserUtils; use PHPUnit\Framework\TestCase; +use Override; require_once(dirname(__FILE__) . '/TestMocks.php'); require_once(dirname(__FILE__) . '/../../src/inc/startup/include.php'); @@ -18,15 +19,21 @@ class TestBase extends TestCase { private array $databaseObjects; protected User $adminUser; + #[Override] protected function setUp(): void { parent::setUp(); $this->databaseObjects = []; $this->adminUser = new User(1, 'admin', 'admin@example.com', 'hash', 'salt', 1, 0, 0, time(), 3600, 1, '', '', '', '', ''); + // Avoid test warnings + $_SERVER['HTTP_HOST'] = $_SERVER['HTTP_HOST'] ?? 'localhost'; + $_SERVER['SERVER_PORT'] = $_SERVER['SERVER_PORT'] ?? 80; + \hashtopolis_clear_test_mocks(); } + #[Override] protected function tearDown(): void { \hashtopolis_clear_test_mocks(); diff --git a/ci/phpunit/inc/utils/AccessControlTest.php b/ci/phpunit/inc/utils/AccessControlTest.php new file mode 100644 index 000000000..32e06fdd4 --- /dev/null +++ b/ci/phpunit/inc/utils/AccessControlTest.php @@ -0,0 +1,231 @@ +resetAccessControlInstance(); + $this->resetLoginInstance(); + } + + protected function tearDown(): void { + parent::tearDown(); + } + + public function testGetInstanceWithoutArgsReusesSameObject(): void { + $first = AccessControl::getInstance(); + $second = AccessControl::getInstance(); + + $this->assertInstanceOf(AccessControl::class, $first); + $this->assertNull($first->getUser()); + + $this->assertSame($first, $second); + } + + public function testGetInstanceWithGroupIdOverwritesInstance(): void { + $first = AccessControl::getInstance(); + $second = AccessControl::getInstance(null, 1); + + $this->assertInstanceOf(AccessControl::class, $first); + $this->assertNull($first->getUser()); + + $this->assertInstanceOf(AccessControl::class, $second); + $this->assertNull($second->getUser()); + + $this->assertNotSame($first, $second); + } + + public function testGetInstanceWithUserOverwritesInstance(): void { + $first = AccessControl::getInstance(); + $second = AccessControl::getInstance($this->adminUser); + + $this->assertInstanceOf(AccessControl::class, $first); + $this->assertNull($first->getUser()); + + $this->assertInstanceOf(AccessControl::class, $second); + $this->assertEquals($this->adminUser, $second->getUser()); + + $this->assertNotSame($first, $second); + } + + public function testReloadReloadsTheRightsGroupForUser(): void { + $group = $this->createDatabaseObject( + Factory::getRightGroupFactory(), + new RightGroup(null, 'phpunit-' . uniqid('', true), '{}') + ); + + $user = $this->createDatabaseObject( + Factory::getUserFactory(), + new User(null, 'phpunit_' . uniqid(), 'phpunit_' . uniqid() . '@example.com', 'hash', 'salt', 1, 0, 0, time(), 3600, $group->getId(), '', '', '', '', '') + ); + + $accessControl = AccessControl::getInstance($user); + $this->assertFalse($accessControl->hasPermission(DAccessControl::MANAGE_TASK_ACCESS)); + Factory::getRightGroupFactory()->set( + $group, + RightGroup::PERMISSIONS, + json_encode([DAccessControl::MANAGE_TASK_ACCESS => true]) + ); + $this->assertFalse($accessControl->hasPermission(DAccessControl::MANAGE_TASK_ACCESS)); + + $accessControl->reload(); + $this->assertTrue($accessControl->hasPermission(DAccessControl::MANAGE_TASK_ACCESS)); + } + + public function testReloadDoesNotReloadTheRightsGroupWithoutUser(): void { + $group = $this->createDatabaseObject( + Factory::getRightGroupFactory(), + new RightGroup(null, 'phpunit-' . uniqid('', true), '{}') + ); + + $accessControl = AccessControl::getInstance(null, $group->getId()); + $this->assertFalse($accessControl->hasPermission(DAccessControl::MANAGE_TASK_ACCESS)); + + Factory::getRightGroupFactory()->set( + $group, + RightGroup::PERMISSIONS, + json_encode([DAccessControl::MANAGE_TASK_ACCESS => true]) + ); + + $this->assertFalse($accessControl->hasPermission(DAccessControl::MANAGE_TASK_ACCESS)); + + $accessControl->reload(); + + // TODO: Check if this is the desired behavour, ie not reloading if a groupId only. + $this->assertFalse($accessControl->hasPermission(DAccessControl::MANAGE_TASK_ACCESS)); + } + + public function testPermissionPublicAccessAlwaysPermit(): void { + $accessControl = AccessControl::getInstance(); + $this->assertTrue($accessControl->hasPermission(DAccessControl::PUBLIC_ACCESS)); + } + + public function testPermissionLoginWithLoggedInUserPermit(): void { + $this->setLoginState(true, $this->adminUser); + $accessControl = AccessControl::getInstance(); + $this->assertTrue($accessControl->hasPermission(DAccessControl::LOGIN_ACCESS)); + } + + public function testPermissionLoginWithoutLoggedInUserDenies(): void { + $accessControl = AccessControl::getInstance(); + $this->assertFalse($accessControl->hasPermission(DAccessControl::LOGIN_ACCESS)); + } + + public function testUninitializedAccessControlDenies() { + $accessControl = AccessControl::getInstance(); + foreach(DAccessControl::getConstants() as $constant) { + $permission = is_array($constant) ? $constant[0] : $constant; + if ($permission != DAccessControl::PUBLIC_ACCESS) { + $this->assertFalse($accessControl->hasPermission($permission)); + } + } + } + + public function testRegularUserWithPermissionPermits(): void { + $group = $this->createDatabaseObject( + Factory::getRightGroupFactory(), + new RightGroup(null, 'phpunit-' . uniqid('', true), json_encode([DAccessControl::MANAGE_TASK_ACCESS => true])) + ); + + $user = $this->createDatabaseObject( + Factory::getUserFactory(), + new User(null, 'phpunit_' . uniqid(), 'phpunit_' . uniqid() . '@example.com', 'hash', 'salt', 1, 0, 0, time(), 3600, $group->getId(), '', '', '', '', '') + ); + + $accessControl = AccessControl::getInstance($user); + $this->assertTrue($accessControl->hasPermission(DAccessControl::MANAGE_TASK_ACCESS)); + } + + public function testRegularUserWithoutPermissionDenies(): void { + $group = $this->createDatabaseObject( + Factory::getRightGroupFactory(), + new RightGroup(null, 'phpunit-' . uniqid('', true), json_encode([DAccessControl::VIEW_HASHES_ACCESS => true])) + ); + + $user = $this->createDatabaseObject( + Factory::getUserFactory(), + new User(null, 'phpunit_' . uniqid(), 'phpunit_' . uniqid() . '@example.com', 'hash', 'salt', 1, 0, 0, time(), 3600, $group->getId(), '', '', '', '', '') + ); + + $accessControl = AccessControl::getInstance($user); + $this->assertFalse($accessControl->hasPermission(DAccessControl::MANAGE_TASK_ACCESS)); + } + + public function testALLUserPermissionPermitsAllPermissions(): void { + $accessControl = AccessControl::getInstance($this->adminUser); + foreach(DAccessControl::getConstants() as $constant) { + $permission = is_array($constant) ? $constant[0] : $constant; + $this->assertTrue($accessControl->hasPermission($permission)); + } + } + + public function testGivenByDependencyImplied(): void { + $group = $this->createDatabaseObject( + Factory::getRightGroupFactory(), + new RightGroup(null, 'phpunit-' . uniqid('', true), json_encode([DAccessControl::MANAGE_AGENT_ACCESS => true])) + ); + + $accessControl = AccessControl::getInstance(null, $group->getId()); + + $this->assertTrue($accessControl->givenByDependency(DAccessControl::VIEW_AGENT_ACCESS[0])); + } + + public function testGivenByDependencyDirect(): void { + $group = $this->createDatabaseObject( + Factory::getRightGroupFactory(), + new RightGroup( + null, + 'phpunit-' . uniqid('', true), + json_encode([DAccessControl::MANAGE_TASK_ACCESS => true]) + ) + ); + + $accessControl = AccessControl::getInstance(null, $group->getId()); + + $this->assertTrue($accessControl->givenByDependency(DAccessControl::MANAGE_TASK_ACCESS)); + } + + /* + Local test helpers + */ + private function resetAccessControlInstance(): void { + $reflection = new ReflectionClass(AccessControl::class); + $instanceProperty = $reflection->getProperty('instance'); + $instanceProperty->setValue(null, null); + } + + private function setLoginState(bool $valid, ?User $user = null): void { + $reflection = new ReflectionClass(\Hashtopolis\inc\Login::class); + $instanceProperty = $reflection->getProperty('instance'); + $instance = $instanceProperty->getValue(); + + if ($instance === null) { + \Hashtopolis\inc\Login::getInstance(); + $instance = $instanceProperty->getValue(); + } + + $validProperty = $reflection->getProperty('valid'); + $validProperty->setValue($instance, $valid); + + $userProperty = $reflection->getProperty('user'); + $userProperty->setValue($instance, $user); + } + + private function resetLoginInstance(): void { + $reflection = new ReflectionClass(\Hashtopolis\inc\Login::class); + $instanceProperty = $reflection->getProperty('instance'); + $instanceProperty->setValue(null, null); + } +} \ No newline at end of file diff --git a/ci/phpunit/inc/utils/AccessControlUtilsTest.php b/ci/phpunit/inc/utils/AccessControlUtilsTest.php new file mode 100644 index 000000000..13a362682 --- /dev/null +++ b/ci/phpunit/inc/utils/AccessControlUtilsTest.php @@ -0,0 +1,266 @@ +group = $this->createDatabaseObject( + Factory::getRightGroupFactory(), + new RightGroup(null, 'phpunit-' . uniqid('', true), '[]') + ); + + $this->otherGroup = $this->createDatabaseObject( + Factory::getRightGroupFactory(), + new RightGroup(null, 'phpunit-' . uniqid('', true), '[]') + ); + } + + #[Override] + protected function tearDown(): void{ + parent::tearDown(); + } + + + + public function testGetMembersOfGroupReturnsOnlyMembersOfGroup(): void { + $firstMember = $this->createDatabaseObject( + Factory::getUserFactory(), + new User(null, 'phpunit_' . uniqid(), 'phpunit_' . uniqid() . '@example.com', 'hash', 'salt', 1, 0, 0, time(), 3600, $this->group->getId(), '', '', '', '', '') + ); + + $secondMember = $this->createDatabaseObject( + Factory::getUserFactory(), + new User(null, 'phpunit_' . uniqid(), 'phpunit_' . uniqid() . '@example.com', 'hash', 'salt', 1, 0, 0, time(), 3600, $this->group->getId(), '', '', '', '', '') + ); + + $otherMember = $this->createDatabaseObject( + Factory::getUserFactory(), + new User(null, 'phpunit_' . uniqid(), 'phpunit_' . uniqid() . '@example.com', 'hash', 'salt', 1, 0, 0, time(), 3600, $this->otherGroup->getId(), '', '', '', '', '') + ); + + $members = AccessControlUtils::getMembers($this->group->getId()); + $memberIds = array_map(static fn (User $user): int => $user->getId(), $members); + + $this->assertCount(2, $members); + $this->assertContains($firstMember->getId(), $memberIds); + $this->assertContains($secondMember->getId(), $memberIds); + $this->assertNotContains($otherMember->getId(), $memberIds); + } + + public function testThatAdminGroupPermissionCanNotBeAltered(): void { + $this->expectException(HTException::class); + AccessControlUtils::addToPermissions( + $this->adminUser->getRightGroupId(), + [DAccessControl::MANAGE_TASK_ACCESS => true] + ); + } + + public function testAddPermissionsToNonExistentGroup(): void { + $this->expectException(HTException::class); + AccessControlUtils::addToPermissions( + -3, + [DAccessControl::MANAGE_TASK_ACCESS => true] + ); + } + + public function testGetGroupLoadsExistingGroup(): void { + $loadedGroup = AccessControlUtils::getGroup($this->group->getId()); + + $this->assertInstanceOf(RightGroup::class, $loadedGroup); + $this->assertSame($this->group->getId(), $loadedGroup->getId()); + $this->assertSame($this->group->getGroupName(), $loadedGroup->getGroupName()); + } + + public function testGetGroupThrowsForNonExistentGroup(): void { + $this->expectException(HTException::class); + AccessControlUtils::getGroup(-3); + } + + public function testAddToPermissionThrowsOnNonExistentGroup(): void { + $this->expectException(HTException::class); + AccessControlUtils::addToPermissions( + -3, + [DAccessControl::MANAGE_TASK_ACCESS => true], + ); + } + + public function testAddPermissionToGroup(): void { + AccessControlUtils::addToPermissions( + $this->group->getId(), + [DAccessControl::MANAGE_TASK_ACCESS => true], + ); + + $updatedGroup = Factory::getRightGroupFactory()->get($this->group->getId()); + $permissions = json_decode($updatedGroup->getPermissions(), true); + + $this->assertIsArray($permissions); + $this->assertArrayHasKey(DAccessControl::MANAGE_TASK_ACCESS, $permissions); + $this->assertTrue($permissions[DAccessControl::MANAGE_TASK_ACCESS]); + } + + //Note: We do not enforce what to write in the permissions + public function testAddNonExistentPermissionToGroup(): void { + $nonexistentPermission = "nonexistent"; + AccessControlUtils::addToPermissions( + $this->group->getId(), + [$nonexistentPermission => true], + ); + + $updatedGroup = Factory::getRightGroupFactory()->get($this->group->getId()); + $permissions = json_decode($updatedGroup->getPermissions(), true); + + $this->assertIsArray($permissions); + $this->assertArrayHasKey($nonexistentPermission, $permissions); + } + + public function testUpdateNonexistentGroupThrowsException() { + $this->expectException(HTException::class); + AccessControlUtils::updateGroupPermissions( + -3, + [DAccessControl::CRACKER_BINARY_ACCESS => true], + ); + } + + public function testUpdateAdminPermissionsIsNotAllowed(): void { + $this->expectException(HTException::class); + AccessControlUtils::updateGroupPermissions( + $this->adminUser->getRightGroupId(), + [DAccessControl::CRACKER_BINARY_ACCESS => true], + ); + } + + public function testUpdatePermission() { + $changed = AccessControlUtils::updateGroupPermissions( + $this->group->getId(), + [DAccessControl::MANAGE_TASK_ACCESS . '-1'] + ); + + $updatedGroup = Factory::getRightGroupFactory()->get($this->group->getId()); + $permissions = json_decode($updatedGroup->getPermissions(), true); + + $this->assertFalse($changed); + $this->assertIsArray($permissions); + $this->assertArrayHasKey(DAccessControl::MANAGE_TASK_ACCESS, $permissions); + $this->assertTrue($permissions[DAccessControl::MANAGE_TASK_ACCESS]); + } + + public function testUpdatePermissionIgnoresValidPermissionWithInvalidInteger(): void { + $changed = AccessControlUtils::updateGroupPermissions( + $this->group->getId(), + [DAccessControl::MANAGE_TASK_ACCESS . '-2'] + ); + + $updatedGroup = Factory::getRightGroupFactory()->get($this->group->getId()); + $permissions = json_decode($updatedGroup->getPermissions(), true); + + $this->assertFalse($changed); + $this->assertSame([], $permissions); + } + + public function testUpdatePermissionIgnoresInvalidPermissionWithValidInteger(): void { + $changed = AccessControlUtils::updateGroupPermissions( + $this->group->getId(), + ['nonexistentPermission-1'] + ); + + $updatedGroup = Factory::getRightGroupFactory()->get($this->group->getId()); + $permissions = json_decode($updatedGroup->getPermissions(), true); + + $this->assertFalse($changed); + $this->assertSame([], $permissions); + } + + public function testUpdatePermissionAppliesDependencyOverride(): void { + $changed = AccessControlUtils::updateGroupPermissions( + $this->group->getId(), + [ + DAccessControl::MANAGE_AGENT_ACCESS . '-1', + DAccessControl::VIEW_AGENT_ACCESS[0] . '-0' + ] + ); + + $updatedGroup = Factory::getRightGroupFactory()->get($this->group->getId()); + $permissions = json_decode($updatedGroup->getPermissions(), true); + + $this->assertTrue($changed); + $this->assertIsArray($permissions); + $this->assertArrayHasKey(DAccessControl::MANAGE_AGENT_ACCESS, $permissions); + $this->assertTrue($permissions[DAccessControl::MANAGE_AGENT_ACCESS]); + $this->assertArrayHasKey(DAccessControl::VIEW_AGENT_ACCESS[0], $permissions); + $this->assertTrue($permissions[DAccessControl::VIEW_AGENT_ACCESS[0]]); + } + + public function testCreateGroupThrowsForEmptyName(): void { + $this->expectException(HttpError::class); + + AccessControlUtils::createGroup(''); + } + + public function testCreateGroupThrowsForNameLongerThanMaxLength(): void { + $this->expectException(HttpError::class); + + AccessControlUtils::createGroup(str_repeat('a', DLimits::ACCESS_GROUP_MAX_LENGTH + 1)); + } + + public function testCreateGroupAllowsNameAtMaxLength(): void { + $group = AccessControlUtils::createGroup(str_repeat('a', DLimits::ACCESS_GROUP_MAX_LENGTH)); + $this->registerDatabaseObject(Factory::getRightGroupFactory(), $group); + + $this->assertInstanceOf(RightGroup::class, $group); + $this->assertSame(DLimits::ACCESS_GROUP_MAX_LENGTH, strlen($group->getGroupName())); + } + + public function testCreateGroupThrowsForExistingGroupName(): void { + $this->expectException(HttpConflict::class); + + AccessControlUtils::createGroup($this->group->getGroupName()); + } + + public function testDeleteGroupThrowsForNonExistentGroup(): void { + $this->expectException(HTException::class); + + AccessControlUtils::deleteGroup(-3); + } + + public function testDeleteGroupThrowsWhenGroupHasUsers(): void { + $this->createDatabaseObject( + Factory::getUserFactory(), + new User(null, 'phpunit_' . uniqid(), 'phpunit_' . uniqid() . '@example.com', 'hash', 'salt', 1, 0, 0, time(), 3600, $this->group->getId(), '', '', '', '', '') + ); + + $this->expectException(HttpError::class); + + AccessControlUtils::deleteGroup($this->group->getId()); + } + + public function testDeleteGroupDeletesEmptyGroup(): void { + $groupId = $this->group->getId(); + + AccessControlUtils::deleteGroup($groupId); + + $this->expectException(HTException::class); + AccessControlUtils::getGroup($groupId); + } + +} \ No newline at end of file diff --git a/ci/phpunit/inc/utils/AccessGroupUtilsTest.php b/ci/phpunit/inc/utils/AccessGroupUtilsTest.php new file mode 100644 index 000000000..b479f2a08 --- /dev/null +++ b/ci/phpunit/inc/utils/AccessGroupUtilsTest.php @@ -0,0 +1,402 @@ +firstGroup = $this->createAccessGroup('group_one'); + $this->secondGroup = $this->createAccessGroup('group_two'); + $this->firstUser = $this->createUser('user_one'); + $this->secondUser = $this->createUser('user_two'); + $this->firstAgent = $this->createAgent('agent_one'); + $this->secondAgent = $this->createAgent('agent_two'); + + $this->createDatabaseObject( + Factory::getAccessGroupUserFactory(), + new AccessGroupUser(null, $this->firstGroup->getId(), $this->firstUser->getId()) + ); + $this->createDatabaseObject( + Factory::getAccessGroupUserFactory(), + new AccessGroupUser(null, $this->secondGroup->getId(), $this->secondUser->getId()) + ); + $this->createDatabaseObject( + Factory::getAccessGroupAgentFactory(), + new AccessGroupAgent(null, $this->firstGroup->getId(), $this->firstAgent->getId()) + ); + $this->createDatabaseObject( + Factory::getAccessGroupAgentFactory(), + new AccessGroupAgent(null, $this->secondGroup->getId(), $this->secondAgent->getId()) + ); + } + + #[Override] + protected function tearDown(): void { + parent::tearDown(); + } + + public function testGetUsersReturnsUsersAssignedToRequestedGroup(): void { + $users = AccessGroupUtils::getUsers($this->firstGroup->getId()); + + $this->assertCount(1, $users); + $this->assertSame($this->firstGroup->getId(), $users[0]->getAccessGroupId()); + $this->assertSame($this->firstUser->getId(), $users[0]->getUserId()); + } + + public function testGetAgentsReturnsAgentsAssignedToRequestedGroup(): void { + $agents = AccessGroupUtils::getAgents($this->firstGroup->getId()); + + $this->assertCount(1, $agents); + $this->assertSame($this->firstGroup->getId(), $agents[0]->getAccessGroupId()); + $this->assertSame($this->firstAgent->getId(), $agents[0]->getAgentId()); + } + + public function testGetGroupsReturnsAtLeastCreatedGroups(): void { + $groups = AccessGroupUtils::getGroups(); + $groupIds = array_map( + fn (AccessGroup $group) => $group->getId(), + $groups + ); + + $this->assertContains($this->firstGroup->getId(), $groupIds); + $this->assertContains($this->secondGroup->getId(), $groupIds); + } + + public function testCreateGroupThrowsForEmptyName(): void { + $this->expectException(HttpError::class); + AccessGroupUtils::createGroup(''); + } + + public function testCreateGroupThrowsForNameLongerThanMaxLength(): void { + $this->expectException(HttpError::class); + AccessGroupUtils::createGroup(str_repeat('a', DLimits::ACCESS_GROUP_MAX_LENGTH + 1)); + } + + public function testCreateGroupThrowsForExistingGroupName(): void { + $this->expectException(HttpConflict::class); + AccessGroupUtils::createGroup($this->firstGroup->getGroupName()); + } + + public function testCreateGroupCreatesGroupWithValidUniqueName(): void { + $groupName = 'created_group_' . uniqid(); + + $group = AccessGroupUtils::createGroup($groupName); + $this->registerDatabaseObject(Factory::getAccessGroupFactory(), $group); + + $this->assertInstanceOf(AccessGroup::class, $group); + $this->assertSame($groupName, $group->getGroupName()); + $this->assertNotNull($group->getId()); + $this->assertSame($groupName, Factory::getAccessGroupFactory()->get($group->getId())->getGroupName()); + } + + public function testRenameThrowsForNonExistentGroup(): void { + $this->expectException(HTException::class); + AccessGroupUtils::rename(-1, 'renamed_group'); + } + + public function testRenameThrowsForEmptyName(): void { + $this->expectException(HTException::class); + AccessGroupUtils::rename($this->firstGroup->getId(), ''); + } + + public function testAbortChunksGroupThrowsForNonExistentGroup(): void { + $this->expectException(HTException::class); + AccessGroupUtils::abortChunksGroup(-1, $this->firstUser); + } + + public function testAbortChunksGroupOnlyAbortsInitAndRunningChunks(): void { + $hashType = $this->createHashType(); + $hashlist = $this->createHashlist($this->firstGroup, $hashType); + $crackerBinaryType = $this->createCrackerBinaryType(); + $crackerBinary = $this->createCrackerBinary($crackerBinaryType); + $taskWrapper = $this->createTaskWrapper($this->firstGroup, $hashlist); + $task = $this->createTask($taskWrapper, $crackerBinary, $crackerBinaryType); + $statusCases = [ + DHashcatStatus::INIT => DHashcatStatus::ABORTED, + DHashcatStatus::AUTOTUNE => DHashcatStatus::AUTOTUNE, + DHashcatStatus::RUNNING => DHashcatStatus::ABORTED, + DHashcatStatus::PAUSED => DHashcatStatus::PAUSED, + DHashcatStatus::EXHAUSTED => DHashcatStatus::EXHAUSTED, + DHashcatStatus::CRACKED => DHashcatStatus::CRACKED, + DHashcatStatus::ABORTED => DHashcatStatus::ABORTED, + DHashcatStatus::QUIT => DHashcatStatus::QUIT, + DHashcatStatus::BYPASS => DHashcatStatus::BYPASS, + DHashcatStatus::ABORTED_CHECKPOINT => DHashcatStatus::ABORTED_CHECKPOINT, + DHashcatStatus::STATUS_ABORTED_RUNTIME => DHashcatStatus::STATUS_ABORTED_RUNTIME, + ]; + + + $chunksByState = []; + foreach ($statusCases as $initialState => $expectedState) { + $chunksByState[$initialState] = $this->createChunk($task, $this->firstAgent, $initialState); + } + + AccessGroupUtils::abortChunksGroup($this->firstGroup->getId(), $this->firstUser); + + foreach ($statusCases as $initialState => $expectedState) { + $updatedChunk = Factory::getChunkFactory()->get($chunksByState[$initialState]->getId()); + + $this->assertInstanceOf(Chunk::class, $updatedChunk); + $this->assertSame($expectedState, $updatedChunk->getState()); + } + } + + public function testAddAgentAddsAgentToGroup(): void { + AccessGroupUtils::addAgent($this->secondAgent->getId(), $this->firstGroup->getId()); + + $qF1 = new QueryFilter(AccessGroupAgent::ACCESS_GROUP_ID, $this->firstGroup->getId(), '='); + $qF2 = new QueryFilter(AccessGroupAgent::AGENT_ID, $this->secondAgent->getId(), '='); + $addedMembership = Factory::getAccessGroupAgentFactory()->filter([Factory::FILTER => [$qF1, $qF2]], true); + + $this->assertInstanceOf(AccessGroupAgent::class, $addedMembership); + $this->assertSame($this->firstGroup->getId(), $addedMembership->getAccessGroupId()); + $this->assertSame($this->secondAgent->getId(), $addedMembership->getAgentId()); + $this->registerDatabaseObject(Factory::getAccessGroupAgentFactory(), $addedMembership); + } + + public function testAddAgentThrowsWhenAgentAlreadyInGroup(): void { + $this->expectException(HTException::class); + AccessGroupUtils::addAgent($this->firstAgent->getId(), $this->firstGroup->getId()); + } + + public function testAddUserAddsUserToGroup(): void { + AccessGroupUtils::addUser($this->secondUser->getId(), $this->firstGroup->getId()); + + $qF1 = new QueryFilter(AccessGroupUser::ACCESS_GROUP_ID, $this->firstGroup->getId(), '='); + $qF2 = new QueryFilter(AccessGroupUser::USER_ID, $this->secondUser->getId(), '='); + $addedMembership = Factory::getAccessGroupUserFactory()->filter([Factory::FILTER => [$qF1, $qF2]], true); + + $this->assertInstanceOf(AccessGroupUser::class, $addedMembership); + $this->assertSame($this->firstGroup->getId(), $addedMembership->getAccessGroupId()); + $this->assertSame($this->secondUser->getId(), $addedMembership->getUserId()); + $this->registerDatabaseObject(Factory::getAccessGroupUserFactory(), $addedMembership); + } + + public function testAddUserThrowsWhenUserAlreadyInGroup(): void { + $this->expectException(HTException::class); + AccessGroupUtils::addUser($this->firstUser->getId(), $this->firstGroup->getId()); + } + + public function testRemoveAgentRemovesAgentFromGroup(): void { + AccessGroupUtils::removeAgent($this->firstAgent->getId(), $this->firstGroup->getId()); + + $qF1 = new QueryFilter(AccessGroupAgent::ACCESS_GROUP_ID, $this->firstGroup->getId(), '='); + $qF2 = new QueryFilter(AccessGroupAgent::AGENT_ID, $this->firstAgent->getId(), '='); + $removedMembership = Factory::getAccessGroupAgentFactory()->filter([Factory::FILTER => [$qF1, $qF2]], true); + + $this->assertNull($removedMembership); + } + + public function testRemoveAgentThrowsWhenAgentIsNotInGroup(): void { + $this->expectException(HTException::class); + AccessGroupUtils::removeAgent($this->secondAgent->getId(), $this->firstGroup->getId()); + } + + public function testRemoveUserRemovesUserFromGroup(): void { + AccessGroupUtils::removeUser($this->firstUser->getId(), $this->firstGroup->getId()); + + $qF1 = new QueryFilter(AccessGroupUser::ACCESS_GROUP_ID, $this->firstGroup->getId(), '='); + $qF2 = new QueryFilter(AccessGroupUser::USER_ID, $this->firstUser->getId(), '='); + $removedMembership = Factory::getAccessGroupUserFactory()->filter([Factory::FILTER => [$qF1, $qF2]], true); + + $this->assertNull($removedMembership); + } + + public function testRemoveUserThrowsWhenUserIsNotInGroup(): void { + $this->expectException(HTException::class); + AccessGroupUtils::removeUser($this->secondUser->getId(), $this->firstGroup->getId()); + } + + public function testDeleteGroupThrowsForDefaultGroup(): void { + $defaultGroup = AccessUtils::getOrCreateDefaultAccessGroup(); + + $this->expectException(HTException::class); + AccessGroupUtils::deleteGroup($defaultGroup->getId()); + } + + public function testDeleteGroupReassignsDependentEntitiesToDefaultGroup(): void { + $defaultGroup = AccessUtils::getOrCreateDefaultAccessGroup(); + $groupToDelete = $this->createAccessGroup('delete_group_'); + + $this->createDatabaseObject( + Factory::getAccessGroupUserFactory(), + new AccessGroupUser(null, $groupToDelete->getId(), $this->firstUser->getId()), + ); + $this->createDatabaseObject( + Factory::getAccessGroupAgentFactory(), + new AccessGroupAgent(null, $groupToDelete->getId(), $this->firstAgent->getId()), + ); + + $hashType = $this->createHashType(); + $hashlist = $this->createHashlist($groupToDelete, $hashType); + $taskWrapper = $this->createTaskWrapper($groupToDelete, $hashlist); + $file = $this->createFile($groupToDelete); + + AccessGroupUtils::deleteGroup($groupToDelete->getId()); + + $updatedHashlist = Factory::getHashlistFactory()->get($hashlist->getId()); + $updatedTaskWrapper = Factory::getTaskWrapperFactory()->get($taskWrapper->getId()); + $updatedFile = Factory::getFileFactory()->get($file->getId()); + $deletedGroup = Factory::getAccessGroupFactory()->get($groupToDelete->getId()); + $remainingUsers = AccessGroupUtils::getUsers($groupToDelete->getId()); + $remainingAgents = AccessGroupUtils::getAgents($groupToDelete->getId()); + + $this->assertInstanceOf(Hashlist::class, $updatedHashlist); + $this->assertSame($defaultGroup->getId(), $updatedHashlist->getAccessGroupId()); + $this->assertInstanceOf(TaskWrapper::class, $updatedTaskWrapper); + $this->assertSame($defaultGroup->getId(), $updatedTaskWrapper->getAccessGroupId()); + $this->assertInstanceOf(File::class, $updatedFile); + $this->assertSame($defaultGroup->getId(), $updatedFile->getAccessGroupId()); + $this->assertNull($deletedGroup); + $this->assertSame([], $remainingUsers); + $this->assertSame([], $remainingAgents); + } + + /* + * Local test helpers + */ + private function createAccessGroup(string $prefix): AccessGroup { + $group = $this->createDatabaseObject( + Factory::getAccessGroupFactory(), + new AccessGroup(null, $prefix . '_' . uniqid()) + ); + $this->assertTrue($group instanceof AccessGroup); + return $group; + } + + private function createAgent(string $prefix): Agent { + $suffix = uniqid('', true); + $agent = $this->createDatabaseObject( + Factory::getAgentFactory(), + new Agent(null, $prefix . '_' . $suffix, 'uid_' . $suffix, 0, '[]', '', 0, 1, 1, 'token_' . uniqid(), 'idle', time(), '127.0.0.1', null, 0, 'sig_' . uniqid()) + ); + $this->assertTrue($agent instanceof Agent); + return $agent; + } + + private function createRightGroup(): RightGroup { + $group = $this->createDatabaseObject(Factory::getRightGroupFactory(), new RightGroup(null, 'phpunit-' . uniqid('', true), '[]')); + $this->assertTrue($group instanceof RightGroup); + return $group; + } + + private function createUser(string $prefix): User { + $username = $prefix . '_' . uniqid(); + $user = UserUtils::createUser($username, $username . '@example.com', $this->createRightGroup()->getId(), $this->adminUser); + $this->registerDatabaseObject(Factory::getUserFactory(), $user); + return $user; + } + + private function createHashType(): HashType { + $hashType = $this->createDatabaseObject( + Factory::getHashTypeFactory(), + new HashType(null, 'hash_type_' . uniqid(), 0, 0) + ); + $this->assertTrue($hashType instanceof HashType); + return $hashType; + } + + private function createHashlist(AccessGroup $group, HashType $hashType): Hashlist { + $hashlist = $this->createDatabaseObject( + Factory::getHashlistFactory(), + new Hashlist(null, 'hashlist_' . uniqid(), DHashlistFormat::PLAIN, $hashType->getId(), 1, ':', 0, 0, 0, 0, $group->getId(), '', 0, 0, 0) + ); + $this->assertTrue($hashlist instanceof Hashlist); + return $hashlist; + } + + private function createCrackerBinaryType(): CrackerBinaryType { + $crackerBinaryType = $this->createDatabaseObject( + Factory::getCrackerBinaryTypeFactory(), + new CrackerBinaryType(null, 'type_' . uniqid(), 1) + ); + $this->assertTrue($crackerBinaryType instanceof CrackerBinaryType); + return $crackerBinaryType; + } + + private function createCrackerBinary(CrackerBinaryType $crackerBinaryType): CrackerBinary { + $crackerBinary = $this->createDatabaseObject( + Factory::getCrackerBinaryFactory(), + new CrackerBinary(null, $crackerBinaryType->getId(), '1.0.' . uniqid(), 'https://example.invalid/' . uniqid(), 'binary_' . uniqid()) + ); + $this->assertTrue($crackerBinary instanceof CrackerBinary); + return $crackerBinary; + } + + private function createTaskWrapper(AccessGroup $group, Hashlist $hashlist): TaskWrapper { + $taskWrapper = $this->createDatabaseObject( + Factory::getTaskWrapperFactory(), + new TaskWrapper(null, 1, 1, DTaskTypes::NORMAL, $hashlist->getId(), $group->getId(), 'wrapper_' . uniqid(), 0, 0) + ); + $this->assertTrue($taskWrapper instanceof TaskWrapper); + return $taskWrapper; + } + + private function createTask(TaskWrapper $taskWrapper, CrackerBinary $crackerBinary, CrackerBinaryType $crackerBinaryType): Task { + $task = $this->createDatabaseObject( + Factory::getTaskFactory(), + new Task(null, 'task_' . uniqid(), '--attack-mode 0', 60, 30, 0, 0, 1, 1, '#ffffff', 0, 0, 0, 0, $crackerBinary->getId(), $crackerBinaryType->getId(), $taskWrapper->getId(), 0, '', 0, 0, 0, 0, '') + ); + $this->assertTrue($task instanceof Task); + return $task; + } + + private function createChunk(Task $task, Agent $agent, int $state): Chunk { + $chunk = $this->createDatabaseObject( + Factory::getChunkFactory(), + new Chunk(null, $task->getId(), 0, 100, $agent->getId(), time(), 0, 0, 0, $state, 0, 0) + ); + $this->assertTrue($chunk instanceof Chunk); + return $chunk; + } + + private function createFile(AccessGroup $group): File { + $file = $this->createDatabaseObject( + Factory::getFileFactory(), + new File(null, 'file_' . uniqid(), 0, 0, 0, $group->getId(), 0) + ); + $this->assertTrue($file instanceof File); + return $file; + } +} \ No newline at end of file diff --git a/src/inc/utils/AccessControl.php b/src/inc/utils/AccessControl.php index 42866aaf2..fc07b50ad 100644 --- a/src/inc/utils/AccessControl.php +++ b/src/inc/utils/AccessControl.php @@ -4,22 +4,23 @@ use Hashtopolis\dba\models\User; use Hashtopolis\dba\Factory; +use Hashtopolis\dba\models\RightGroup; use Hashtopolis\inc\defines\DAccessControl; use Hashtopolis\inc\Login; use Hashtopolis\inc\UI; class AccessControl { - private $user; - private $rightGroup; + private ?User $user = null; + private ?RightGroup $rightGroup = null; - private static $instance = null; + private static ?self $instance = null; /** * @param User $user * @param int $groupId * @return AccessControl */ - public static function getInstance($user = null, $groupId = 0) { + public static function getInstance(?User $user = null, int $groupId = 0): self { if ($user != null || $groupId != 0) { self::$instance = new AccessControl($user, $groupId); } @@ -32,7 +33,7 @@ public static function getInstance($user = null, $groupId = 0) { /** * @return User */ - public function getUser() { + public function getUser(): ?User { return $this->user; } @@ -41,7 +42,7 @@ public function getUser() { * @param $user User * @param $groupId int */ - private function __construct($user = null, $groupId = 0) { + private function __construct(?User $user = null, int $groupId = 0) { $this->user = $user; if ($this->user != null) { $this->rightGroup = Factory::getRightGroupFactory()->get($this->user->getRightGroupId()); @@ -54,7 +55,7 @@ private function __construct($user = null, $groupId = 0) { /** * Force a reload of the permissions from the database */ - public function reload() { + public function reload(): void { if ($this->user != null) { $this->rightGroup = Factory::getRightGroupFactory()->get($this->user->getRightGroupId()); } @@ -64,7 +65,7 @@ public function reload() { * If access is not granted, permission denied page will be shown * @param $perm string|string[] */ - public function checkPermission($perm) { + public function checkPermission(string|array $perm): void { if (!$this->hasPermission($perm)) { UI::permissionError(); } @@ -74,7 +75,7 @@ public function checkPermission($perm) { * @param $singlePerm string * @return bool */ - public function givenByDependency($singlePerm) { + public function givenByDependency(string $singlePerm): bool { $constants = DAccessControl::getConstants(); foreach ($constants as $constant) { if (is_array($constant) && $singlePerm == $constant[0] && $this->hasPermission($constant)) { @@ -91,7 +92,7 @@ public function givenByDependency($singlePerm) { * @param $perm string|string[] * @return bool true if access is granted */ - public function hasPermission($perm) { + public function hasPermission(string|array $perm): bool { if ($perm == DAccessControl::PUBLIC_ACCESS) { return true; } diff --git a/src/inc/utils/AccessControlUtils.php b/src/inc/utils/AccessControlUtils.php index ce1715387..0af5f35f3 100644 --- a/src/inc/utils/AccessControlUtils.php +++ b/src/inc/utils/AccessControlUtils.php @@ -17,7 +17,7 @@ class AccessControlUtils { * @param int $groupId * @return User[] */ - public static function getMembers($groupId) { + public static function getMembers(int $groupId): array { $qF = new QueryFilter(User::RIGHT_GROUP_ID, $groupId, "="); return Factory::getUserFactory()->filter([Factory::FILTER => $qF]); } @@ -25,14 +25,14 @@ public static function getMembers($groupId) { /** * @return RightGroup[] */ - public static function getGroups() { + public static function getGroups(): array { return Factory::getRightGroupFactory()->filter([]); } /** * @throws HTException */ - public static function addToPermissions($groupId, $perm) { + public static function addToPermissions(int $groupId, array $perm): void { $group = AccessControlUtils::getGroup($groupId); $current_permissions = $group->getPermissions(); if ($current_permissions == 'ALL') { @@ -46,11 +46,11 @@ public static function addToPermissions($groupId, $perm) { /** * @param int $groupId - * @param array $perm + * @param array $perm - Array of strings, permission-1|0 * @return boolean * @throws HTException */ - public static function updateGroupPermissions($groupId, $perm) { + public static function updateGroupPermissions(int $groupId, array $perm): bool { $group = AccessControlUtils::getGroup($groupId); if ($group->getPermissions() == 'ALL') { throw new HTException("Administrator group cannot be changed!"); @@ -116,7 +116,7 @@ public static function createGroup(string $groupName): RightGroup { * @throws HttpError * @throws HTException */ - public static function deleteGroup($groupId) { + public static function deleteGroup(int $groupId): void { $group = AccessControlUtils::getGroup($groupId); $qF = new QueryFilter(User::RIGHT_GROUP_ID, $group->getId(), "="); $count = Factory::getUserFactory()->countFilter([Factory::FILTER => $qF]); @@ -133,7 +133,7 @@ public static function deleteGroup($groupId) { * @return RightGroup * @throws HTException */ - public static function getGroup($groupId) { + public static function getGroup(int $groupId): RightGroup { $group = Factory::getRightGroupFactory()->get($groupId); if ($group === null) { throw new HTException("Invalid group!"); diff --git a/src/inc/utils/AccessGroupUtils.php b/src/inc/utils/AccessGroupUtils.php index 5f76ba115..abfa14b08 100644 --- a/src/inc/utils/AccessGroupUtils.php +++ b/src/inc/utils/AccessGroupUtils.php @@ -13,6 +13,7 @@ use Hashtopolis\dba\models\Hashlist; use Hashtopolis\dba\Factory; use Hashtopolis\dba\models\File; +use Hashtopolis\dba\models\User; use Hashtopolis\inc\apiv2\error\HttpConflict; use Hashtopolis\inc\apiv2\error\HttpError; use Hashtopolis\inc\defines\DHashcatStatus; @@ -25,7 +26,7 @@ class AccessGroupUtils { * @param int $groupId * @return AccessGroupUser[] */ - public static function getUsers($groupId) { + public static function getUsers(int $groupId): array { $qF = new QueryFilter(AccessGroupUser::ACCESS_GROUP_ID, $groupId, "="); return Factory::getAccessGroupUserFactory()->filter([Factory::FILTER => $qF]); } @@ -34,7 +35,7 @@ public static function getUsers($groupId) { * @param int $groupId * @return AccessGroupAgent[] */ - public static function getAgents($groupId) { + public static function getAgents(int $groupId): array { $qF = new QueryFilter(AccessGroupAgent::ACCESS_GROUP_ID, $groupId, "="); return Factory::getAccessGroupAgentFactory()->filter([Factory::FILTER => $qF]); } @@ -42,7 +43,7 @@ public static function getAgents($groupId) { /** * @return AccessGroup[] */ - public static function getGroups() { + public static function getGroups(): array { return Factory::getAccessGroupFactory()->filter([]); } @@ -52,7 +53,7 @@ public static function getGroups() { * @throws HttpError * @throws HttpConflict */ - public static function createGroup($groupName) { + public static function createGroup(string $groupName): AccessGroup { if (strlen($groupName) == 0 || strlen($groupName) > DLimits::ACCESS_GROUP_MAX_LENGTH) { throw new HttpError("Access group name is too short or too long!"); } @@ -70,7 +71,7 @@ public static function createGroup($groupName) { /** * @throws HTException */ - public static function rename($accessGroupId, $newname) { + public static function rename(int $accessGroupId, string $newname): void { $accessGroup = AccessGroupUtils::getGroup($accessGroupId); $name = htmlentities($newname, ENT_QUOTES, "UTF-8"); if (strlen($name) == 0) { @@ -84,7 +85,7 @@ public static function rename($accessGroupId, $newname) { * @param $user * @throws HTException */ - public static function abortChunksGroup($groupId, $user) { + public static function abortChunksGroup(int $groupId, User $user): void { $accessGroups = Util::arrayOfIds(AccessUtils::getAccessGroupsOfUser($user)); if (!in_array($groupId, $accessGroups)) { throw new HTException("User is not a member of this access group!"); @@ -107,7 +108,7 @@ public static function abortChunksGroup($groupId, $user) { * @param int $groupId * @throws HTException */ - public static function addAgent($agentId, $groupId) { + public static function addAgent(int $agentId, int $groupId): void { $group = AccessGroupUtils::getGroup($groupId); $agent = AgentUtils::getAgent($agentId); @@ -127,7 +128,7 @@ public static function addAgent($agentId, $groupId) { * @param int $groupId * @throws HTException */ - public static function addUser($userId, $groupId) { + public static function addUser(int $userId, int $groupId): void { $group = AccessGroupUtils::getGroup($groupId); $user = UserUtils::getUser($userId); @@ -147,7 +148,7 @@ public static function addUser($userId, $groupId) { * @param int $groupId * @throws HTException */ - public static function removeAgent($agentId, $groupId) { + public static function removeAgent(int $agentId, int $groupId): void { $group = AccessGroupUtils::getGroup($groupId); $agent = AgentUtils::getAgent($agentId); @@ -165,7 +166,7 @@ public static function removeAgent($agentId, $groupId) { * @param int $groupId * @throws HTException */ - public static function removeUser($userId, $groupId) { + public static function removeUser(int $userId, int $groupId): void { $group = AccessGroupUtils::getGroup($groupId); $user = UserUtils::getUser($userId); @@ -182,7 +183,7 @@ public static function removeUser($userId, $groupId) { * @param int $groupId * @throws HTException */ - public static function deleteGroup($groupId) { + public static function deleteGroup(int $groupId): void { $group = AccessGroupUtils::getGroup($groupId); $default = AccessUtils::getOrCreateDefaultAccessGroup(); if ($default->getId() == $group->getId()) { @@ -221,7 +222,7 @@ public static function deleteGroup($groupId) { * @return AccessGroup * @throws HTException */ - public static function getGroup($groupId) { + public static function getGroup(int $groupId): AccessGroup { $group = Factory::getAccessGroupFactory()->get($groupId); if ($group === null) { throw new HTException("Invalid group!");