diff --git a/appinfo/routes.php b/appinfo/routes.php index bda4b2d2fe..8f2f299f0d 100644 --- a/appinfo/routes.php +++ b/appinfo/routes.php @@ -345,6 +345,11 @@ 'url' => '/api/settings/allownewaccounts', 'verb' => 'POST' ], + [ + 'name' => 'settings#setAllowNewMailAliases', + 'url' => '/api/settings/allownewaliases', + 'verb' => 'POST' + ], [ 'name' => 'settings#setEnabledLlmProcessing', 'url' => '/api/settings/llm', diff --git a/lib/Controller/PageController.php b/lib/Controller/PageController.php index 9c1f3ea6f0..52ba72efe2 100644 --- a/lib/Controller/PageController.php +++ b/lib/Controller/PageController.php @@ -304,6 +304,11 @@ public function index(): TemplateResponse { $this->config->getAppValue('mail', 'allow_new_mail_accounts', 'yes') === 'yes' ); + $this->initialStateService->provideInitialState( + 'allow-new-aliases', + $this->config->getAppValue('mail', 'allow_new_mail_aliases', 'yes') === 'yes' + ); + $this->initialStateService->provideInitialState( 'llm_summaries_available', $this->aiIntegrationsService->isLlmProcessingEnabled() && $this->aiIntegrationsService->isLlmAvailable(SummaryTaskType::class) diff --git a/lib/Controller/SettingsController.php b/lib/Controller/SettingsController.php index 2c27a891d2..4ce45895b6 100644 --- a/lib/Controller/SettingsController.php +++ b/lib/Controller/SettingsController.php @@ -114,6 +114,11 @@ public function setAllowNewMailAccounts(bool $allowed): void { $this->config->setAppValue('mail', 'allow_new_mail_accounts', $allowed ? 'yes' : 'no'); } + public function setAllowNewMailAliases(bool $allowed): JSONResponse { + $this->config->setAppValue('mail', 'allow_new_mail_aliases', $allowed ? 'yes' : 'no'); + return new JSONResponse([]); + } + public function setEnabledLlmProcessing(bool $enabled): JSONResponse { $this->config->setAppValue('mail', 'llm_processing', $enabled ? 'yes' : 'no'); return new JSONResponse([]); diff --git a/lib/Service/AliasesService.php b/lib/Service/AliasesService.php index 62f4538fd6..071f6097c6 100644 --- a/lib/Service/AliasesService.php +++ b/lib/Service/AliasesService.php @@ -15,6 +15,7 @@ use OCA\Mail\Db\MailAccountMapper; use OCA\Mail\Exception\ClientException; use OCP\AppFramework\Db\DoesNotExistException; +use OCP\IConfig; class AliasesService { /** @var AliasMapper */ @@ -22,10 +23,13 @@ class AliasesService { /** @var MailAccountMapper */ private $mailAccountMapper; + /** @var IConfig */ + private $config; - public function __construct(AliasMapper $aliasMapper, MailAccountMapper $mailAccountMapper) { + public function __construct(AliasMapper $aliasMapper, MailAccountMapper $mailAccountMapper, IConfig $config) { $this->aliasMapper = $aliasMapper; $this->mailAccountMapper = $mailAccountMapper; + $this->config = $config; } /** @@ -67,6 +71,10 @@ public function findByAliasAndUserId(string $aliasEmail, string $userId): Alias * @throws DoesNotExistException */ public function create(string $userId, int $accountId, string $alias, string $aliasName): Alias { + if ($this->config->getAppValue('mail', 'allow_new_mail_aliases', 'yes') === 'no') { + throw new ClientException('Creating aliases has been disabled by the administrator.'); + } + $this->mailAccountMapper->find($userId, $accountId); $aliasEntity = new Alias(); diff --git a/lib/Settings/AdminSettings.php b/lib/Settings/AdminSettings.php index 32e8c004a7..3fdc79d58a 100644 --- a/lib/Settings/AdminSettings.php +++ b/lib/Settings/AdminSettings.php @@ -82,6 +82,12 @@ public function getForm() { $this->config->getAppValue('mail', 'allow_new_mail_accounts', 'yes') === 'yes' ); + $this->initialStateService->provideInitialState( + Application::APP_ID, + 'allow_new_mail_aliases', + $this->config->getAppValue('mail', 'allow_new_mail_aliases', 'yes') === 'yes' + ); + $this->initialStateService->provideInitialState( Application::APP_ID, 'layout_message_view', diff --git a/src/components/AccountSettings.vue b/src/components/AccountSettings.vue index f2043e7fc2..289d139c7f 100644 --- a/src/components/AccountSettings.vue +++ b/src/components/AccountSettings.vue @@ -11,7 +11,12 @@ :additional-trap-elements="trapElements" :name="t('mail', 'Account settings')" @update:open="updateOpen"> - + + + @@ -209,6 +214,16 @@ export default { email() { return this.account.emailAddress }, + allowNewAliases() { + return this.mainStore.getPreference('allow-new-aliases', true) + }, + showProviderAppPassword() { + // Show the password reset section if: + // 1. Account is managed by a provider (managedByProvider is set) + // 2. Provider supports app passwords + return this.account.managedByProvider + && this.account.providerCapabilities?.appPasswords === true + }, }, watch: { diff --git a/src/components/AliasSettings.vue b/src/components/AliasSettings.vue index 1371d50417..2bf5ed65c5 100644 --- a/src/components/AliasSettings.vue +++ b/src/components/AliasSettings.vue @@ -53,7 +53,7 @@
@@ -125,7 +125,9 @@ export default { aliases() { return this.account.aliases }, - + allowNewAliases() { + return this.mainStore.getPreference('allow-new-aliases', true) + }, accountAlias() { return { alias: this.account.emailAddress, diff --git a/src/components/settings/AdminSettings.vue b/src/components/settings/AdminSettings.vue index 78f999f90c..b8fdfb7e29 100644 --- a/src/components/settings/AdminSettings.vue +++ b/src/components/settings/AdminSettings.vue @@ -136,6 +136,18 @@

+
+

{{ t('mail', 'Allow aliases') }}

+
+

+ + {{ t('mail', 'Allow users to create mail aliases') }} + +

+
+
@@ -300,6 +312,7 @@ import { setImportanceClassificationEnabledByDefault, setLayoutMessageView, updateAllowNewMailAccounts, + updateAllowNewMailAliases, updateEnabledSmartReply, updateLlmEnabled, updateProvisioningSettings, @@ -368,6 +381,7 @@ export default { }, allowNewMailAccounts: loadState('mail', 'allow_new_mail_accounts', true), + allowNewMailAliases: loadState('mail', 'allow_new_mail_aliases', true), isLlmSummaryConfigured: loadState('mail', 'enabled_llm_summary_backend'), isLlmEnabled: loadState('mail', 'llm_processing', true), isLlmFreePromptConfigured: loadState('mail', 'enabled_llm_free_prompt_backend'), @@ -431,6 +445,9 @@ export default { async updateAllowNewMailAccounts(checked) { await updateAllowNewMailAccounts(checked) }, + async updateAllowNewMailAliases(checked) { + await updateAllowNewMailAliases(checked) + }, async updateLlmEnabled(checked) { await updateLlmEnabled(checked) diff --git a/src/init.js b/src/init.js index 980c886668..cbdcd61f5f 100644 --- a/src/init.js +++ b/src/init.js @@ -69,6 +69,10 @@ export default function initAfterAppCreation() { key: 'allow-new-accounts', value: loadState('mail', 'allow-new-accounts', true), }) + mainStore.savePreferenceMutation({ + key: 'allow-new-aliases', + value: loadState('mail', 'allow-new-aliases', true), + }) mainStore.savePreferenceMutation({ key: 'password-is-unavailable', value: loadState('mail', 'password-is-unavailable', false), diff --git a/src/service/SettingsService.js b/src/service/SettingsService.js index fe95eab8a4..2fb9772351 100644 --- a/src/service/SettingsService.js +++ b/src/service/SettingsService.js @@ -61,6 +61,14 @@ export function updateAllowNewMailAccounts(allowed) { return axios.post(url, data).then((resp) => resp.data) } +export function updateAllowNewMailAliases(allowed) { + const url = generateUrl('/apps/mail/api/settings/allownewaliases') + const data = { + allowed, + } + return axios.post(url, data).then((resp) => resp.data) +} + export async function updateLlmEnabled(enabled) { const url = generateUrl('/apps/mail/api/settings/llm') const data = { diff --git a/tests/Unit/Controller/AliasesControllerTest.php b/tests/Unit/Controller/AliasesControllerTest.php index 51e36e7933..b22dc3306f 100644 --- a/tests/Unit/Controller/AliasesControllerTest.php +++ b/tests/Unit/Controller/AliasesControllerTest.php @@ -18,6 +18,7 @@ use OCP\AppFramework\Db\DoesNotExistException; use OCP\AppFramework\Http; use OCP\AppFramework\Http\JSONResponse; +use OCP\IConfig; class AliasesControllerTest extends TestCase { private $controller; @@ -46,8 +47,12 @@ public function setUp(): void { $this->aliasMapper = $this->createMock(AliasMapper::class); $this->mailAccountMapper = $this->createMock(MailAccountMapper::class); + $config = $this->createMock(IConfig::class); + $config->method('getAppValue') + ->with('mail', 'allow_new_mail_aliases', 'yes') + ->willReturn('yes'); - $this->aliasService = new AliasesService($this->aliasMapper, $this->mailAccountMapper); + $this->aliasService = new AliasesService($this->aliasMapper, $this->mailAccountMapper, $config); $this->controller = new AliasesController($this->appName, $this->request, $this->aliasService, $this->userId); } diff --git a/tests/Unit/Controller/PageControllerTest.php b/tests/Unit/Controller/PageControllerTest.php index 804b9df2ef..32ed433061 100644 --- a/tests/Unit/Controller/PageControllerTest.php +++ b/tests/Unit/Controller/PageControllerTest.php @@ -269,7 +269,7 @@ public function testIndex(): void { ['version', '0.0.0', '26.0.0'], ['app.mail.attachment-size-limit', 0, 123], ]); - $this->config->expects($this->exactly(7)) + $this->config->expects($this->exactly(8)) ->method('getAppValue') ->withConsecutive( [ 'mail', 'installed_version' ], @@ -279,6 +279,7 @@ public function testIndex(): void { ['mail', 'microsoft_oauth_tenant_id' ], ['core', 'backgroundjobs_mode', 'ajax' ], ['mail', 'allow_new_mail_accounts', 'yes'], + ['mail', 'allow_new_mail_aliases', 'yes'], )->willReturnOnConsecutiveCalls( $this->returnValue('1.2.3'), $this->returnValue('threaded'), @@ -287,7 +288,7 @@ public function testIndex(): void { $this->returnValue(''), $this->returnValue('cron'), $this->returnValue('yes'), - $this->returnValue('no') + $this->returnValue('yes'), ); $this->aiIntegrationsService->expects(self::exactly(4)) ->method('isLlmProcessingEnabled') diff --git a/tests/Unit/Service/AliasesServiceTest.php b/tests/Unit/Service/AliasesServiceTest.php index fa550a3e05..b5bdb51431 100644 --- a/tests/Unit/Service/AliasesServiceTest.php +++ b/tests/Unit/Service/AliasesServiceTest.php @@ -14,6 +14,7 @@ use OCA\Mail\Exception\ClientException; use OCA\Mail\Service\AliasesService; use OCP\AppFramework\Db\DoesNotExistException; +use OCP\IConfig; class AliasesServiceTest extends TestCase { /** @var AliasesService */ @@ -28,15 +29,24 @@ class AliasesServiceTest extends TestCase { /** @var MailAccountMapper */ private $mailAccountMapper; + /** @var IConfig */ + private $config; + protected function setUp(): void { parent::setUp(); $this->aliasMapper = $this->createMock(AliasMapper::class); $this->mailAccountMapper = $this->createMock(MailAccountMapper::class); + $this->config = $this->createMock(IConfig::class); + + $this->config->method('getAppValue') + ->with('mail', 'allow_new_mail_aliases', 'yes') + ->willReturn('yes'); $this->service = new AliasesService( $this->aliasMapper, - $this->mailAccountMapper + $this->mailAccountMapper, + $this->config, ); } @@ -123,6 +133,23 @@ public function testCreateForbiddenAccountId(): void { ); } + public function testCreateDisabledByAdmin(): void { + $this->expectException(ClientException::class); + $this->expectExceptionMessage('Creating aliases has been disabled by the administrator.'); + + $this->config->expects(self::once()) + ->method('getAppValue') + ->with('mail', 'allow_new_mail_aliases', 'yes') + ->willReturn('no'); + + $this->mailAccountMapper->expects(self::never()) + ->method('find'); + $this->aliasMapper->expects(self::never()) + ->method('insert'); + + $this->service->create(300, 200, 'jane@doe.com', 'Jane Doe'); + } + public function testDelete(): void { $entity = new Alias(); $entity->setId(101); diff --git a/tests/Unit/Settings/AdminSettingsTest.php b/tests/Unit/Settings/AdminSettingsTest.php index b53df84efc..c83a7ed440 100644 --- a/tests/Unit/Settings/AdminSettingsTest.php +++ b/tests/Unit/Settings/AdminSettingsTest.php @@ -37,7 +37,7 @@ public function testGetSection() { } public function testGetForm() { - $this->serviceMock->getParameter('initialStateService')->expects($this->exactly(14)) + $this->serviceMock->getParameter('initialStateService')->expects($this->exactly(15)) ->method('provideInitialState') ->withConsecutive( [ @@ -55,6 +55,11 @@ public function testGetForm() { 'allow_new_mail_accounts', $this->anything() ], + [ + Application::APP_ID, + 'allow_new_mail_aliases', + $this->anything() + ], [ Application::APP_ID, 'layout_message_view',