Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 6 additions & 6 deletions lib/private/App/AppManager.php
Original file line number Diff line number Diff line change
Expand Up @@ -523,32 +523,32 @@ public function loadApp(string $app): void {
$settingsManager = Server::get(ISettingsManager::class);
if (!empty($info['settings']['admin'])) {
foreach ($info['settings']['admin'] as $setting) {
$settingsManager->registerSetting('admin', $setting);
$settingsManager->registerSetting('admin', $setting, $app);
}
}
if (!empty($info['settings']['admin-section'])) {
foreach ($info['settings']['admin-section'] as $section) {
$settingsManager->registerSection('admin', $section);
$settingsManager->registerSection('admin', $section, $app);
}
}
if (!empty($info['settings']['personal'])) {
foreach ($info['settings']['personal'] as $setting) {
$settingsManager->registerSetting('personal', $setting);
$settingsManager->registerSetting('personal', $setting, $app);
}
}
if (!empty($info['settings']['personal-section'])) {
foreach ($info['settings']['personal-section'] as $section) {
$settingsManager->registerSection('personal', $section);
$settingsManager->registerSection('personal', $section, $app);
}
}
if (!empty($info['settings']['admin-delegation'])) {
foreach ($info['settings']['admin-delegation'] as $setting) {
$settingsManager->registerSetting(ISettingsManager::SETTINGS_DELEGATION, $setting);
$settingsManager->registerSetting(ISettingsManager::SETTINGS_DELEGATION, $setting, $app);
}
}
if (!empty($info['settings']['admin-delegation-section'])) {
foreach ($info['settings']['admin-delegation-section'] as $section) {
$settingsManager->registerSection(ISettingsManager::SETTINGS_DELEGATION, $section);
$settingsManager->registerSection(ISettingsManager::SETTINGS_DELEGATION, $section, $app);
}
}
}
Expand Down
42 changes: 40 additions & 2 deletions lib/private/Settings/Manager.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
namespace OC\Settings;

