Skip to content

Commit c45cd44

Browse files
feat(ionos): delete IONOS mailbox when user deletes mail account
- Add IONOS mailbox deletion support to AccountService - When a mail account is deleted, check if it's an IONOS account by comparing email domain - Automatically delete the IONOS mailbox via IonosMailService.tryDeleteEmailAccount() - Graceful error handling ensures Nextcloud account deletion proceeds even if IONOS deletion fails - Add comprehensive unit tests for IONOS account deletion scenarios - Complements existing user deletion functionality from PR #17 This ensures IONOS mailboxes are properly cleaned up when users delete their mail accounts, not just when the entire user is deleted from the system. Signed-off-by: Misha M.-Kupriyanov <[email protected]>
1 parent dccbc8d commit c45cd44

File tree

2 files changed

+203
-0
lines changed

2 files changed

+203
-0
lines changed

lib/Service/AccountService.php

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,11 +22,14 @@
2222
use OCA\Mail\Exception\ClientException;
2323
use OCA\Mail\Exception\ServiceException;
2424
use OCA\Mail\IMAP\IMAPClientFactory;
25+
use OCA\Mail\Service\IONOS\IonosConfigService;
26+
use OCA\Mail\Service\IONOS\IonosMailService;
2527
use OCP\AppFramework\Db\DoesNotExistException;
2628
use OCP\AppFramework\Utility\ITimeFactory;
2729
use OCP\BackgroundJob\IJob;
2830
use OCP\BackgroundJob\IJobList;
2931
use OCP\IConfig;
32+
use Psr\Log\LoggerInterface;
3033
use function array_map;
3134

3235
class AccountService {
@@ -56,6 +59,9 @@ public function __construct(
5659
IMAPClientFactory $imapClientFactory,
5760
private readonly IConfig $config,
5861
private readonly ITimeFactory $timeFactory,
62+
private readonly IonosMailService $ionosMailService,
63+
private readonly IonosConfigService $ionosConfigService,
64+
private readonly LoggerInterface $logger,
5965
) {
6066
$this->mapper = $mapper;
6167
$this->aliasesService = $aliasesService;
@@ -151,6 +157,10 @@ public function delete(string $currentUserId, int $accountId): void {
151157
} catch (DoesNotExistException $e) {
152158
throw new ClientException("Account $accountId does not exist", 0, $e);
153159
}
160+
161+
// Check if this is an IONOS account and delete the mailbox if needed
162+
$this->tryDeleteIonosMailbox($mailAccount);
163+
154164
$this->aliasesService->deleteAll($accountId);
155165
$this->mapper->delete($mailAccount);
156166
}
@@ -166,10 +176,70 @@ public function deleteByAccountId(int $accountId): void {
166176
} catch (DoesNotExistException $e) {
167177
throw new ClientException("Account $accountId does not exist", 0, $e);
168178
}
179+
180+
// Check if this is an IONOS account and delete the mailbox if needed
181+
$this->tryDeleteIonosMailbox($mailAccount);
182+
169183
$this->aliasesService->deleteAll($accountId);
170184
$this->mapper->delete($mailAccount);
171185
}
172186

