-
-
Notifications
You must be signed in to change notification settings - Fork 4.7k
fix(admin-delegation): Prevent delegation to group if delegation already #56646
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,10 @@ | ||
| <?php | ||
|
|
||
| /** | ||
| * SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors | ||
| * SPDX-License-Identifier: AGPL-3.0-or-later | ||
| */ | ||
| namespace OCA\Settings\Service; | ||
|
|
||
| class ConflictException extends ServiceException { | ||
| } |
| Original file line number | Diff line number | Diff line change | ||
|---|---|---|---|---|
| @@ -0,0 +1,157 @@ | ||||
| <?php | ||||
|
|
||||
| /** | ||||
| * SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors | ||||
| * SPDX-License-Identifier: AGPL-3.0-or-later | ||||
| */ | ||||
| namespace OCA\Settings\Tests\Integration; | ||||
|
|
||||
| use OC\Settings\AuthorizedGroup; | ||||
| use OC\Settings\AuthorizedGroupMapper; | ||||
| use OCA\Settings\Service\AuthorizedGroupService; | ||||
| use OCA\Settings\Service\ConflictException; | ||||
| use OCP\AppFramework\Db\DoesNotExistException; | ||||
| use Test\TestCase; | ||||
|
|
||||
| /** | ||||
| * Integration test for duplicate prevention in AuthorizedGroupService | ||||
| * This test verifies the complete flow of duplicate detection and prevention | ||||
| */ | ||||
| #[\PHPUnit\Framework\Attributes\Group('DB')] | ||||
| class DuplicateAssignmentIntegrationTest extends TestCase { | ||||
printminion-co marked this conversation as resolved.
Show resolved
Hide resolved
|
||||
|
|
||||
| private AuthorizedGroupService $service; | ||||
| private AuthorizedGroupMapper $mapper; | ||||
|
|
||||
| protected function setUp(): void { | ||||
| parent::setUp(); | ||||
|
|
||||
| // Use real mapper for integration testing | ||||
| $this->mapper = \OCP\Server::get(AuthorizedGroupMapper::class); | ||||
| $this->service = new AuthorizedGroupService($this->mapper); | ||||
| } | ||||
|
|
||||
| protected function tearDown(): void { | ||||
| // Clean up any test data | ||||
| try { | ||||
| $allGroups = $this->mapper->findAll(); | ||||
| foreach ($allGroups as $group) { | ||||
| if (str_starts_with($group->getGroupId(), 'test_') | ||||
| || str_starts_with($group->getClass(), 'TestClass')) { | ||||
| $this->mapper->delete($group); | ||||
| } | ||||
| } | ||||
| } catch (\Exception $e) { | ||||
| // Ignore cleanup errors | ||||
| } | ||||
| parent::tearDown(); | ||||
| } | ||||
|
|
||||
| public function testDuplicateAssignmentPrevention(): void { | ||||
| $groupId = 'test_duplicate_group'; | ||||
| $class = 'TestClass\\DuplicateTest'; | ||||
|
|
||||
| // First assignment should succeed | ||||
| $result1 = $this->service->create($groupId, $class); | ||||
| $this->assertInstanceOf(AuthorizedGroup::class, $result1); | ||||
| $this->assertEquals($groupId, $result1->getGroupId()); | ||||
| $this->assertEquals($class, $result1->getClass()); | ||||
| $this->assertNotNull($result1->getId()); | ||||
|
|
||||
| // Second assignment of same group to same class should throw ConflictException | ||||
| $this->expectException(ConflictException::class); | ||||
| $this->expectExceptionMessage('Group is already assigned to this class'); | ||||
|
|
||||
| $this->service->create($groupId, $class); | ||||
| } | ||||
|
|
||||
| public function testDifferentGroupsSameClassAllowed(): void { | ||||
| $groupId1 = 'test_group_1'; | ||||
| $groupId2 = 'test_group_2'; | ||||
| $class = 'TestClass\\MultiGroup'; | ||||
|
|
||||
| // Both assignments should succeed | ||||
| $result1 = $this->service->create($groupId1, $class); | ||||
| $result2 = $this->service->create($groupId2, $class); | ||||
|
|
||||
| $this->assertEquals($groupId1, $result1->getGroupId()); | ||||
| $this->assertEquals($groupId2, $result2->getGroupId()); | ||||
| $this->assertEquals($class, $result1->getClass()); | ||||
| $this->assertEquals($class, $result2->getClass()); | ||||
| $this->assertNotEquals($result1->getId(), $result2->getId()); | ||||
| } | ||||
|
|
||||
| public function testSameGroupDifferentClassesAllowed(): void { | ||||
| $groupId = 'test_multi_class_group'; | ||||
| $class1 = 'TestClass\\First'; | ||||
| $class2 = 'TestClass\\Second'; | ||||
|
|
||||
| // Both assignments should succeed | ||||
| $result1 = $this->service->create($groupId, $class1); | ||||
| $result2 = $this->service->create($groupId, $class2); | ||||
|
|
||||
| $this->assertEquals($groupId, $result1->getGroupId()); | ||||
| $this->assertEquals($groupId, $result2->getGroupId()); | ||||
| $this->assertEquals($class1, $result1->getClass()); | ||||
| $this->assertEquals($class2, $result2->getClass()); | ||||
| $this->assertNotEquals($result1->getId(), $result2->getId()); | ||||
| } | ||||
|
|
||||
| public function testCreateAfterDelete(): void { | ||||
| $groupId = 'test_recreate_group'; | ||||
| $class = 'TestClass\\Recreate'; | ||||
|
|
||||
| // Create initial assignment | ||||
| $result1 = $this->service->create($groupId, $class); | ||||
| $initialId = $result1->getId(); | ||||
|
|
||||
| // Delete the assignment | ||||
| $this->service->delete($initialId); | ||||
|
|
||||
| // Verify it's deleted by trying to find it | ||||
| $this->expectException(\OCP\AppFramework\Db\DoesNotExistException::class); | ||||
|
||||
| $this->expectException(\OCP\AppFramework\Db\DoesNotExistException::class); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The condition
if ($existing)is redundant. ThefindByGroupIdAndClassmethod will either return an entity or throwDoesNotExistException. If it returns, the entity will always be truthy. The exception should be thrown immediately after the method call without the conditional check.