diff --git a/lib/Controller/IonosAccountsController.php b/lib/Controller/IonosAccountsController.php index de8e448704..9a1a5b700d 100644 --- a/lib/Controller/IonosAccountsController.php +++ b/lib/Controller/IonosAccountsController.php @@ -38,13 +38,10 @@ public function __construct( } // Helper: input validation - private function validateInput(string $accountName, string $emailAddress): ?JSONResponse { - if ($accountName === '' || $emailAddress === '') { + private function validateInput(string $accountName, string $emailUser): ?JSONResponse { + if ($accountName === '' || $emailUser === '') { return new JSONResponse(['success' => false, 'message' => self::ERR_ALL_FIELDS_REQUIRED, 'error' => self::ERR_IONOS_API_ERROR], 400); } - if (!filter_var($emailAddress, FILTER_VALIDATE_EMAIL)) { - return new JSONResponse(['success' => false, 'message' => 'Invalid email address format', 'error' => self::ERR_IONOS_API_ERROR], 400); - } return null; } @@ -52,20 +49,19 @@ private function validateInput(string $accountName, string $emailAddress): ?JSON * @NoAdminRequired */ #[TrapError] - public function create(string $accountName, string $emailAddress): JSONResponse { - if ($error = $this->validateInput($accountName, $emailAddress)) { + public function create(string $accountName, string $emailUser): JSONResponse { + if ($error = $this->validateInput($accountName, $emailUser)) { return $error; } try { - $this->logger->info('Starting IONOS email account creation', [ 'emailAddress' => $emailAddress, 'accountName' => $accountName ]); - $ionosResponse = $this->ionosMailService->createEmailAccount($emailAddress); + $this->logger->info('Starting IONOS email account creation', [ 'emailAddress' => $emailUser, 'accountName' => $accountName ]); + $ionosResponse = $this->ionosMailService->createEmailAccount($emailUser); - $this->logger->info('IONOS email account created successfully', [ 'emailAddress' => $emailAddress ]); - return $this->createNextcloudMailAccount($accountName, $emailAddress, $ionosResponse); + $this->logger->info('IONOS email account created successfully', [ 'emailAddress' => $ionosResponse->getEmail() ]); + return $this->createNextcloudMailAccount($accountName, $ionosResponse); } catch (ServiceException $e) { $data = [ - 'emailAddress' => $emailAddress, 'error' => self::ERR_IONOS_API_ERROR, 'statusCode' => $e->getCode(), ]; @@ -77,13 +73,13 @@ public function create(string $accountName, string $emailAddress): JSONResponse } } - private function createNextcloudMailAccount(string $accountName, string $emailAddress, MailAccountConfig $mailConfig): JSONResponse { + private function createNextcloudMailAccount(string $accountName, MailAccountConfig $mailConfig): JSONResponse { $imap = $mailConfig->getImap(); $smtp = $mailConfig->getSmtp(); return $this->accountsController->create( $accountName, - $emailAddress, + $mailConfig->getEmail(), $imap->getHost(), $imap->getPort(), $imap->getSecurity(), diff --git a/lib/Controller/PageController.php b/lib/Controller/PageController.php index df82aafb34..2ea0f62352 100644 --- a/lib/Controller/PageController.php +++ b/lib/Controller/PageController.php @@ -21,6 +21,7 @@ use OCA\Mail\Service\AliasesService; use OCA\Mail\Service\Classification\ClassificationSettingsService; use OCA\Mail\Service\InternalAddressService; +use OCA\Mail\Service\IONOS\IonosConfigService; use OCA\Mail\Service\OutboxService; use OCA\Mail\Service\QuickActionsService; use OCA\Mail\Service\SmimeService; @@ -74,7 +75,8 @@ class PageController extends Controller { private InternalAddressService $internalAddressService; private QuickActionsService $quickActionsService; - public function __construct(string $appName, + public function __construct( + string $appName, IRequest $request, IURLGenerator $urlGenerator, IConfig $config, @@ -97,6 +99,7 @@ public function __construct(string $appName, InternalAddressService $internalAddressService, IAvailabilityCoordinator $availabilityCoordinator, QuickActionsService $quickActionsService, + private IonosConfigService $ionosConfigService, ) { parent::__construct($appName, $request); @@ -208,9 +211,12 @@ public function index(): TemplateResponse { $user = $this->userSession->getUser(); $response = new TemplateResponse($this->appName, 'index'); + + $this->initialStateService->provideInitialState('preferences', [ 'attachment-size-limit' => $this->config->getSystemValue('app.mail.attachment-size-limit', 0), 'ionos-mailconfig-enabled' => $this->config->getAppValue('mail', 'ionos-mailconfig-enabled', 'no') === 'yes', + 'ionos-mailconfig-domain' => $this->ionosConfigService->getMailDomain(), 'app-version' => $this->config->getAppValue('mail', 'installed_version'), 'external-avatars' => $this->preferences->getPreference($this->currentUserId, 'external-avatars', 'true'), 'layout-mode' => $this->preferences->getPreference($this->currentUserId, 'layout-mode', 'vertical-split'), diff --git a/lib/Service/IONOS/IonosConfigService.php b/lib/Service/IONOS/IonosConfigService.php index a429972324..6606c510bb 100644 --- a/lib/Service/IONOS/IonosConfigService.php +++ b/lib/Service/IONOS/IonosConfigService.php @@ -13,7 +13,10 @@ use OCP\Exceptions\AppConfigException; use OCP\IAppConfig; use OCP\IConfig; +use Pdp\Domain; +use Pdp\Rules; use Psr\Log\LoggerInterface; +use Throwable; /** * Service for managing IONOS API configuration @@ -125,4 +128,49 @@ public function getApiConfig(): array { 'basicAuthPass' => $this->getBasicAuthPassword(), ]; } + + /** + * Get the mail domain from customer domain + * + * Extracts the registrable domain (mail domain) from the customer domain + * configured in system settings. + */ + public function getMailDomain(): string { + $customerDomain = $this->config->getSystemValue('ncw.customerDomain', ''); + return $this->extractMailDomain($customerDomain); + } + + /** + * Extract the registrable domain (mail domain) from a customer domain. + * + * Uses the Public Suffix List via Pdp library to properly extract the + * registrable domain, handling multi-level TLDs like .co.uk correctly. + * + * Examples: + * - foo.bar.lol -> bar.lol + * - mail.test.co.uk -> test.co.uk + * - sub.domain.example.com -> example.com + * + * @param string $customerDomain The full customer domain + * @return string The extracted mail domain, or empty string if input is empty + */ + private function extractMailDomain(string $customerDomain): string { + if (empty($customerDomain)) { + return ''; + } + + try { + $publicSuffixList = Rules::fromPath(__DIR__ . '/../../../resources/public_suffix_list.dat'); + $domain = Domain::fromIDNA2008($customerDomain); + $result = $publicSuffixList->resolve($domain); + return $result->registrableDomain()->toString(); + } catch (Throwable $e) { + // Fallback to simple extraction if Pdp fails + $parts = explode('.', $customerDomain); + if (count($parts) >= 2) { + return $parts[count($parts) - 2] . '.' . $parts[count($parts) - 1]; + } + return $customerDomain; + } + } } diff --git a/lib/Service/IONOS/IonosMailService.php b/lib/Service/IONOS/IonosMailService.php index e7218de40b..4e9e802a79 100644 --- a/lib/Service/IONOS/IonosMailService.php +++ b/lib/Service/IONOS/IonosMailService.php @@ -41,24 +41,23 @@ public function __construct( * @throws ServiceException * @throws AppConfigException */ - public function createEmailAccount(string $emailAddress): MailAccountConfig { - $config = $this->configService->getApiConfig(); + public function createEmailAccount(string $userName): MailAccountConfig { $userId = $this->getCurrentUserId(); - $userName = $this->extractUsername($emailAddress); - $domain = $this->extractDomain($emailAddress); + $domain = $this->configService->getMailDomain(); $this->logger->debug('Sending request to mailconfig service', [ - 'extRef' => $config['extRef'], - 'emailAddress' => $emailAddress, - 'apiBaseUrl' => $config['apiBaseUrl'] + 'extRef' => $this->configService->getExternalReference(), + 'userName' => $userName, + 'domain' => $domain, + 'apiBaseUrl' => $this->configService->getApiBaseUrl() ]); $client = $this->apiClientService->newClient([ - 'auth' => [$config['basicAuthUser'], $config['basicAuthPass']], - 'verify' => !$config['allowInsecure'], + 'auth' => [$this->configService->getBasicAuthUser(), $this->configService->getBasicAuthPassword()], + 'verify' => !$this->configService->getAllowInsecure(), ]); - $apiInstance = $this->apiClientService->newEventAPIApi($client, $config['apiBaseUrl']); + $apiInstance = $this->apiClientService->newEventAPIApi($client, $this->configService->getApiBaseUrl()); $mailCreateData = new MailCreateData(); $mailCreateData->setNextcloudUserId($userId); @@ -72,14 +71,14 @@ public function createEmailAccount(string $emailAddress): MailAccountConfig { try { $this->logger->debug('Send message to mailconfig service', ['data' => $mailCreateData]); - $result = $apiInstance->createMailbox(self::BRAND, $config['extRef'], $mailCreateData); + $result = $apiInstance->createMailbox(self::BRAND, $this->configService->getExternalReference(), $mailCreateData); if ($result instanceof ErrorMessage) { $this->logger->error('Failed to create ionos mail', ['status code' => $result->getStatus(), 'message' => $result->getMessage()]); throw new ServiceException('Failed to create ionos mail', $result->getStatus()); } if ($result instanceof MailAccountResponse) { - return $this->buildSuccessResponse($emailAddress, $result); + return $this->buildSuccessResponse($result); } $this->logger->debug('Failed to create ionos mail: Unknown response type', ['data' => $result ]); @@ -98,40 +97,6 @@ public function createEmailAccount(string $emailAddress): MailAccountConfig { } } - /** - * Extract domain from email address - * - * @throws ServiceException - */ - public function extractDomain(string $emailAddress): string { - $atPosition = strrchr($emailAddress, '@'); - if ($atPosition === false) { - throw new ServiceException('Invalid email address: unable to extract domain'); - } - $domain = substr($atPosition, 1); - if ($domain === '') { - throw new ServiceException('Invalid email address: unable to extract domain'); - } - return $domain; - } - - /** - * Extract username from email address - * - * @throws ServiceException - */ - public function extractUsername(string $emailAddress): string { - $atPosition = strrpos($emailAddress, '@'); - if ($atPosition === false) { - throw new ServiceException('Invalid email address: unable to extract username'); - } - $userName = substr($emailAddress, 0, $atPosition); - if ($userName === '') { - throw new ServiceException('Invalid email address: unable to extract username'); - } - return $userName; - } - /** * Get the current user ID * @@ -170,11 +135,10 @@ private function normalizeSslMode(string $apiSslMode): string { /** * Build success response with mail configuration * - * @param string $emailAddress * @param MailAccountResponse $response * @return MailAccountConfig */ - private function buildSuccessResponse(string $emailAddress, MailAccountResponse $response): MailAccountConfig { + private function buildSuccessResponse(MailAccountResponse $response): MailAccountConfig { $smtpServer = $response->getServer()->getSmtp(); $imapServer = $response->getServer()->getImap(); @@ -195,7 +159,7 @@ private function buildSuccessResponse(string $emailAddress, MailAccountResponse ); return new MailAccountConfig( - email: $emailAddress, + email: $response->getEmail(), imap: $imapConfig, smtp: $smtpConfig, ); diff --git a/src/components/ionos/NewEmailAddressTab.vue b/src/components/ionos/NewEmailAddressTab.vue index d0b99e8a85..cb4c290f49 100644 --- a/src/components/ionos/NewEmailAddressTab.vue +++ b/src/components/ionos/NewEmailAddressTab.vue @@ -13,17 +13,17 @@ @change="clearAllFeedback" autofocus /> -

- {{ t('mail', 'Please enter an email of the format name@example.com') }} +

+ {{ t('mail', 'Please enter a valid email user name') }}

- @myworkspace.com + @{{ emailDomain }}