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
2 changes: 1 addition & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@
"source": {
"type": "git",
"url": "https://github.com/ionos-productivity/ionos-mail-configuration-api-client.git",
"reference": "2.0.0-20251031093901"
"reference": "2.0.0-20251110130214"
},
"autoload": {
"psr-4": {
Expand Down
4 changes: 2 additions & 2 deletions composer.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

18 changes: 14 additions & 4 deletions lib/Listener/UserDeletedListener.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@

use OCA\Mail\Exception\ClientException;
use OCA\Mail\Service\AccountService;
use OCA\Mail\Service\IONOS\IonosMailService;
use OCP\EventDispatcher\Event;
use OCP\EventDispatcher\IEventListener;
use OCP\User\Events\UserDeletedEvent;
Expand All @@ -26,8 +27,11 @@ class UserDeletedListener implements IEventListener {
/** @var LoggerInterface */
private $logger;

public function __construct(AccountService $accountService,
LoggerInterface $logger) {
public function __construct(
AccountService $accountService,
LoggerInterface $logger,
private readonly IonosMailService $ionosMailService,
) {
$this->accountService = $accountService;
$this->logger = $logger;
}
Expand All @@ -40,10 +44,16 @@ public function handle(Event $event): void {
}

$user = $event->getUser();
foreach ($this->accountService->findByUserId($user->getUID()) as $account) {
$userId = $user->getUID();

// Delete IONOS mailbox if IONOS integration is enabled
$this->ionosMailService->tryDeleteEmailAccount($userId);

// Delete all mail accounts in Nextcloud
foreach ($this->accountService->findByUserId($userId) as $account) {
try {
$this->accountService->delete(
$user->getUID(),
$userId,
$account->getId()
);
} catch (ClientException $e) {
Expand Down
4 changes: 2 additions & 2 deletions lib/Service/IONOS/ApiMailConfigClientService.php
Original file line number Diff line number Diff line change
Expand Up @@ -24,13 +24,13 @@ public function newClient(array $config): ClientInterface {
}

/**
* Create a new EventAPIApi
* Create a new MailConfigurationAPIApi
*
* @param ClientInterface $client
* @param string $apiBaseUrl
* @return MailConfigurationAPIApi
*/
public function newEventAPIApi(ClientInterface $client, string $apiBaseUrl): MailConfigurationAPIApi {
public function newMailConfigurationAPIApi(ClientInterface $client, string $apiBaseUrl): MailConfigurationAPIApi {

if (empty($apiBaseUrl)) {
throw new \InvalidArgumentException('API base URL is required');
Expand Down
36 changes: 36 additions & 0 deletions lib/Service/IONOS/IonosConfigService.php
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,42 @@ public function getApiConfig(): array {
];
}

/**
* Check if IONOS mail configuration feature is enabled
*/
public function isMailConfigEnabled(): bool {
return $this->config->getAppValue('mail', 'ionos-mailconfig-enabled', 'no') === 'yes';
}

/**
* Check if IONOS integration is fully enabled and configured
*
* Returns true only if:
* 1. The mail config feature is enabled
* 2. All required API configuration is valid
*
* @return bool True if IONOS integration is enabled and configured, false otherwise
*/
public function isIonosIntegrationEnabled(): bool {
try {
// Check if feature is enabled
if (!$this->isMailConfigEnabled()) {
return false;
}

// Verify all required API configuration is valid
$this->getApiConfig();

return true;
} catch (AppConfigException $e) {
// Configuration is missing or invalid
$this->logger->debug('IONOS integration not available - configuration error', [
'exception' => $e,
]);
return false;
}
}

/**
* Get the mail domain from customer domain
*
Expand Down
10 changes: 4 additions & 6 deletions lib/Service/IONOS/IonosMailConfigService.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,15 +9,14 @@

namespace OCA\Mail\Service\IONOS;

use OCP\IConfig;
use Psr\Log\LoggerInterface;

/**
* Service to check if IONOS mail configuration should be available for the user
*/
class IonosMailConfigService {
public function __construct(
private IConfig $config,
private IonosConfigService $ionosConfigService,
private IonosMailService $ionosMailService,
private LoggerInterface $logger,
) {
Expand All @@ -27,16 +26,15 @@ public function __construct(
* Check if IONOS mail configuration should be available for the current user
*
* The configuration is available only if:
* 1. The feature is enabled in app config
* 1. The IONOS integration is enabled and properly configured
* 2. The user does NOT already have an IONOS mail account
*
* @return bool True if mail configuration should be shown, false otherwise
*/
public function isMailConfigAvailable(): bool {
try {
// Check if feature is enabled in app config
$isEnabled = $this->config->getAppValue('mail', 'ionos-mailconfig-enabled', 'no') === 'yes';
if (!$isEnabled) {
// Check if IONOS integration is enabled and configured
if (!$this->ionosConfigService->isIonosIntegrationEnabled()) {
return false;
}

Expand Down
138 changes: 113 additions & 25 deletions lib/Service/IONOS/IonosMailService.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,10 @@

namespace OCA\Mail\Service\IONOS;

use IONOS\MailConfigurationAPI\Client\Api\MailConfigurationAPIApi;
use IONOS\MailConfigurationAPI\Client\ApiException;
use IONOS\MailConfigurationAPI\Client\Model\ErrorMessage;
use IONOS\MailConfigurationAPI\Client\Model\MailAccountResponse;
use IONOS\MailConfigurationAPI\Client\Model\MailAddonErrorMessage;
use IONOS\MailConfigurationAPI\Client\Model\MailCreateData;
use OCA\Mail\Exception\ServiceException;
use OCA\Mail\Service\IONOS\Dto\MailAccountConfig;
Expand All @@ -25,6 +26,8 @@
*/
class IonosMailService {
private const BRAND = 'IONOS';
private const HTTP_NOT_FOUND = 404;
private const HTTP_INTERNAL_SERVER_ERROR = 500;

public function __construct(
private ApiMailConfigClientService $apiClientService,
Expand Down Expand Up @@ -57,12 +60,7 @@ public function mailAccountExistsForCurrentUserId(string $userId): bool {
'extRef' => $this->configService->getExternalReference(),
]);

$client = $this->apiClientService->newClient([
'auth' => [$this->configService->getBasicAuthUser(), $this->configService->getBasicAuthPassword()],
'verify' => !$this->configService->getAllowInsecure(),
]);

$apiInstance = $this->apiClientService->newEventAPIApi($client, $this->configService->getApiBaseUrl());
$apiInstance = $this->createApiInstance();

$result = $apiInstance->getFunctionalAccount(self::BRAND, $this->configService->getExternalReference(), $userId);

Expand All @@ -76,18 +74,17 @@ public function mailAccountExistsForCurrentUserId(string $userId): bool {

return false;
} catch (ApiException $e) {
$statusCode = $e->getCode();
// 404 - no account exists
if ($statusCode === 404) {
if ($e->getCode() === self::HTTP_NOT_FOUND) {
$this->logger->debug('User does not have IONOS mail account', [
'userId' => $userId,
'statusCode' => $statusCode
'statusCode' => $e->getCode()
]);
return false;
}

$this->logger->error('API Exception when checking for existing mail account', [
'statusCode' => $statusCode,
'statusCode' => $e->getCode(),
'message' => $e->getMessage(),
'responseBody' => $e->getResponseBody()
]);
Expand Down Expand Up @@ -119,12 +116,7 @@ public function createEmailAccount(string $userName): MailAccountConfig {
'apiBaseUrl' => $this->configService->getApiBaseUrl()
]);

$client = $this->apiClientService->newClient([
'auth' => [$this->configService->getBasicAuthUser(), $this->configService->getBasicAuthPassword()],
'verify' => !$this->configService->getAllowInsecure(),
]);

$apiInstance = $this->apiClientService->newEventAPIApi($client, $this->configService->getApiBaseUrl());
$apiInstance = $this->createApiInstance();

$mailCreateData = new MailCreateData();
$mailCreateData->setNextcloudUserId($userId);
Expand All @@ -136,14 +128,14 @@ public function createEmailAccount(string $userName): MailAccountConfig {
'userId' => $userId,
'userName' => $userName
]);
throw new ServiceException('Invalid mail configuration', 500);
throw new ServiceException('Invalid mail configuration', self::HTTP_INTERNAL_SERVER_ERROR);
}

try {
$this->logger->debug('Send message to mailconfig service', ['data' => $mailCreateData]);
$result = $apiInstance->createMailbox(self::BRAND, $this->configService->getExternalReference(), $mailCreateData);

if ($result instanceof ErrorMessage) {
if ($result instanceof MailAddonErrorMessage) {
$this->logger->error('Failed to create ionos mail', [
'status code' => $result->getStatus(),
'message' => $result->getMessage(),
Expand All @@ -166,25 +158,24 @@ public function createEmailAccount(string $userName): MailAccountConfig {
'userId' => $userId,
'userName' => $userName
]);
throw new ServiceException('Failed to create ionos mail', 500);
throw new ServiceException('Failed to create ionos mail', self::HTTP_INTERNAL_SERVER_ERROR);
} catch (ServiceException $e) {
// Re-throw ServiceException without modification
// Re-throw ServiceException without additional logging
throw $e;
} catch (ApiException $e) {
$statusCode = $e->getCode();
$this->logger->error('API Exception when calling MailConfigurationAPIApi->createMailbox', [
'statusCode' => $statusCode,
'statusCode' => $e->getCode(),
'message' => $e->getMessage(),
'responseBody' => $e->getResponseBody()
]);
throw new ServiceException('Failed to create ionos mail: ' . $e->getMessage(), $statusCode, $e);
throw new ServiceException('Failed to create ionos mail: ' . $e->getMessage(), $e->getCode(), $e);
} catch (\Exception $e) {
$this->logger->error('Exception when calling MailConfigurationAPIApi->createMailbox', [
'exception' => $e,
'userId' => $userId,
'userName' => $userName
]);
throw new ServiceException('Failed to create ionos mail', 500, $e);
throw new ServiceException('Failed to create ionos mail', self::HTTP_INTERNAL_SERVER_ERROR, $e);
}
}

Expand All @@ -202,6 +193,20 @@ private function getCurrentUserId(): string {
return $user->getUID();
}

/**
* Create and configure API instance with authentication
*
* @return MailConfigurationAPIApi
*/
private function createApiInstance(): MailConfigurationAPIApi {
$client = $this->apiClientService->newClient([
'auth' => [$this->configService->getBasicAuthUser(), $this->configService->getBasicAuthPassword()],
'verify' => !$this->configService->getAllowInsecure(),
]);

return $this->apiClientService->newMailConfigurationAPIApi($client, $this->configService->getApiBaseUrl());
}

/**
* Normalize SSL mode from API response to expected format
*
Expand Down Expand Up @@ -261,4 +266,87 @@ private function buildSuccessResponse(MailAccountResponse $response): MailAccoun
smtp: $smtpConfig,
);
}

/**
* Delete an IONOS email account via API
*
* @param string $userId The Nextcloud user ID
* @return bool true if deletion was successful, false otherwise
* @throws ServiceException
*/
public function deleteEmailAccount(string $userId): bool {
$this->logger->info('Attempting to delete IONOS email account', [
'userId' => $userId,
'extRef' => $this->configService->getExternalReference(),
]);

try {
$apiInstance = $this->createApiInstance();

$apiInstance->deleteMailbox(self::BRAND, $this->configService->getExternalReference(), $userId);

$this->logger->info('Successfully deleted IONOS email account', [
'userId' => $userId
]);

return true;
} catch (ApiException $e) {
// 404 means the mailbox doesn't exist - treat as success
if ($e->getCode() === self::HTTP_NOT_FOUND) {
$this->logger->debug('IONOS mailbox does not exist (already deleted or never created)', [
'userId' => $userId,
'statusCode' => $e->getCode()
]);
return true;
}

$this->logger->error('API Exception when calling MailConfigurationAPIApi->deleteMailbox', [
'statusCode' => $e->getCode(),
'message' => $e->getMessage(),
'responseBody' => $e->getResponseBody(),
'userId' => $userId
]);

throw new ServiceException('Failed to delete IONOS mail: ' . $e->getMessage(), $e->getCode(), $e);
} catch (\Exception $e) {
$this->logger->error('Exception when calling MailConfigurationAPIApi->deleteMailbox', [
'exception' => $e,
'userId' => $userId
]);

throw new ServiceException('Failed to delete IONOS mail', self::HTTP_INTERNAL_SERVER_ERROR, $e);
}
}

/**
* Delete an IONOS email account without throwing exceptions (fire and forget)
*
* This method checks if IONOS integration is enabled and attempts to delete
* the email account. All errors are logged but not thrown, making it safe
* to call in event listeners or other contexts where exceptions should not
* interrupt the flow.
*
* @param string $userId The Nextcloud user ID
* @return void
*/
public function tryDeleteEmailAccount(string $userId): void {
// Check if IONOS integration is enabled
if (!$this->configService->isIonosIntegrationEnabled()) {
$this->logger->debug('IONOS integration is not enabled, skipping email account deletion', [
'userId' => $userId
]);
return;
}

try {
$this->deleteEmailAccount($userId);
// Success is already logged by deleteEmailAccount
} catch (ServiceException $e) {
$this->logger->error('Failed to delete IONOS mailbox for user', [
'userId' => $userId,
'exception' => $e,
]);
// Don't throw - this is a fire and forget operation
}
}
}
Loading
Loading