use Closure;
use OCP\App\IAppManager;
use OCP\AppFramework\QueryException;
use OCP\Group\ISubAdmin;
use OCP\IGroupManager;
Expand Down Expand Up @@ -38,6 +39,9 @@ class Manager implements IManager {
/** @var array<self::SETTINGS_*, array<string, list<ISettings>>> */
protected array $settings = [];

/** @var array<class-string<ISettings|IIconSection>, string> App each class was registered by */
protected array $appIds = [];

public function __construct(
private LoggerInterface $log,
private IFactory $l10nFactory,
Expand All @@ -46,19 +50,23 @@ public function __construct(
private AuthorizedGroupMapper $mapper,
private IGroupManager $groupManager,
private ISubAdmin $subAdmin,
private IAppManager $appManager,
) {
}

/**
* @inheritdoc
*/
#[\Override]
public function registerSection(string $type, string $section) {
public function registerSection(string $type, string $section, ?string $appId = null) {
if (!isset($this->sectionClasses[$type])) {
$this->sectionClasses[$type] = [];
}

$this->sectionClasses[$type][] = $section;
if ($appId !== null) {
$this->appIds[$section] = $appId;
}
}

/**
Expand All @@ -76,6 +84,11 @@ protected function getSections(string $type): array {
}

foreach (array_unique($this->sectionClasses[$type]) as $index => $class) {
if ($type === self::SETTINGS_PERSONAL && !$this->isAvailableToCurrentUser($class)) {
unset($this->sectionClasses[$type][$index]);
continue;
}

try {
/** @var IIconSection $section */
$section = $this->container->get($class);
Expand Down Expand Up @@ -122,8 +135,28 @@ protected function isKnownDuplicateSectionId(string $sectionID): bool {
* @inheritdoc
*/
#[\Override]
public function registerSetting(string $type, string $setting) {
public function registerSetting(string $type, string $setting, ?string $appId = null) {
$this->settingClasses[$setting] = $type;
if ($appId !== null) {
$this->appIds[$setting] = $appId;
}
}

/**
* Apps can be limited to some groups, but their settings are registered for
* every user. So check the app of a setting or section is available to the
* current user before showing it.
*
* @param class-string<ISettings|IIconSection> $class
*/
protected function isAvailableToCurrentUser(string $class): bool {
$appId = $this->appIds[$class] ?? null;
if ($appId === null) {
// Not registered by an app, e.g. a built-in setting.
return true;
}

return $this->appManager->isEnabledForUser($appId);
}

/**
Expand All @@ -145,6 +178,11 @@ protected function getSettings(string $type, string $section, ?Closure $filter =
continue;
}

if ($type === self::SETTINGS_PERSONAL && !$this->isAvailableToCurrentUser($class)) {
unset($this->settingClasses[$class]);
continue;
}

try {
/** @var ISettings $setting */
$setting = $this->container->get($class);
Expand Down
8 changes: 6 additions & 2 deletions lib/public/Settings/IManager.php
Original file line number Diff line number Diff line change
Expand Up @@ -56,16 +56,20 @@ interface IManager {
/**
* @psalm-param self::SETTINGS_* $type
* @param class-string<IIconSection> $section
* @param ?string $appId app the section belongs to, so personal sections of
* apps not enabled for the user can be hidden (since 35.0.0)
* @since 14.0.0
*/
public function registerSection(string $type, string $section);
public function registerSection(string $type, string $section, ?string $appId = null);

/**
* @psalm-param self::SETTINGS_* $type
* @param class-string<ISettings> $setting
* @param ?string $appId app the setting belongs to, so personal settings of
* apps not enabled for the user can be hidden (since 35.0.0)
* @since 14.0.0
*/
public function registerSetting(string $type, string $setting);
public function registerSetting(string $type, string $setting, ?string $appId = null);

/**
* returns a list of the admin sections
Expand Down
68 changes: 68 additions & 0 deletions tests/lib/Settings/ManagerTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
use OC\Settings\AuthorizedGroupMapper;
use OC\Settings\Manager;
use OCA\WorkflowEngine\Settings\Section;
use OCP\App\IAppManager;
use OCP\Group\ISubAdmin;
use OCP\IGroupManager;
use OCP\IL10N;
Expand All @@ -32,6 +33,7 @@ class ManagerTest extends TestCase {
private AuthorizedGroupMapper&MockObject $mapper;
private IGroupManager&MockObject $groupManager;
private ISubAdmin&MockObject $subAdmin;
private IAppManager&MockObject $appManager;

private Manager $manager;

Expand All @@ -47,6 +49,7 @@ protected function setUp(): void {
$this->mapper = $this->createMock(AuthorizedGroupMapper::class);
$this->groupManager = $this->createMock(IGroupManager::class);
$this->subAdmin = $this->createMock(ISubAdmin::class);
$this->appManager = $this->createMock(IAppManager::class);

$this->manager = new Manager(
$this->logger,
Expand All @@ -56,6 +59,7 @@ protected function setUp(): void {
$this->mapper,
$this->groupManager,
$this->subAdmin,
$this->appManager,
);
}

Expand Down Expand Up @@ -186,6 +190,70 @@ public function testGetPersonalSettings(): void {
], $settings);
}

public function testGetPersonalSettingsHidesSettingsOfAppsNotEnabledForUser(): void {
$visible = $this->createMock(ISettings::class);
$visible->method('getPriority')
->willReturn(16);
$visible->method('getSection')
->willReturn('security');

$this->manager->registerSetting('personal', 'visibleClass', 'enabled_app');
$this->manager->registerSetting('personal', 'hiddenClass', 'restricted_app');

$this->appManager->method('isEnabledForUser')
->willReturnCallback(static fn (string $appId): bool => $appId === 'enabled_app');

// The settings of the app the user has no access to are never instantiated.
$this->container->expects($this->once())
->method('get')
->with('visibleClass')
->willReturn($visible);

$this->assertEquals([
16 => [$visible],
], $this->manager->getPersonalSettings('security'));
}

public function testGetPersonalSectionsHidesSectionsOfAppsNotEnabledForUser(): void {
$this->l10nFactory->method('get')
->with('lib')
->willReturn($this->l10n);
$this->l10n->method('t')
->willReturnArgument(0);

$this->manager->registerSection('personal', Section::class, 'restricted_app');

$this->appManager->method('isEnabledForUser')
->with('restricted_app')
->willReturn(false);

$this->container->expects($this->never())
->method('get');

$this->assertEquals([], $this->manager->getPersonalSections());
}

public function testGetAdminSettingsAreNotHiddenForAppsNotEnabledForUser(): void {
// Admins configure apps they are not a member of themselves.
$setting = $this->createMock(ISettings::class);
$setting->method('getPriority')
->willReturn(13);
$setting->method('getSection')
->willReturn('sharing');

$this->manager->registerSetting('admin', 'myAdminClass', 'restricted_app');

$this->appManager->expects($this->never())
->method('isEnabledForUser');
$this->container->method('get')
->with('myAdminClass')
->willReturn($setting);

$this->assertEquals([
13 => [$setting],
], $this->manager->getAdminSettings('sharing'));
}

public function testSameSectionAsPersonalAndAdmin(): void {
$this->l10nFactory
->expects($this->once())
Expand Down
Loading