diff --git a/apps/files_external/ajax/applicable.php b/apps/files_external/ajax/applicable.php
deleted file mode 100644
index ece913ffc068a..0000000000000
--- a/apps/files_external/ajax/applicable.php
+++ /dev/null
@@ -1,42 +0,0 @@
-search($pattern, $limit, $offset) as $group) {
- $groups[$group->getGID()] = $group->getDisplayName();
-}
-
-$users = [];
-foreach (Server::get(IUserManager::class)->searchDisplayName($pattern, $limit, $offset) as $user) {
- $users[$user->getUID()] = $user->getDisplayName();
-}
-
-$results = ['groups' => $groups, 'users' => $users];
-
-\OC_JSON::success($results);
diff --git a/apps/files_external/ajax/oauth2.php b/apps/files_external/ajax/oauth2.php
deleted file mode 100644
index d961d41ea6bcc..0000000000000
--- a/apps/files_external/ajax/oauth2.php
+++ /dev/null
@@ -1,13 +0,0 @@
-getL10N('files_external');
-
-// TODO: implement redirect to which storage backend requested this
diff --git a/apps/files_external/appinfo/routes.php b/apps/files_external/appinfo/routes.php
index 5602e1c0d11ea..fb695eafefead 100644
--- a/apps/files_external/appinfo/routes.php
+++ b/apps/files_external/appinfo/routes.php
@@ -6,13 +6,6 @@
* SPDX-License-Identifier: AGPL-3.0-only
*/
-
-$this->create('files_external_oauth2', 'apps/files_external/ajax/oauth2.php')
- ->actionInclude('files_external/ajax/oauth2.php');
-
-$this->create('files_external_list_applicable', '/apps/files_external/applicable')
- ->actionInclude('files_external/ajax/applicable.php');
-
return [
'resources' => [
'global_storages' => ['url' => '/globalstorages'],
@@ -20,11 +13,20 @@
'user_global_storages' => ['url' => '/userglobalstorages'],
],
'routes' => [
+ [
+ 'name' => 'Ajax#getApplicableEntities',
+ 'url' => '/ajax/applicable',
+ 'verb' => 'GET',
+ ],
+ [
+ 'name' => 'Ajax#oauth2Callback',
+ 'url' => '/ajax/oauth2.php',
+ 'verb' => 'GET',
+ ],
[
'name' => 'Ajax#getSshKeys',
'url' => '/ajax/public_key.php',
'verb' => 'POST',
- 'requirements' => [],
],
[
'name' => 'Ajax#saveGlobalCredentials',
diff --git a/apps/files_external/lib/Controller/AjaxController.php b/apps/files_external/lib/Controller/AjaxController.php
index 5cee642253010..b97bd7f55b53b 100644
--- a/apps/files_external/lib/Controller/AjaxController.php
+++ b/apps/files_external/lib/Controller/AjaxController.php
@@ -7,16 +7,20 @@
*/
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\AuthorizedAdminSetting;
use OCP\AppFramework\Http\Attribute\NoAdminRequired;
use OCP\AppFramework\Http\Attribute\PasswordConfirmationRequired;
use OCP\AppFramework\Http\JSONResponse;
use OCP\IGroupManager;
use OCP\IL10N;
use OCP\IRequest;
+use OCP\IUserManager;
use OCP\IUserSession;
class AjaxController extends Controller {
@@ -35,11 +39,47 @@ public function __construct(
private GlobalAuth $globalAuth,
private IUserSession $userSession,
private IGroupManager $groupManager,
+ private IUserManager $userManager,
private IL10N $l10n,
+ private AuthorizedGroupMapper $authorizedGroupMapper,
) {
parent::__construct($appName, $request);
}
+
+ /**
+ * Legacy endpoint for oauth2 callback
+ */
+ #[NoAdminRequired()]
+ public function oauth2Callback(): JSONResponse {
+ return new JSONResponse(['status' => 'success']);
+ }
+
+ /**
+ * Returns a list of users and groups that match the given pattern.
+ * Used for user and group picker in the admin settings.
+ *
+ * @param string $pattern The search pattern
+ * @param int|null $limit The maximum number of results to return
+ * @param int|null $offset The offset from which to start returning results
+ * @return JSONResponse
+ */
+ #[AuthorizedAdminSetting(settings: Admin::class)]
+ public function getApplicableEntities(string $pattern = '', ?int $limit = null, ?int $offset = null): JSONResponse {
+ $groups = [];
+ foreach ($this->groupManager->search($pattern, $limit, $offset) as $group) {
+ $groups[$group->getGID()] = $group->getDisplayName();
+ }
+
+ $users = [];
+ foreach ($this->userManager->searchDisplayName($pattern, $limit, $offset) as $user) {
+ $users[$user->getUID()] = $user->getDisplayName();
+ }
+
+ $results = ['groups' => $groups, 'users' => $users];
+ return new JSONResponse($results);
+ }
+
/**
* @param int $keyLength
* @return array
@@ -87,9 +127,10 @@ public function saveGlobalCredentials($uid, $user, $password): JSONResponse {
}
// Non-admins can only edit their own credentials
- // Admin can edit global credentials
+ // Admin or delegated admin can edit global credentials
$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/lib/Controller/GlobalStoragesController.php b/apps/files_external/lib/Controller/GlobalStoragesController.php
index e7274c9cfb64c..ebfc4e1d40933 100644
--- a/apps/files_external/lib/Controller/GlobalStoragesController.php
+++ b/apps/files_external/lib/Controller/GlobalStoragesController.php
@@ -9,7 +9,9 @@
use OCA\Files_External\NotFoundException;
use OCA\Files_External\Service\GlobalStoragesService;
+use OCA\Files_External\Settings\Admin;
use OCP\AppFramework\Http;
+use OCP\AppFramework\Http\Attribute\AuthorizedAdminSetting;
use OCP\AppFramework\Http\Attribute\PasswordConfirmationRequired;
use OCP\AppFramework\Http\DataResponse;
use OCP\IConfig;
@@ -71,6 +73,7 @@ public function __construct(
*
* @return DataResponse
*/
+ #[AuthorizedAdminSetting(settings: Admin::class)]
#[PasswordConfirmationRequired(strict: true)]
public function create(
$mountPoint,
@@ -136,6 +139,7 @@ public function create(
*
* @return DataResponse
*/
+ #[AuthorizedAdminSetting(settings: Admin::class)]
#[PasswordConfirmationRequired(strict: true)]
public function update(
$id,
@@ -186,4 +190,20 @@ public function update(
Http::STATUS_OK
);
}
+
+ #[AuthorizedAdminSetting(settings: Admin::class)]
+ public function index() {
+ return parent::index();
+ }
+
+ #[AuthorizedAdminSetting(settings: Admin::class)]
+ public function show(int $id, $testOnly = true) {
+ return parent::show($id, $testOnly);
+ }
+
+ #[AuthorizedAdminSetting(settings: Admin::class)]
+ #[PasswordConfirmationRequired(strict: true)]
+ public function destroy(int $id) {
+ return parent::destroy($id);
+ }
}
diff --git a/apps/files_external/lib/Settings/Admin.php b/apps/files_external/lib/Settings/Admin.php
index 9af0f3c61c16b..ce7631a7acc0a 100644
--- a/apps/files_external/lib/Settings/Admin.php
+++ b/apps/files_external/lib/Settings/Admin.php
@@ -12,15 +12,16 @@
use OCA\Files_External\Service\GlobalStoragesService;
use OCP\AppFramework\Http\TemplateResponse;
use OCP\Encryption\IManager;
-use OCP\Settings\ISettings;
-
-class Admin implements ISettings {
+use OCP\IL10N;
+use OCP\Settings\IDelegatedSettings;
+class Admin implements IDelegatedSettings {
public function __construct(
private IManager $encryptionManager,
private GlobalStoragesService $globalStoragesService,
private BackendService $backendService,
private GlobalAuth $globalAuth,
+ private IL10N $l10n,
) {
}
@@ -60,4 +61,12 @@ public function getSection() {
public function getPriority() {
return 40;
}
+
+ public function getName(): string {
+ return $this->l10n->t('External storage');
+ }
+
+ public function getAuthorizedAppConfig(): array {
+ return [];
+ }
}
diff --git a/apps/files_external/src/settings.js b/apps/files_external/src/settings.js
index 6b86245b9c9d2..223237da38b8e 100644
--- a/apps/files_external/src/settings.js
+++ b/apps/files_external/src/settings.js
@@ -120,7 +120,7 @@ function initApplicableUsersMultiselect($elements, userListLimit) {
dropdownCssClass: 'files-external-select2',
// minimumInputLength: 1,
ajax: {
- url: OC.generateUrl('apps/files_external/applicable'),
+ url: OC.generateUrl('apps/files_external/ajax/applicable'),
dataType: 'json',
quietMillis: 100,
data(term, page) { // page is the one-based page number tracked by Select2
@@ -131,26 +131,21 @@ function initApplicableUsersMultiselect($elements, userListLimit) {
}
},
results(data) {
- if (data.status === 'success') {
-
- const results = []
- let userCount = 0 // users is an object
+ const results = []
+ let userCount = 0 // users is an object
- // add groups
- $.each(data.groups, function(gid, group) {
- results.push({ name: gid + '(group)', displayname: group, type: 'group' })
- })
- // add users
- $.each(data.users, function(id, user) {
- userCount++
- results.push({ name: id, displayname: user, type: 'user' })
- })
+ // add groups
+ $.each(data.groups, function(gid, group) {
+ results.push({ name: gid + '(group)', displayname: group, type: 'group' })
+ })
+ // add users
+ $.each(data.users, function(id, user) {
+ userCount++
+ results.push({ name: id, displayname: user, type: 'user' })
+ })
- const more = (userCount >= userListLimit) || (data.groups.length >= userListLimit)
- return { results, more }
- } else {
- // FIXME add error handling
- }
+ const more = (userCount >= userListLimit) || (data.groups.length >= userListLimit)
+ return { results, more }
},
},
initSelection(element, callback) {
diff --git a/apps/files_external/tests/Controller/AjaxControllerTest.php b/apps/files_external/tests/Controller/AjaxControllerTest.php
index b1ea7a2b1b1b6..f4bb34994bbf5 100644
--- a/apps/files_external/tests/Controller/AjaxControllerTest.php
+++ b/apps/files_external/tests/Controller/AjaxControllerTest.php
@@ -7,14 +7,18 @@
*/
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\IGroup;
use OCP\IGroupManager;
use OCP\IL10N;
use OCP\IRequest;
use OCP\IUser;
+use OCP\IUserManager;
use OCP\IUserSession;
use PHPUnit\Framework\MockObject\MockObject;
use Test\TestCase;
@@ -25,7 +29,9 @@ class AjaxControllerTest extends TestCase {
private GlobalAuth&MockObject $globalAuth;
private IUserSession&MockObject $userSession;
private IGroupManager&MockObject $groupManager;
+ private IUserManager&MockObject $userManager;
private IL10N&MockObject $l10n;
+ private AuthorizedGroupMapper&MockObject $authorizedGroupMapper;
private AjaxController $ajaxController;
protected function setUp(): void {
@@ -34,7 +40,9 @@ protected function setUp(): void {
$this->globalAuth = $this->createMock(GlobalAuth::class);
$this->userSession = $this->createMock(IUserSession::class);
$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',
@@ -43,7 +51,9 @@ protected function setUp(): void {
$this->globalAuth,
$this->userSession,
$this->groupManager,
+ $this->userManager,
$this->l10n,
+ $this->authorizedGroupMapper,
);
$this->l10n->expects($this->any())
@@ -58,6 +68,50 @@ protected function setUp(): void {
parent::setUp();
}
+ public function testGetApplicableEntitiesReturnsGroupsAndUsers(): void {
+ $group = $this->createMock(IGroup::class);
+ $group->method('getGID')->willReturn('group1');
+ $group->method('getDisplayName')->willReturn('Group One');
+
+ $user = $this->createMock(IUser::class);
+ $user->method('getUID')->willReturn('user1');
+ $user->method('getDisplayName')->willReturn('User One');
+
+ $this->groupManager
+ ->expects($this->once())
+ ->method('search')
+ ->with('test', 10, 0)
+ ->willReturn([$group]);
+ $this->userManager
+ ->expects($this->once())
+ ->method('searchDisplayName')
+ ->with('test', 10, 0)
+ ->willReturn([$user]);
+
+ $response = $this->ajaxController->getApplicableEntities('test', 10, 0);
+ $this->assertSame(200, $response->getStatus());
+ $this->assertSame(['group1' => 'Group One'], $response->getData()['groups']);
+ $this->assertSame(['user1' => 'User One'], $response->getData()['users']);
+ }
+
+ public function testGetApplicableEntitiesWithNoResults(): void {
+ $this->groupManager
+ ->expects($this->once())
+ ->method('search')
+ ->with('', null, null)
+ ->willReturn([]);
+ $this->userManager
+ ->expects($this->once())
+ ->method('searchDisplayName')
+ ->with('', null, null)
+ ->willReturn([]);
+
+ $response = $this->ajaxController->getApplicableEntities();
+ $this->assertSame(200, $response->getStatus());
+ $this->assertSame([], $response->getData()['groups']);
+ $this->assertSame([], $response->getData()['users']);
+ }
+
public function testGetSshKeys(): void {
$this->rsa
->expects($this->once())
@@ -149,4 +203,71 @@ public function testSaveGlobalCredentialsAsNormalUserForAnotherUser(): void {
$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 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']);
+ }
}
diff --git a/apps/files_external/tests/Settings/AdminTest.php b/apps/files_external/tests/Settings/AdminTest.php
index fd4a1949760c0..7abbb33d23d4a 100644
--- a/apps/files_external/tests/Settings/AdminTest.php
+++ b/apps/files_external/tests/Settings/AdminTest.php
@@ -14,6 +14,7 @@
use OCA\Files_External\Settings\Admin;
use OCP\AppFramework\Http\TemplateResponse;
use OCP\Encryption\IManager;
+use OCP\IL10N;
use PHPUnit\Framework\MockObject\MockObject;
use Test\TestCase;
@@ -22,6 +23,7 @@ class AdminTest extends TestCase {
private GlobalStoragesService&MockObject $globalStoragesService;
private BackendService&MockObject $backendService;
private GlobalAuth&MockObject $globalAuth;
+ private IL10N&MockObject $l10n;
private Admin $admin;
protected function setUp(): void {
@@ -30,12 +32,17 @@ protected function setUp(): void {
$this->globalStoragesService = $this->createMock(GlobalStoragesService::class);
$this->backendService = $this->createMock(BackendService::class);
$this->globalAuth = $this->createMock(GlobalAuth::class);
+ $this->l10n = $this->createMock(IL10N::class);
+ $this->l10n->method('t')->willReturnCallback(function ($text) {
+ return $text;
+ });
$this->admin = new Admin(
$this->encryptionManager,
$this->globalStoragesService,
$this->backendService,
- $this->globalAuth
+ $this->globalAuth,
+ $this->l10n
);
}
@@ -91,4 +98,22 @@ public function testGetSection(): void {
public function testGetPriority(): void {
$this->assertSame(40, $this->admin->getPriority());
}
+
+ public function testGetName(): void {
+ $this->l10n->expects($this->once())
+ ->method('t')
+ ->with('External storage')
+ ->willReturn('External storage');
+
+ $this->assertSame('External storage', $this->admin->getName());
+ }
+
+ public function testGetAuthorizedAppConfig(): void {
+ $this->assertSame([], $this->admin->getAuthorizedAppConfig());
+ }
+
+ public function testImplementsIDelegatedSettings(): void {
+ $this->assertInstanceOf(\OCP\Settings\IDelegatedSettings::class, $this->admin);
+ $this->assertInstanceOf(\OCP\Settings\ISettings::class, $this->admin);
+ }
}
diff --git a/build/psalm-baseline.xml b/build/psalm-baseline.xml
index 24b8f823832f0..b5f2271284608 100644
--- a/build/psalm-baseline.xml
+++ b/build/psalm-baseline.xml
@@ -1404,27 +1404,6 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/tests/lib/UrlGeneratorTest.php b/tests/lib/UrlGeneratorTest.php
index 4320efc419083..e8f88d8f35b80 100644
--- a/tests/lib/UrlGeneratorTest.php
+++ b/tests/lib/UrlGeneratorTest.php
@@ -116,16 +116,16 @@ public static function provideRoutes(): array {
public static function provideDocRootAppUrlParts(): array {
return [
- ['files_external', 'ajax/oauth2.php', [], '/index.php/apps/files_external/ajax/oauth2.php'],
- ['files_external', 'ajax/oauth2.php', ['trut' => 'trat', 'dut' => 'dat'], '/index.php/apps/files_external/ajax/oauth2.php?trut=trat&dut=dat'],
+ ['user_ldap', 'ajax/wizard.php', [], '/index.php/apps/user_ldap/ajax/wizard.php'],
+ ['user_ldap', 'ajax/wizard.php', ['trut' => 'trat', 'dut' => 'dat'], '/index.php/apps/user_ldap/ajax/wizard.php?trut=trat&dut=dat'],
['', 'index.php', ['trut' => 'trat', 'dut' => 'dat'], '/index.php?trut=trat&dut=dat'],
];
}
public static function provideSubDirAppUrlParts(): array {
return [
- ['files_external', 'ajax/oauth2.php', [], '/nextcloud/index.php/apps/files_external/ajax/oauth2.php'],
- ['files_external', 'ajax/oauth2.php', ['trut' => 'trat', 'dut' => 'dat'], '/nextcloud/index.php/apps/files_external/ajax/oauth2.php?trut=trat&dut=dat'],
+ ['user_ldap', 'ajax/wizard.php', [], '/nextcloud/index.php/apps/user_ldap/ajax/wizard.php'],
+ ['user_ldap', 'ajax/wizard.php', ['trut' => 'trat', 'dut' => 'dat'], '/nextcloud/index.php/apps/user_ldap/ajax/wizard.php?trut=trat&dut=dat'],
['', 'index.php', ['trut' => 'trat', 'dut' => 'dat'], '/nextcloud/index.php?trut=trat&dut=dat'],
];
}