187+
/**
188+
* Check if the mail account is an IONOS account and delete the IONOS mailbox
189+
*
190+
* This method determines if an account belongs to IONOS by checking if the email
191+
* domain matches the configured IONOS mail domain. If it does, it attempts to
192+
* delete the corresponding IONOS mailbox.
193+
*
194+
* @param MailAccount $mailAccount The mail account being deleted
195+
* @return void
196+
*/
197+
private function tryDeleteIonosMailbox(MailAccount $mailAccount): void {
198+
// Check if IONOS integration is enabled
199+
if (!$this->ionosConfigService->isIonosIntegrationEnabled()) {
200+
return;
201+
}
202+
203+
try {
204+
$email = $mailAccount->getEmail();
205+
$ionosMailDomain = $this->ionosConfigService->getMailDomain();
206+
207+
// Check if the account's email domain matches the IONOS mail domain
208+
if (!empty($ionosMailDomain) && $this->isIonosEmail($email, $ionosMailDomain)) {
209+
$this->logger->info('Detected IONOS mail account deletion, attempting to delete IONOS mailbox', [
210+
'email' => $email,
211+
'userId' => $mailAccount->getUserId(),
212+
'accountId' => $mailAccount->getId(),
213+
]);
214+
215+
// Use tryDeleteEmailAccount to avoid throwing exceptions
216+
$this->ionosMailService->tryDeleteEmailAccount($mailAccount->getUserId());
217+
}
218+
} catch (\Exception $e) {
219+
// Log but don't throw - account deletion in Nextcloud should proceed
220+
$this->logger->error('Error checking/deleting IONOS mailbox during account deletion', [
221+
'exception' => $e,
222+
'accountId' => $mailAccount->getId(),
223+
]);
224+
}
225+
}
226+
227+
/**
228+
* Check if an email address belongs to the IONOS mail domain
229+
*
230+
* @param string $email The email address to check
231+
* @param string $ionosMailDomain The IONOS mail domain
232+
* @return bool True if the email belongs to the IONOS domain
233+
*/
234+
private function isIonosEmail(string $email, string $ionosMailDomain): bool {
235+
if (empty($email) || empty($ionosMailDomain)) {
236+
return false;
237+
}
238+
239+
$emailDomain = substr(strrchr($email, '@'), 1);
240+
return strcasecmp($emailDomain, $ionosMailDomain) === 0;
241+
}
242+
173243
/**
174244
* @param MailAccount $newAccount
175245
* @return MailAccount

tests/Unit/Service/AccountServiceTest.php

Lines changed: 133 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,11 +19,14 @@
1919
use OCA\Mail\IMAP\IMAPClientFactory;
2020
use OCA\Mail\Service\AccountService;
2121
use OCA\Mail\Service\AliasesService;
22+
use OCA\Mail\Service\IONOS\IonosConfigService;
23+
use OCA\Mail\Service\IONOS\IonosMailService;
2224
use OCP\AppFramework\Utility\ITimeFactory;
2325
use OCP\BackgroundJob\IJobList;
2426
use OCP\IConfig;
2527
use OCP\IL10N;
2628
use PHPUnit\Framework\MockObject\MockObject;
29+
use Psr\Log\LoggerInterface;
2730

2831
class AccountServiceTest extends TestCase {
2932
private string $user = 'herbert';
@@ -61,6 +64,9 @@ class AccountServiceTest extends TestCase {
6164

6265
private IConfig&MockObject $config;
6366
private ITimeFactory&MockObject $time;
67+
private IonosMailService&MockObject $ionosMailService;
68+
private IonosConfigService&MockObject $ionosConfigService;
69+
private LoggerInterface&MockObject $logger;
6470

6571
protected function setUp(): void {
6672
parent::setUp();
@@ -72,13 +78,19 @@ protected function setUp(): void {
7278
$this->imapClientFactory = $this->createMock(IMAPClientFactory::class);
7379
$this->config = $this->createMock(IConfig::class);
7480
$this->time = $this->createMock(ITimeFactory::class);
81+
$this->ionosMailService = $this->createMock(IonosMailService::class);
82+
$this->ionosConfigService = $this->createMock(IonosConfigService::class);
83+
$this->logger = $this->createMock(LoggerInterface::class);
7584
$this->accountService = new AccountService(
7685
$this->mapper,
7786
$this->aliasesService,
7887
$this->jobList,
7988
$this->imapClientFactory,
8089
$this->config,
8190
$this->time,
91+
$this->ionosMailService,
92+
$this->ionosConfigService,
93+
$this->logger,
8294
);
8395

8496
$this->account1 = new MailAccount();
@@ -139,6 +151,10 @@ public function testFindById() {
139151
public function testDelete() {
140152
$accountId = 33;
141153

154+
$this->ionosConfigService->expects($this->once())
155+
->method('isIonosIntegrationEnabled')
156+
->willReturn(false);
157+
142158
$this->mapper->expects($this->once())
143159
->method('find')
144160
->with($this->user, $accountId)
@@ -153,6 +169,10 @@ public function testDelete() {
153169
public function testDeleteByAccountId() {
154170
$accountId = 33;
155171

172+
$this->ionosConfigService->expects($this->once())
173+
->method('isIonosIntegrationEnabled')
174+
->willReturn(false);
175+
156176
$this->mapper->expects($this->once())
157177
->method('findById')
158178
->with($accountId)
@@ -252,4 +272,117 @@ public function testScheduleBackgroundJobs(): void {
252272

253273
$this->accountService->scheduleBackgroundJobs($mailAccountId);
254274
}
275+
276+
public function testDeleteIonosAccount(): void {
277+
$accountId = 33;
278+
$mailAccount = new MailAccount();
279+
$mailAccount->setId($accountId);
280+
$mailAccount->setUserId('testuser');
281+
$mailAccount->setEmail('[email protected]');
282+
283+
$this->ionosConfigService->expects($this->once())
284+
->method('isIonosIntegrationEnabled')
285+
->willReturn(true);
286+
$this->ionosConfigService->expects($this->once())
287+
->method('getMailDomain')
288+
->willReturn('example.com');
289+
290+
$this->ionosMailService->expects($this->once())
291+
->method('tryDeleteEmailAccount')
292+
->with('testuser');
293+
294+
$this->mapper->expects($this->once())
295+
->method('find')
296+
->with($this->user, $accountId)
297+
->willReturn($mailAccount);
298+
$this->mapper->expects($this->once())
299+
->method('delete')
300+
->with($mailAccount);
301+
302+
$this->accountService->delete($this->user, $accountId);
303+
}
304+
305+
public function testDeleteNonIonosAccount(): void {
306+
$accountId = 33;
307+
$mailAccount = new MailAccount();
308+
$mailAccount->setId($accountId);
309+
$mailAccount->setUserId('testuser');
310+
$mailAccount->setEmail('[email protected]');
311+
312+
$this->ionosConfigService->expects($this->once())
313+
->method('isIonosIntegrationEnabled')
314+
->willReturn(true);
315+
$this->ionosConfigService->expects($this->once())
316+
->method('getMailDomain')
317+
->willReturn('example.com');
318+
319+
// IONOS mailbox deletion should NOT be called for non-IONOS domain
320+
$this->ionosMailService->expects($this->never())
321+
->method('tryDeleteEmailAccount');
322+
323+
$this->mapper->expects($this->once())
324+
->method('find')
325+
->with($this->user, $accountId)
326+
->willReturn($mailAccount);
327+
$this->mapper->expects($this->once())
328+
->method('delete')
329+
->with($mailAccount);
330+
331+
$this->accountService->delete($this->user, $accountId);
332+
}
333+
334+
public function testDeleteIonosAccountByAccountId(): void {
335+
$accountId = 33;
336+
$mailAccount = new MailAccount();
337+
$mailAccount->setId($accountId);
338+
$mailAccount->setUserId('testuser');
339+
$mailAccount->setEmail('[email protected]');
340+
341+
$this->ionosConfigService->expects($this->once())
342+
->method('isIonosIntegrationEnabled')
343+
->willReturn(true);
344+
$this->ionosConfigService->expects($this->once())
345+
->method('getMailDomain')
346+
->willReturn('example.com');
347+
348+
$this->ionosMailService->expects($this->once())
349+
->method('tryDeleteEmailAccount')
350+
->with('testuser');
351+
352+
$this->mapper->expects($this->once())
353+
->method('findById')
354+
->with($accountId)
355+
->willReturn($mailAccount);
356+
$this->mapper->expects($this->once())
357+
->method('delete')
358+
->with($mailAccount);
359+
360+
$this->accountService->deleteByAccountId($accountId);
361+
}
362+
363+
public function testDeleteAccountWhenIonosIntegrationDisabled(): void {
364+
$accountId = 33;
365+
$mailAccount = new MailAccount();
366+
$mailAccount->setId($accountId);
367+
$mailAccount->setUserId('testuser');
368+
$mailAccount->setEmail('[email protected]');
369+
370+
$this->ionosConfigService->expects($this->once())
371+
->method('isIonosIntegrationEnabled')
372+
->willReturn(false);
373+
374+
// IONOS mailbox deletion should NOT be attempted when integration is disabled
375+
$this->ionosMailService->expects($this->never())
376+
->method('tryDeleteEmailAccount');
377+
378+
$this->mapper->expects($this->once())
379+
->method('find')
380+
->with($this->user, $accountId)
381+
->willReturn($mailAccount);
382+
$this->mapper->expects($this->once())
383+
->method('delete')
384+
->with($mailAccount);
385+
386+
$this->accountService->delete($this->user, $accountId);
387+
}
255388
}

0 commit comments

Comments
 (0)