From 5852eaae0b605e7b4a078440bc3aad655da55ffc Mon Sep 17 00:00:00 2001 From: Tatjana Kaschperko Lindt Date: Mon, 30 Mar 2026 12:54:06 +0200 Subject: [PATCH] feat(files_external): allow delegated admins to save global credentials Signed-off-by: Tatjana Kaschperko Lindt --- .../lib/Controller/AjaxController.php | 11 ++- .../tests/Controller/AjaxControllerTest.php | 92 +++++++++++++++++++ 2 files changed, 101 insertions(+), 2 deletions(-) diff --git a/apps/files_external/lib/Controller/AjaxController.php b/apps/files_external/lib/Controller/AjaxController.php index 3d1d61254dd..72518106530 100644 --- a/apps/files_external/lib/Controller/AjaxController.php +++ b/apps/files_external/lib/Controller/AjaxController.php @@ -7,8 +7,10 @@ */ namespace OCA\Files_External\Controller; +use OC\Settings\AuthorizedGroupMapper; use OCA\Files_External\Lib\Auth\Password\GlobalAuth; use OCA\Files_External\Lib\Auth\PublicKey\RSA; +use OCA\Files_External\Settings\Admin; use OCP\AppFramework\Controller; use OCP\AppFramework\Http; use OCP\AppFramework\Http\Attribute\NoAdminRequired; @@ -38,6 +40,7 @@ class AjaxController extends Controller { private IGroupManager $groupManager, private IUserManager $userManager, private IL10N $l10n, + private AuthorizedGroupMapper $authorizedGroupMapper, ) { parent::__construct($appName, $request); } @@ -112,10 +115,14 @@ class AjaxController extends Controller { ], Http::STATUS_UNAUTHORIZED); } - // Non-admins can only edit their own credentials - // Admin can edit global credentials + // Non-admins can only edit their own credentials. + // Admin or delegated admin can edit global credentials (uid === ''). + // Cannot use #[AuthorizedAdminSetting] here because this endpoint is + // #[NoAdminRequired] and must also allow users to edit their own (uid !== '') + // credentials — the two paths share one method. $allowedToEdit = $uid === '' ? $this->groupManager->isAdmin($currentUser->getUID()) + || in_array(Admin::class, $this->authorizedGroupMapper->findAllClassesForUser($currentUser), true) : $currentUser->getUID() === $uid; if ($allowedToEdit) { diff --git a/apps/files_external/tests/Controller/AjaxControllerTest.php b/apps/files_external/tests/Controller/AjaxControllerTest.php index 144564df61d..7fd5255e93a 100644 --- a/apps/files_external/tests/Controller/AjaxControllerTest.php +++ b/apps/files_external/tests/Controller/AjaxControllerTest.php @@ -7,9 +7,11 @@ declare(strict_types=1); */ namespace OCA\Files_External\Tests\Controller; +use OC\Settings\AuthorizedGroupMapper; use OCA\Files_External\Controller\AjaxController; use OCA\Files_External\Lib\Auth\Password\GlobalAuth; use OCA\Files_External\Lib\Auth\PublicKey\RSA; +use OCA\Files_External\Settings\Admin; use OCP\AppFramework\Http\JSONResponse; use OCP\IGroupManager; use OCP\IL10N; @@ -28,6 +30,7 @@ class AjaxControllerTest extends TestCase { private IGroupManager&MockObject $groupManager; private IUserManager&MockObject $userManager; private IL10N&MockObject $l10n; + private AuthorizedGroupMapper&MockObject $authorizedGroupMapper; private AjaxController $ajaxController; protected function setUp(): void { @@ -38,6 +41,7 @@ class AjaxControllerTest extends TestCase { $this->groupManager = $this->createMock(IGroupManager::class); $this->userManager = $this->createMock(IUserManager::class); $this->l10n = $this->createMock(IL10N::class); + $this->authorizedGroupMapper = $this->createMock(AuthorizedGroupMapper::class); $this->ajaxController = new AjaxController( 'files_external', @@ -48,6 +52,7 @@ class AjaxControllerTest extends TestCase { $this->groupManager, $this->userManager, $this->l10n, + $this->authorizedGroupMapper, ); $this->l10n->expects($this->any()) @@ -153,4 +158,91 @@ class AjaxControllerTest extends TestCase { $this->assertSame($response->getStatus(), 403); $this->assertSame('Permission denied', $response->getData()['message']); } + + public function testSaveGlobalCredentialsAsAdminForGlobal(): void { + $user = $this->createMock(IUser::class); + $user->method('getUID')->willReturn('MyAdminUid'); + $this->userSession->method('getUser')->willReturn($user); + $this->groupManager + ->expects($this->once()) + ->method('isAdmin') + ->with('MyAdminUid') + ->willReturn(true); + $this->authorizedGroupMapper + ->expects($this->never()) + ->method('findAllClassesForUser'); + $this->globalAuth + ->expects($this->once()) + ->method('saveAuth') + ->with('', 'test', 'password'); + + $response = $this->ajaxController->saveGlobalCredentials('', 'test', 'password'); + $this->assertSame(200, $response->getStatus()); + } + + public function testSaveGlobalCredentialsAsDelegatedAdminForGlobal(): void { + $user = $this->createMock(IUser::class); + $user->method('getUID')->willReturn('DelegatedUid'); + $this->userSession->method('getUser')->willReturn($user); + $this->groupManager + ->expects($this->once()) + ->method('isAdmin') + ->with('DelegatedUid') + ->willReturn(false); + $this->authorizedGroupMapper + ->expects($this->once()) + ->method('findAllClassesForUser') + ->with($user) + ->willReturn([Admin::class]); + $this->globalAuth + ->expects($this->once()) + ->method('saveAuth') + ->with('', 'test', 'password'); + + $response = $this->ajaxController->saveGlobalCredentials('', 'test', 'password'); + $this->assertSame(200, $response->getStatus()); + } + + public function testSaveGlobalCredentialsAsDelegatedAdminForAnotherUser(): void { + // Delegated admins may only set global (uid='') credentials, not impersonate other users. + $user = $this->createMock(IUser::class); + $user->method('getUID')->willReturn('DelegatedUid'); + $this->userSession->method('getUser')->willReturn($user); + $this->groupManager + ->expects($this->never()) + ->method('isAdmin'); + $this->authorizedGroupMapper + ->expects($this->never()) + ->method('findAllClassesForUser'); + $this->globalAuth + ->expects($this->never()) + ->method('saveAuth'); + + $response = $this->ajaxController->saveGlobalCredentials('OtherUserUid', 'test', 'password'); + $this->assertSame(403, $response->getStatus()); + $this->assertSame('Permission denied', $response->getData()['message']); + } + + public function testSaveGlobalCredentialsAsNormalUserForGlobal(): void { + $user = $this->createMock(IUser::class); + $user->method('getUID')->willReturn('NormalUid'); + $this->userSession->method('getUser')->willReturn($user); + $this->groupManager + ->expects($this->once()) + ->method('isAdmin') + ->with('NormalUid') + ->willReturn(false); + $this->authorizedGroupMapper + ->expects($this->once()) + ->method('findAllClassesForUser') + ->with($user) + ->willReturn([]); + $this->globalAuth + ->expects($this->never()) + ->method('saveAuth'); + + $response = $this->ajaxController->saveGlobalCredentials('', 'test', 'password'); + $this->assertSame(403, $response->getStatus()); + $this->assertSame('Permission denied', $response->getData()['message']); + } }