diff --git a/appinfo/info.xml b/appinfo/info.xml
index 9ab13f49cb..ec6726f493 100644
--- a/appinfo/info.xml
+++ b/appinfo/info.xml
@@ -34,7 +34,7 @@ The rating depends on the installed text processing backend. See [the rating ove
Learn more about the Nextcloud Ethical AI Rating [in our blog](https://nextcloud.com/blog/nextcloud-ethical-ai-rating/).
]]>
- 5.8.0-dev.2
+ 5.8.0-dev.3
agpl
Christoph Wurst
GretaD
diff --git a/appinfo/routes.php b/appinfo/routes.php
index bda4b2d2fe..22350d2a1c 100644
--- a/appinfo/routes.php
+++ b/appinfo/routes.php
@@ -505,6 +505,21 @@
'url' => '/api/follow-up/check-message-ids',
'verb' => 'POST',
],
+ [
+ 'name' => 'delegation#getDelegatedUsers',
+ 'url' => '/api/delegations/{accountId}',
+ 'verb' => 'GET',
+ ],
+ [
+ 'name' => 'delegation#delegate',
+ 'url' => '/api/delegations/{accountId}',
+ 'verb' => 'POST',
+ ],
+ [
+ 'name' => 'delegation#unDelegate',
+ 'url' => '/api/delegations/{accountId}/{userId}',
+ 'verb' => 'DELETE',
+ ],
[
'name' => 'textBlockShares#getTextBlockShares',
'url' => '/api/textBlocks/{id}/shares',
diff --git a/lib/Controller/AccountApiController.php b/lib/Controller/AccountApiController.php
index 1105d78329..ba6e1cf4f6 100644
--- a/lib/Controller/AccountApiController.php
+++ b/lib/Controller/AccountApiController.php
@@ -14,6 +14,7 @@
use OCA\Mail\ResponseDefinitions;
use OCA\Mail\Service\AccountService;
use OCA\Mail\Service\AliasesService;
+use OCA\Mail\Service\DelegationService;
use OCP\AppFramework\Http;
use OCP\AppFramework\Http\Attribute\ApiRoute;
use OCP\AppFramework\Http\Attribute\NoAdminRequired;
@@ -32,6 +33,7 @@ public function __construct(
private readonly ?string $userId,
private readonly AccountService $accountService,
private readonly AliasesService $aliasesService,
+ private readonly DelegationService $delegationService,
) {
parent::__construct($appName, $request);
}
@@ -54,17 +56,36 @@ public function list(): DataResponse {
}
$accounts = $this->accountService->findByUserId($userId);
- return new DataResponse(array_map(function (Account $account) use ($userId) {
+ $result = array_map(function (Account $account) use ($userId) {
$aliases = $this->aliasesService->findAll($account->getId(), $userId);
return [
'id' => $account->getId(),
'email' => $account->getEmail(),
+ 'isDelegated' => false,
'aliases' => array_map(static fn (Alias $alias) => [
'id' => $alias->getId(),
'email' => $alias->getAlias(),
'name' => $alias->getName(),
], $aliases),
];
- }, $accounts));
+ }, $accounts);
+
+ $delegatedAccounts = $this->accountService->findDelegatedAccounts($userId);
+ foreach ($delegatedAccounts as $account) {
+ $ownerUserId = $account->getUserId();
+ $aliases = $this->aliasesService->findAll($account->getId(), $ownerUserId);
+ $result[] = [
+ 'id' => $account->getId(),
+ 'email' => $account->getEmail(),
+ 'isDelegated' => true,
+ 'aliases' => array_map(static fn (Alias $alias) => [
+ 'id' => $alias->getId(),
+ 'email' => $alias->getAlias(),
+ 'name' => $alias->getName(),
+ ], $aliases),
+ ];
+ }
+
+ return new DataResponse($result);
}
}
diff --git a/lib/Controller/AccountsController.php b/lib/Controller/AccountsController.php
index 974b0d89df..49084d1149 100644
--- a/lib/Controller/AccountsController.php
+++ b/lib/Controller/AccountsController.php
@@ -25,6 +25,7 @@
use OCA\Mail\Model\NewMessageData;
use OCA\Mail\Service\AccountService;
use OCA\Mail\Service\AliasesService;
+use OCA\Mail\Service\DelegationService;
use OCA\Mail\Service\SetupService;
use OCA\Mail\Service\Sync\SyncService;
use OCP\AppFramework\Controller;
@@ -52,6 +53,7 @@ class AccountsController extends Controller {
private IConfig $config;
private IRemoteHostValidator $hostValidator;
private MailboxSync $mailboxSync;
+ private DelegationService $delegationService;
public function __construct(
string $appName,
@@ -69,6 +71,7 @@ public function __construct(
IRemoteHostValidator $hostValidator,
MailboxSync $mailboxSync,
private ITimeFactory $timeFactory,
+ DelegationService $delegationService,
) {
parent::__construct($appName, $request);
$this->accountService = $accountService;
@@ -83,6 +86,7 @@ public function __construct(
$this->config = $config;
$this->hostValidator = $hostValidator;
$this->mailboxSync = $mailboxSync;
+ $this->delegationService = $delegationService;
}
/**
@@ -98,6 +102,15 @@ public function index(): JSONResponse {
foreach ($mailAccounts as $mailAccount) {
$conf = $mailAccount->jsonSerialize();
$conf['aliases'] = $this->aliasesService->findAll($conf['accountId'], $this->currentUserId);
+ $conf['isDelegated'] = false;
+ $json[] = $conf;
+ }
+
+ $delegatedAccounts = $this->accountService->findDelegatedAccounts($this->currentUserId);
+ foreach ($delegatedAccounts as $delegatedAccount) {
+ $conf = $delegatedAccount->jsonSerialize();
+ $conf['isDelegated'] = true;
+ $conf['aliases'] = $this->aliasesService->findAll($conf['accountId'], $delegatedAccount->getUserId());
$json[] = $conf;
}
return new JSONResponse($json);
@@ -113,7 +126,8 @@ public function index(): JSONResponse {
*/
#[TrapError]
public function show(int $id): JSONResponse {
- return new JSONResponse($this->accountService->find($this->currentUserId, $id));
+ $effectiveUserId = $this->delegationService->resolveAccountUserId($id, $this->currentUserId);
+ return new JSONResponse($this->accountService->find($effectiveUserId, $id));
}
/**
@@ -136,9 +150,10 @@ public function update(int $id,
?string $imapPassword = null,
?string $smtpPassword = null,
string $authMethod = 'password'): JSONResponse {
+ $effectiveUserId = $this->delegationService->resolveAccountUserId($id, $this->currentUserId);
try {
// Make sure the account actually exists
- $this->accountService->find($this->currentUserId, $id);
+ $this->accountService->find($effectiveUserId, $id);
} catch (ClientException $e) {
return new JSONResponse([], Http::STATUS_BAD_REQUEST);
}
@@ -164,9 +179,11 @@ public function update(int $id,
}
try {
- return MailJsonResponse::success(
- $this->setup->createNewAccount($accountName, $emailAddress, $imapHost, $imapPort, $imapSslMode, $imapUser, $imapPassword, $smtpHost, $smtpPort, $smtpSslMode, $smtpUser, $smtpPassword, $this->currentUserId, $authMethod, $id)
+ $result = MailJsonResponse::success(
+ $this->setup->createNewAccount($accountName, $emailAddress, $imapHost, $imapPort, $imapSslMode, $imapUser, $imapPassword, $smtpHost, $smtpPort, $smtpSslMode, $smtpUser, $smtpPassword, $effectiveUserId, $authMethod, $id)
);
+ $this->delegationService->logDelegatedAction("$this->currentUserId updated account <$id> on behalf of $effectiveUserId");
+ return $result;
} catch (CouldNotConnectException $e) {
$data = [
'error' => $e->getReason(),
@@ -221,28 +238,29 @@ public function patchAccount(int $id,
?bool $classificationEnabled = null,
?bool $imipCreate = null,
): JSONResponse {
- $account = $this->accountService->find($this->currentUserId, $id);
+ $effectiveUserId = $this->delegationService->resolveAccountUserId($id, $this->currentUserId);
+ $account = $this->accountService->find($effectiveUserId, $id);
$dbAccount = $account->getMailAccount();
if ($draftsMailboxId !== null) {
- $this->mailManager->getMailbox($this->currentUserId, $draftsMailboxId);
+ $this->mailManager->getMailbox($effectiveUserId, $draftsMailboxId);
$dbAccount->setDraftsMailboxId($draftsMailboxId);
}
if ($sentMailboxId !== null) {
- $this->mailManager->getMailbox($this->currentUserId, $sentMailboxId);
+ $this->mailManager->getMailbox($effectiveUserId, $sentMailboxId);
$dbAccount->setSentMailboxId($sentMailboxId);
}
if ($trashMailboxId !== null) {
- $this->mailManager->getMailbox($this->currentUserId, $trashMailboxId);
+ $this->mailManager->getMailbox($effectiveUserId, $trashMailboxId);
$dbAccount->setTrashMailboxId($trashMailboxId);
}
if ($archiveMailboxId !== null) {
- $this->mailManager->getMailbox($this->currentUserId, $archiveMailboxId);
+ $this->mailManager->getMailbox($effectiveUserId, $archiveMailboxId);
$dbAccount->setarchiveMailboxId($archiveMailboxId);
}
if ($snoozeMailboxId !== null) {
- $this->mailManager->getMailbox($this->currentUserId, $snoozeMailboxId);
+ $this->mailManager->getMailbox($effectiveUserId, $snoozeMailboxId);
$dbAccount->setSnoozeMailboxId($snoozeMailboxId);
}
if ($editorMode !== null) {
@@ -262,7 +280,7 @@ public function patchAccount(int $id,
$dbAccount->setTrashRetentionDays($trashRetentionDays <= 0 ? null : $trashRetentionDays);
}
if ($junkMailboxId !== null) {
- $this->mailManager->getMailbox($this->currentUserId, $junkMailboxId);
+ $this->mailManager->getMailbox($effectiveUserId, $junkMailboxId);
$dbAccount->setJunkMailboxId($junkMailboxId);
}
if ($searchBody !== null) {
@@ -274,9 +292,11 @@ public function patchAccount(int $id,
if ($imipCreate !== null) {
$dbAccount->setImipCreate($imipCreate);
}
- return new JSONResponse(
+ $result = new JSONResponse(
new Account($this->accountService->save($dbAccount))
);
+ $this->delegationService->logDelegatedAction("$this->currentUserId patched account <$id> on behalf of $effectiveUserId");
+ return $result;
}
/**
@@ -292,7 +312,9 @@ public function patchAccount(int $id,
*/
#[TrapError]
public function updateSignature(int $id, ?string $signature = null): JSONResponse {
- $this->accountService->updateSignature($id, $this->currentUserId, $signature);
+ $effectiveUserId = $this->delegationService->resolveAccountUserId($id, $this->currentUserId);
+ $this->accountService->updateSignature($id, $effectiveUserId, $signature);
+ $this->delegationService->logDelegatedAction("$this->currentUserId updated signature for account <$id> on behalf of $effectiveUserId");
return new JSONResponse();
}
@@ -307,7 +329,9 @@ public function updateSignature(int $id, ?string $signature = null): JSONRespons
*/
#[TrapError]
public function destroy(int $id): JSONResponse {
- $this->accountService->delete($this->currentUserId, $id);
+ $effectiveUserId = $this->delegationService->resolveAccountUserId($id, $this->currentUserId);
+ $this->accountService->delete($effectiveUserId, $id);
+ $this->delegationService->logDelegatedAction("$this->currentUserId deleted account <$id> on behalf of $effectiveUserId");
return new JSONResponse();
}
@@ -420,11 +444,12 @@ public function draft(int $id,
$this->logger->info("Updating draft <$draftId> in account <$id>");
}
- $account = $this->accountService->find($this->currentUserId, $id);
+ $effectiveUserId = $this->delegationService->resolveAccountUserId($id, $this->currentUserId);
+ $account = $this->accountService->find($effectiveUserId, $id);
$previousDraft = null;
if ($draftId !== null) {
try {
- $previousDraft = $this->mailManager->getMessage($this->currentUserId, $draftId);
+ $previousDraft = $this->mailManager->getMessage($effectiveUserId, $draftId);
} catch (ClientException $e) {
$this->logger->info("Draft {$draftId} could not be loaded: {$e->getMessage()}");
}
@@ -442,6 +467,7 @@ public function draft(int $id,
null,
[]
);
+ $this->delegationService->logDelegatedAction("$this->currentUserId saved draft in account <$id> on behalf of $effectiveUserId");
return new JSONResponse([
'id' => $this->mailManager->getMessageIdForUid($draftsMailbox, $newUID)
]);
@@ -460,7 +486,8 @@ public function draft(int $id,
* @throws ClientException
*/
public function getQuota(int $id): JSONResponse {
- $account = $this->accountService->find($this->currentUserId, $id);
+ $effectiveUserId = $this->delegationService->resolveAccountUserId($id, $this->currentUserId);
+ $account = $this->accountService->find($effectiveUserId, $id);
$quota = $this->mailManager->getQuota($account);
if ($quota === null) {
@@ -479,9 +506,11 @@ public function getQuota(int $id): JSONResponse {
* @throws ClientException
*/
public function updateSmimeCertificate(int $id, ?int $smimeCertificateId = null) {
- $account = $this->accountService->find($this->currentUserId, $id)->getMailAccount();
+ $effectiveUserId = $this->delegationService->resolveAccountUserId($id, $this->currentUserId);
+ $account = $this->accountService->find($effectiveUserId, $id)->getMailAccount();
$account->setSmimeCertificateId($smimeCertificateId);
$this->accountService->update($account);
+ $this->delegationService->logDelegatedAction("$this->currentUserId updated S/MIME certificate for account <$id> on behalf of $effectiveUserId");
return MailJsonResponse::success();
}
@@ -494,8 +523,9 @@ public function updateSmimeCertificate(int $id, ?int $smimeCertificateId = null)
* @throws ClientException
*/
public function testAccountConnection(int $id) {
+ $effectiveUserId = $this->delegationService->resolveAccountUserId($id, $this->currentUserId);
return new JSONResponse([
- 'data' => $this->accountService->testAccountConnection($this->currentUserId, $id),
+ 'data' => $this->accountService->testAccountConnection($effectiveUserId, $id),
]);
}
diff --git a/lib/Controller/AliasesController.php b/lib/Controller/AliasesController.php
index ccdf08d47a..d9fb03b0e7 100644
--- a/lib/Controller/AliasesController.php
+++ b/lib/Controller/AliasesController.php
@@ -12,6 +12,7 @@
use OCA\Mail\Exception\NotImplemented;
use OCA\Mail\Http\TrapError;
use OCA\Mail\Service\AliasesService;
+use OCA\Mail\Service\DelegationService;
use OCP\AppFramework\Controller;
use OCP\AppFramework\Db\DoesNotExistException;
use OCP\AppFramework\Http;
@@ -23,14 +24,17 @@
class AliasesController extends Controller {
private AliasesService $aliasService;
private string $currentUserId;
+ private DelegationService $delegationService;
public function __construct(string $appName,
IRequest $request,
AliasesService $aliasesService,
- string $userId) {
+ string $userId,
+ DelegationService $delegationService) {
parent::__construct($appName, $request);
$this->aliasService = $aliasesService;
$this->currentUserId = $userId;
+ $this->delegationService = $delegationService;
}
/**
@@ -42,7 +46,8 @@ public function __construct(string $appName,
*/
#[TrapError]
public function index(int $accountId): JSONResponse {
- return new JSONResponse($this->aliasService->findAll($accountId, $this->currentUserId));
+ $effectiveUserId = $this->delegationService->resolveAccountUserId($accountId, $this->currentUserId);
+ return new JSONResponse($this->aliasService->findAll($accountId, $effectiveUserId));
}
/**
@@ -64,15 +69,16 @@ public function update(int $id,
string $alias,
string $aliasName,
?int $smimeCertificateId = null): JSONResponse {
- return new JSONResponse(
- $this->aliasService->update(
- $this->currentUserId,
- $id,
- $alias,
- $aliasName,
- $smimeCertificateId,
- )
+ $effectiveUserId = $this->delegationService->resolveAliasUserId($id, $this->currentUserId);
+ $alias = $this->aliasService->update(
+ $effectiveUserId,
+ $id,
+ $alias,
+ $aliasName,
+ $smimeCertificateId,
);
+ $this->delegationService->logDelegatedAction("$this->currentUserId updated alias: $id on behalf of $effectiveUserId");
+ return new JSONResponse($alias);
}
/**
@@ -83,7 +89,10 @@ public function update(int $id,
*/
#[TrapError]
public function destroy(int $id): JSONResponse {
- return new JSONResponse($this->aliasService->delete($this->currentUserId, $id));
+ $effectiveUserId = $this->delegationService->resolveAliasUserId($id, $this->currentUserId);
+ $alias = $this->aliasService->delete($effectiveUserId, $id);
+ $this->delegationService->logDelegatedAction("$this->currentUserId deleted alias: $id on behalf of $effectiveUserId");
+ return new JSONResponse($alias);
}
/**
@@ -98,8 +107,12 @@ public function destroy(int $id): JSONResponse {
*/
#[TrapError]
public function create(int $accountId, string $alias, string $aliasName): JSONResponse {
+ $effectiveUserId = $this->delegationService->resolveAccountUserId($accountId, $this->currentUserId);
+ $alias = $this->aliasService->create($effectiveUserId, $accountId, $alias, $aliasName);
+ $id = $alias->getId();
+ $this->delegationService->logDelegatedAction("$this->currentUserId created alias: $id on behalf of $effectiveUserId");
return new JSONResponse(
- $this->aliasService->create($this->currentUserId, $accountId, $alias, $aliasName),
+ $alias,
Http::STATUS_CREATED
);
}
@@ -115,6 +128,9 @@ public function create(int $accountId, string $alias, string $aliasName): JSONRe
*/
#[TrapError]
public function updateSignature(int $id, ?string $signature = null): JSONResponse {
- return new JSONResponse($this->aliasService->updateSignature($this->currentUserId, $id, $signature));
+ $effectiveUserId = $this->delegationService->resolveAliasUserId($id, $this->currentUserId);
+ $alias = $this->aliasService->updateSignature($effectiveUserId, $id, $signature);
+ $this->delegationService->logDelegatedAction("$this->currentUserId updated alias: $id 's signature on behalf of $effectiveUserId");
+ return new JSONResponse($alias);
}
}
diff --git a/lib/Controller/DelegationController.php b/lib/Controller/DelegationController.php
new file mode 100644
index 0000000000..0a982a4529
--- /dev/null
+++ b/lib/Controller/DelegationController.php
@@ -0,0 +1,115 @@
+currentUserId = $UserId;
+ }
+
+ /**
+ * @NoAdminRequired
+ *
+ * @param int $accountId
+ * @return JSONResponse
+ */
+ #[TrapError]
+ public function getDelegatedUsers(int $accountId): JSONResponse {
+ $account = $this->accountService->findById($accountId);
+ if ($account->getUserId() !== $this->currentUserId) {
+ return new JSONResponse([], Http::STATUS_UNAUTHORIZED);
+ }
+
+ return new JSONResponse(
+ $this->delegationService->findDelegatedToUsersForAccount($accountId)
+ );
+ }
+
+ /**
+ * @NoAdminRequired
+ *
+ * @param int $accountId
+ * @param string $userId
+ * @return JSONResponse
+ */
+ #[TrapError]
+ public function delegate(int $accountId, string $userId): JSONResponse {
+
+ $account = $this->accountService->findById($accountId);
+ if ($this->currentUserId === null) {
+ return new JSONResponse([], Http::STATUS_UNAUTHORIZED);
+ }
+
+ if ($account->getUserId() !== $this->currentUserId) {
+ return new JSONResponse([], Http::STATUS_UNAUTHORIZED);
+ }
+
+ if ($userId === $this->currentUserId) {
+ return new JSONResponse(['message' => 'Cannot delegate to yourself'], Http::STATUS_BAD_REQUEST);
+ }
+
+ if (!$this->userManager->userExists($userId)) {
+ return new JSONResponse([], Http::STATUS_NOT_FOUND);
+ }
+
+ try {
+ $delegation = $this->delegationService->delegate($account, $userId, $this->currentUserId);
+ } catch (DelegationExistsException) {
+ return new JSONResponse(['message' => 'Delegation already exists'], Http::STATUS_CONFLICT);
+ }
+
+ return new JSONResponse($delegation, Http::STATUS_CREATED);
+ }
+
+ /**
+ * @NoAdminRequired
+ *
+ * @param int $accountId
+ * @param string $userId
+ * @return JSONResponse
+ */
+ #[TrapError]
+ public function unDelegate(int $accountId, string $userId): JSONResponse {
+ $account = $this->accountService->findById($accountId);
+
+ if ($this->currentUserId === null) {
+ return new JSONResponse([], Http::STATUS_UNAUTHORIZED);
+ }
+
+ if ($account->getUserId() !== $this->currentUserId) {
+ return new JSONResponse([], Http::STATUS_UNAUTHORIZED);
+ }
+
+ $this->delegationService->unDelegate($account, $userId, $this->currentUserId);
+ return new JSONResponse([], Http::STATUS_OK);
+ }
+}
diff --git a/lib/Controller/DraftsController.php b/lib/Controller/DraftsController.php
index f1a98cee76..c8eb78b771 100644
--- a/lib/Controller/DraftsController.php
+++ b/lib/Controller/DraftsController.php
@@ -14,6 +14,7 @@
use OCA\Mail\Http\JsonResponse;
use OCA\Mail\Http\TrapError;
use OCA\Mail\Service\AccountService;
+use OCA\Mail\Service\DelegationService;
use OCA\Mail\Service\DraftsService;
use OCA\Mail\Service\SmimeService;
use OCP\AppFramework\Controller;
@@ -30,6 +31,7 @@ class DraftsController extends Controller {
private AccountService $accountService;
private ITimeFactory $timeFactory;
private SmimeService $smimeService;
+ private DelegationService $delegationService;
public function __construct(string $appName,
@@ -38,13 +40,15 @@ public function __construct(string $appName,
DraftsService $service,
AccountService $accountService,
ITimeFactory $timeFactory,
- SmimeService $smimeService) {
+ SmimeService $smimeService,
+ DelegationService $delegationService) {
parent::__construct($appName, $request);
$this->userId = $userId;
$this->service = $service;
$this->accountService = $accountService;
$this->timeFactory = $timeFactory;
$this->smimeService = $smimeService;
+ $this->delegationService = $delegationService;
}
/**
@@ -93,7 +97,8 @@ public function create(
?int $draftId = null,
bool $requestMdn = false,
bool $isPgpMime = false) : JsonResponse {
- $account = $this->accountService->find($this->userId, $accountId);
+ $effectiveUserId = $this->delegationService->resolveAccountUserId($accountId, $this->userId);
+ $account = $this->accountService->find($effectiveUserId, $accountId);
if ($draftId !== null) {
$this->service->handleDraft($account, $draftId);
}
@@ -120,7 +125,8 @@ public function create(
}
$this->service->saveMessage($account, $message, $to, $cc, $bcc, $attachments);
-
+ $id = $message->getId();
+ $this->delegationService->logDelegatedAction("$this->userId created draft: $id on behalf of $effectiveUserId");
return JsonResponse::success($message, Http::STATUS_CREATED);
}
@@ -165,8 +171,9 @@ public function update(int $id,
?int $sendAt = null,
bool $requestMdn = false,
bool $isPgpMime = false): JsonResponse {
- $message = $this->service->getMessage($id, $this->userId);
- $account = $this->accountService->find($this->userId, $accountId);
+ $effectiveUserId = $this->delegationService->resolveAccountUserId($accountId, $this->userId);
+ $message = $this->service->getMessage($id, $effectiveUserId);
+ $account = $this->accountService->find($effectiveUserId, $accountId);
$message->setType(LocalMessage::TYPE_DRAFT);
$message->setAccountId($accountId);
@@ -202,10 +209,12 @@ public function update(int $id,
*/
#[TrapError]
public function destroy(int $id): JsonResponse {
- $message = $this->service->getMessage($id, $this->userId);
- $this->accountService->find($this->userId, $message->getAccountId());
+ $effectiveUserId = $this->delegationService->resolveLocalMessageUserId($id, $this->userId);
+ $message = $this->service->getMessage($id, $effectiveUserId);
+ $this->accountService->find($effectiveUserId, $message->getAccountId());
- $this->service->deleteMessage($this->userId, $message);
+ $this->service->deleteMessage($effectiveUserId, $message);
+ $this->delegationService->logDelegatedAction("$this->userId deleted draft: $id on behalf of $effectiveUserId");
return JsonResponse::success('Message deleted', Http::STATUS_ACCEPTED);
}
@@ -217,10 +226,12 @@ public function destroy(int $id): JsonResponse {
*/
#[TrapError]
public function move(int $id): JsonResponse {
- $message = $this->service->getMessage($id, $this->userId);
- $account = $this->accountService->find($this->userId, $message->getAccountId());
+ $effectiveUserId = $this->delegationService->resolveLocalMessageUserId($id, $this->userId);
+ $message = $this->service->getMessage($id, $effectiveUserId);
+ $account = $this->accountService->find($effectiveUserId, $message->getAccountId());
$this->service->sendMessage($message, $account);
+ $this->delegationService->logDelegatedAction("$this->userId moved draft: $id to the IMAP server on behalf of $effectiveUserId");
return JsonResponse::success(
'Message moved to IMAP', Http::STATUS_ACCEPTED
);
diff --git a/lib/Controller/FilterController.php b/lib/Controller/FilterController.php
index f8a9da64d3..e173f7e8fc 100644
--- a/lib/Controller/FilterController.php
+++ b/lib/Controller/FilterController.php
@@ -11,6 +11,7 @@
use OCA\Mail\AppInfo\Application;
use OCA\Mail\Service\AccountService;
+use OCA\Mail\Service\DelegationService;
use OCA\Mail\Service\FilterService;
use OCP\AppFramework\Controller;
use OCP\AppFramework\Http;
@@ -27,6 +28,7 @@ public function __construct(
string $userId,
private FilterService $mailFilterService,
private AccountService $accountService,
+ private DelegationService $delegationService,
) {
parent::__construct(Application::APP_ID, $request);
$this->currentUserId = $userId;
@@ -39,12 +41,12 @@ public function __construct(
#[Route(Route::TYPE_FRONTPAGE, verb: 'GET', url: '/api/filter/{accountId}', requirements: ['accountId' => '[\d]+'])]
#[NoAdminRequired]
public function getFilters(int $accountId): JSONResponse {
+ $effectiveUserId = $this->delegationService->resolveAccountUserId($accountId, $this->currentUserId);
$account = $this->accountService->findById($accountId);
- if ($account->getUserId() !== $this->currentUserId) {
+ if ($account->getUserId() !== $effectiveUserId) {
return new JSONResponse([], Http::STATUS_NOT_FOUND);
}
-
$result = $this->mailFilterService->parse($account->getMailAccount());
return new JSONResponse($result->getFilters());
@@ -56,13 +58,15 @@ public function getFilters(int $accountId): JSONResponse {
#[Route(Route::TYPE_FRONTPAGE, verb: 'PUT', url: '/api/filter/{accountId}', requirements: ['accountId' => '[\d]+'])]
#[NoAdminRequired]
public function updateFilters(int $accountId, array $filters): JSONResponse {
+ $effectiveUserId = $this->delegationService->resolveAccountUserId($accountId, $this->currentUserId);
$account = $this->accountService->findById($accountId);
- if ($account->getUserId() !== $this->currentUserId) {
+ if ($account->getUserId() !== $effectiveUserId) {
return new JSONResponse([], Http::STATUS_NOT_FOUND);
}
$this->mailFilterService->update($account->getMailAccount(), $filters);
+ $this->delegationService->logDelegatedAction("$this->currentUserId updated account: $accountId 's filters on behalf of $effectiveUserId");
return new JSONResponse([]);
}
diff --git a/lib/Controller/FollowUpController.php b/lib/Controller/FollowUpController.php
index 031280e784..6f61f8f61c 100644
--- a/lib/Controller/FollowUpController.php
+++ b/lib/Controller/FollowUpController.php
@@ -14,6 +14,7 @@
use OCA\Mail\Db\ThreadMapper;
use OCA\Mail\Http\JsonResponse;
use OCA\Mail\Http\TrapError;
+use OCA\Mail\Service\DelegationService;
use OCP\AppFramework\Controller;
use OCP\AppFramework\Db\DoesNotExistException;
use OCP\AppFramework\Http;
@@ -31,6 +32,7 @@ public function __construct(
private ThreadMapper $threadMapper,
private MessageMapper $messageMapper,
private MailboxMapper $mailboxMapper,
+ private DelegationService $delegationService,
) {
parent::__construct($appName, $request);
}
@@ -54,7 +56,8 @@ public function checkMessageIds(array $messageIds): JsonResponse {
$mailboxId = $message->getMailboxId();
if (!isset($mailboxes[$mailboxId])) {
try {
- $mailboxes[$mailboxId] = $this->mailboxMapper->findByUid($mailboxId, $userId);
+ $effectiveUserId = $this->delegationService->resolveMailboxUserId($mailboxId, $userId);
+ $mailboxes[$mailboxId] = $this->mailboxMapper->findByUid($mailboxId, $effectiveUserId);
} catch (DoesNotExistException $e) {
continue;
}
diff --git a/lib/Controller/ListController.php b/lib/Controller/ListController.php
index f91263b827..685b080ac9 100644
--- a/lib/Controller/ListController.php
+++ b/lib/Controller/ListController.php
@@ -15,6 +15,7 @@
use OCA\Mail\Http\JsonResponse;
use OCA\Mail\IMAP\IMAPClientFactory;
use OCA\Mail\Service\AccountService;
+use OCA\Mail\Service\DelegationService;
use OCP\AppFramework\Controller;
use OCP\AppFramework\Db\DoesNotExistException;
use OCP\AppFramework\Http;
@@ -31,6 +32,7 @@ class ListController extends Controller {
private IClientService $httpClientService;
private LoggerInterface $logger;
private ?string $currentUserId;
+ private DelegationService $delegationService;
public function __construct(IRequest $request,
IMailManager $mailManager,
@@ -38,7 +40,8 @@ public function __construct(IRequest $request,
IMAPClientFactory $clientFactory,
IClientService $httpClientService,
LoggerInterface $logger,
- ?string $userId) {
+ ?string $userId,
+ DelegationService $delegationService) {
parent::__construct(Application::APP_ID, $request);
$this->mailManager = $mailManager;
$this->accountService = $accountService;
@@ -47,6 +50,7 @@ public function __construct(IRequest $request,
$this->httpClientService = $httpClientService;
$this->logger = $logger;
$this->currentUserId = $userId;
+ $this->delegationService = $delegationService;
}
/**
@@ -59,9 +63,10 @@ public function unsubscribe(int $id): JsonResponse {
}
try {
- $message = $this->mailManager->getMessage($this->currentUserId, $id);
- $mailbox = $this->mailManager->getMailbox($this->currentUserId, $message->getMailboxId());
- $account = $this->accountService->find($this->currentUserId, $mailbox->getAccountId());
+ $effectiveUserId = $this->delegationService->resolveMessageUserId($id, $this->currentUserId);
+ $message = $this->mailManager->getMessage($effectiveUserId, $id);
+ $mailbox = $this->mailManager->getMailbox($effectiveUserId, $message->getMailboxId());
+ $account = $this->accountService->find($effectiveUserId, $mailbox->getAccountId());
} catch (DoesNotExistException $e) {
return JsonResponse::fail(null, Http::STATUS_NOT_FOUND);
}
@@ -94,6 +99,7 @@ public function unsubscribe(int $id): JsonResponse {
} finally {
$client->logout();
}
+ $this->delegationService->logDelegatedAction("$this->currentUserId unsubscribed from mailing list: $id on behalf of $effectiveUserId");
return JsonResponse::success();
}
diff --git a/lib/Controller/MailboxesApiController.php b/lib/Controller/MailboxesApiController.php
index 106a8892e2..b7c49ca9f7 100644
--- a/lib/Controller/MailboxesApiController.php
+++ b/lib/Controller/MailboxesApiController.php
@@ -13,6 +13,7 @@
use OCA\Mail\Contracts\IMailSearch;
use OCA\Mail\ResponseDefinitions;
use OCA\Mail\Service\AccountService;
+use OCA\Mail\Service\DelegationService;
use OCP\AppFramework\Db\DoesNotExistException;
use OCP\AppFramework\Http;
use OCP\AppFramework\Http\Attribute\ApiRoute;
@@ -33,6 +34,7 @@ public function __construct(
private IMailManager $mailManager,
private readonly AccountService $accountService,
private IMailSearch $mailSearch,
+ private DelegationService $delegationService,
) {
parent::__construct($appName, $request);
}
@@ -56,7 +58,8 @@ public function list(int $accountId): DataResponse {
}
try {
- $account = $this->accountService->find($userId, $accountId);
+ $effectiveUserId = $this->delegationService->resolveAccountUserId($accountId, $userId);
+ $account = $this->accountService->find($effectiveUserId, $accountId);
} catch (DoesNotExistException $e) {
return new DataResponse([], Http::STATUS_NOT_FOUND);
}
@@ -97,8 +100,9 @@ public function listMessages(int $mailboxId,
return new DataResponse([], Http::STATUS_NOT_FOUND);
}
try {
- $mailbox = $this->mailManager->getMailbox($userId, $mailboxId);
- $account = $this->accountService->find($userId, $mailbox->getAccountId());
+ $effectiveUserId = $this->delegationService->resolveMailboxUserId($mailboxId, $userId);
+ $mailbox = $this->mailManager->getMailbox($effectiveUserId, $mailboxId);
+ $account = $this->accountService->find($effectiveUserId, $mailbox->getAccountId());
} catch (DoesNotExistException $e) {
return new DataResponse([], Http::STATUS_FORBIDDEN);
}
diff --git a/lib/Controller/MailboxesController.php b/lib/Controller/MailboxesController.php
index d391f2dee6..cbcf8fbc81 100644
--- a/lib/Controller/MailboxesController.php
+++ b/lib/Controller/MailboxesController.php
@@ -21,8 +21,10 @@
use OCA\Mail\Exception\ServiceException;
use OCA\Mail\Http\TrapError;
use OCA\Mail\Service\AccountService;
+use OCA\Mail\Service\DelegationService;
use OCA\Mail\Service\Sync\SyncService;
use OCP\AppFramework\Controller;
+use OCP\AppFramework\Db\DoesNotExistException;
use OCP\AppFramework\Http;
use OCP\AppFramework\Http\Attribute\NoAdminRequired;
use OCP\AppFramework\Http\Attribute\OpenAPI;
@@ -38,6 +40,7 @@ class MailboxesController extends Controller {
private IMailManager $mailManager;
private SyncService $syncService;
private ?string $currentUserId;
+ private DelegationService $delegationService;
public function __construct(
string $appName,
@@ -48,6 +51,7 @@ public function __construct(
SyncService $syncService,
private readonly IConfig $config,
private readonly ITimeFactory $timeFactory,
+ DelegationService $delegationService,
) {
parent::__construct($appName, $request);
@@ -55,6 +59,7 @@ public function __construct(
$this->currentUserId = $userId;
$this->mailManager = $mailManager;
$this->syncService = $syncService;
+ $this->delegationService = $delegationService;
}
/**
@@ -74,7 +79,12 @@ public function index(int $accountId, bool $forceSync = false): JSONResponse {
return new JSONResponse([], Http::STATUS_UNAUTHORIZED);
}
- $account = $this->accountService->find($this->currentUserId, $accountId);
+ try {
+ $effectiveUserId = $this->delegationService->resolveAccountUserId($accountId, $this->currentUserId);
+ } catch (DoesNotExistException $e) {
+ return new JSONResponse([], Http::STATUS_FORBIDDEN);
+ }
+ $account = $this->accountService->find($effectiveUserId, $accountId);
$mailboxes = $this->mailManager->getMailboxes($account, $forceSync);
return new JSONResponse([
@@ -102,8 +112,13 @@ public function patch(int $id,
return new JSONResponse([], Http::STATUS_UNAUTHORIZED);
}
- $mailbox = $this->mailManager->getMailbox($this->currentUserId, $id);
- $account = $this->accountService->find($this->currentUserId, $mailbox->getAccountId());
+ try {
+ $effectiveUserId = $this->delegationService->resolveMailboxUserId($id, $this->currentUserId);
+ } catch (DoesNotExistException $e) {
+ return new JSONResponse([], Http::STATUS_FORBIDDEN);
+ }
+ $mailbox = $this->mailManager->getMailbox($effectiveUserId, $id);
+ $account = $this->accountService->find($effectiveUserId, $mailbox->getAccountId());
if ($name !== null) {
$mailbox = $this->mailManager->renameMailbox(
@@ -111,6 +126,7 @@ public function patch(int $id,
$mailbox,
$name
);
+ $this->delegationService->logDelegatedAction("$this->currentUserId changed mailbox: id 's name to $name on behalf of $effectiveUserId");
}
if ($subscribed !== null) {
$mailbox = $this->mailManager->updateSubscription(
@@ -118,14 +134,18 @@ public function patch(int $id,
$mailbox,
$subscribed
);
+ $subscribedVerb = $subscribed ? 'subscribed' : 'unsubscribed';
+ $this->delegationService->logDelegatedAction("$this->currentUserId $subscribedVerb to mailbox: $id on behalf of $effectiveUserId");
+
}
if ($syncInBackground !== null) {
$mailbox = $this->mailManager->enableMailboxBackgroundSync(
$mailbox,
$syncInBackground
);
+ $syncVerb = $syncInBackground ? 'enabled' : 'disabled';
+ $this->delegationService->logDelegatedAction("$this->currentUserId $syncVerb background sync for mailbox: $id on behalf of $effectiveUserId");
}
-
return new JSONResponse($mailbox);
}
@@ -148,8 +168,13 @@ public function sync(int $id, array $ids = [], ?int $lastMessageTimestamp = null
return new JSONResponse([], Http::STATUS_UNAUTHORIZED);
}
- $mailbox = $this->mailManager->getMailbox($this->currentUserId, $id);
- $account = $this->accountService->find($this->currentUserId, $mailbox->getAccountId());
+ try {
+ $effectiveUserId = $this->delegationService->resolveMailboxUserId($id, $this->currentUserId);
+ } catch (DoesNotExistException $e) {
+ return new JSONResponse([], Http::STATUS_FORBIDDEN);
+ }
+ $mailbox = $this->mailManager->getMailbox($effectiveUserId, $id);
+ $account = $this->accountService->find($effectiveUserId, $mailbox->getAccountId());
$order = $sortOrder === 'newest' ? IMailSearch::ORDER_NEWEST_FIRST: IMailSearch::ORDER_OLDEST_FIRST;
$this->config->setUserValue(
@@ -194,8 +219,13 @@ public function clearCache(int $id): JSONResponse {
return new JSONResponse([], Http::STATUS_UNAUTHORIZED);
}
- $mailbox = $this->mailManager->getMailbox($this->currentUserId, $id);
- $account = $this->accountService->find($this->currentUserId, $mailbox->getAccountId());
+ try {
+ $effectiveUserId = $this->delegationService->resolveMailboxUserId($id, $this->currentUserId);
+ } catch (DoesNotExistException $e) {
+ return new JSONResponse([], Http::STATUS_FORBIDDEN);
+ }
+ $mailbox = $this->mailManager->getMailbox($effectiveUserId, $id);
+ $account = $this->accountService->find($effectiveUserId, $mailbox->getAccountId());
$this->syncService->clearCache($account, $mailbox);
return new JSONResponse([]);
@@ -216,11 +246,18 @@ public function markAllAsRead(int $id): JSONResponse {
return new JSONResponse([], Http::STATUS_UNAUTHORIZED);
}
- $mailbox = $this->mailManager->getMailbox($this->currentUserId, $id);
- $account = $this->accountService->find($this->currentUserId, $mailbox->getAccountId());
+ try {
+ $effectiveUserId = $this->delegationService->resolveMailboxUserId($id, $this->currentUserId);
+ } catch (DoesNotExistException $e) {
+ return new JSONResponse([], Http::STATUS_FORBIDDEN);
+ }
+ $mailbox = $this->mailManager->getMailbox($effectiveUserId, $id);
+ $account = $this->accountService->find($effectiveUserId, $mailbox->getAccountId());
$this->mailManager->markFolderAsRead($account, $mailbox);
+ $this->delegationService->logDelegatedAction("$this->currentUserId marked all messages as read in mailbox: $id on behalf of $effectiveUserId");
+
return new JSONResponse([]);
}
@@ -240,7 +277,12 @@ public function stats(int $id): JSONResponse {
return new JSONResponse([], Http::STATUS_UNAUTHORIZED);
}
- $mailbox = $this->mailManager->getMailbox($this->currentUserId, $id);
+ try {
+ $effectiveUserId = $this->delegationService->resolveMailboxUserId($id, $this->currentUserId);
+ } catch (DoesNotExistException $e) {
+ return new JSONResponse([], Http::STATUS_FORBIDDEN);
+ }
+ $mailbox = $this->mailManager->getMailbox($effectiveUserId, $id);
return new JSONResponse($mailbox->getStats());
}
@@ -280,9 +322,17 @@ public function create(int $accountId, string $name): JSONResponse {
return new JSONResponse([], Http::STATUS_UNAUTHORIZED);
}
- $account = $this->accountService->find($this->currentUserId, $accountId);
+ try {
+ $effectiveUserId = $this->delegationService->resolveAccountUserId($accountId, $this->currentUserId);
+ } catch (DoesNotExistException $e) {
+ return new JSONResponse([], Http::STATUS_FORBIDDEN);
+ }
+ $account = $this->accountService->find($effectiveUserId, $accountId);
+ $mailbox = $this->mailManager->createMailbox($account, $name);
+ $id = $mailbox->getId();
+ $this->delegationService->logDelegatedAction("$this->currentUserId created mailbox: $id on behalf of $effectiveUserId");
- return new JSONResponse($this->mailManager->createMailbox($account, $name));
+ return new JSONResponse($mailbox);
}
/**
@@ -300,10 +350,17 @@ public function destroy(int $id): JSONResponse {
return new JSONResponse([], Http::STATUS_UNAUTHORIZED);
}
- $mailbox = $this->mailManager->getMailbox($this->currentUserId, $id);
- $account = $this->accountService->find($this->currentUserId, $mailbox->getAccountId());
+ try {
+ $effectiveUserId = $this->delegationService->resolveMailboxUserId($id, $this->currentUserId);
+ } catch (DoesNotExistException $e) {
+ return new JSONResponse([], Http::STATUS_FORBIDDEN);
+ }
+ $mailbox = $this->mailManager->getMailbox($effectiveUserId, $id);
+ $account = $this->accountService->find($effectiveUserId, $mailbox->getAccountId());
$this->mailManager->deleteMailbox($account, $mailbox);
+ $this->delegationService->logDelegatedAction("$this->currentUserId deleted mailbox: $id on behalf of $effectiveUserId");
+
return new JSONResponse();
}
@@ -323,10 +380,16 @@ public function clearMailbox(int $id): JSONResponse {
return new JSONResponse([], Http::STATUS_UNAUTHORIZED);
}
- $mailbox = $this->mailManager->getMailbox($this->currentUserId, $id);
- $account = $this->accountService->find($this->currentUserId, $mailbox->getAccountId());
+ try {
+ $effectiveUserId = $this->delegationService->resolveMailboxUserId($id, $this->currentUserId);
+ } catch (DoesNotExistException $e) {
+ return new JSONResponse([], Http::STATUS_FORBIDDEN);
+ }
+ $mailbox = $this->mailManager->getMailbox($effectiveUserId, $id);
+ $account = $this->accountService->find($effectiveUserId, $mailbox->getAccountId());
$this->mailManager->clearMailbox($account, $mailbox);
+ $this->delegationService->logDelegatedAction("$this->currentUserId cleared mailbox: $id on behalf of $effectiveUserId");
return new JSONResponse();
}
@@ -341,10 +404,17 @@ public function repair(int $id): JSONResponse {
return new JSONResponse([], Http::STATUS_FORBIDDEN);
}
- $mailbox = $this->mailManager->getMailbox($this->currentUserId, $id);
- $account = $this->accountService->find($this->currentUserId, $mailbox->getAccountId());
+ try {
+ $effectiveUserId = $this->delegationService->resolveMailboxUserId($id, $this->currentUserId);
+ } catch (DoesNotExistException $e) {
+ return new JSONResponse([], Http::STATUS_FORBIDDEN);
+ }
+ $mailbox = $this->mailManager->getMailbox($effectiveUserId, $id);
+ $account = $this->accountService->find($effectiveUserId, $mailbox->getAccountId());
$this->syncService->repairSync($account, $mailbox);
+ $this->delegationService->logDelegatedAction("$this->currentUserId repaired mailbox: $id on behalf of $effectiveUserId");
+
return new JsonResponse();
}
}
diff --git a/lib/Controller/MessageApiController.php b/lib/Controller/MessageApiController.php
index 475deca64f..84142f122f 100644
--- a/lib/Controller/MessageApiController.php
+++ b/lib/Controller/MessageApiController.php
@@ -21,6 +21,7 @@
use OCA\Mail\Service\AliasesService;
use OCA\Mail\Service\Attachment\AttachmentService;
use OCA\Mail\Service\Attachment\UploadedFile;
+use OCA\Mail\Service\DelegationService;
use OCA\Mail\Service\ItineraryService;
use OCA\Mail\Service\MailManager;
use OCA\Mail\Service\OutboxService;
@@ -66,6 +67,7 @@ public function __construct(
private IDkimService $dkimService,
private ItineraryService $itineraryService,
private TrustedSenderService $trustedSenderService,
+ private DelegationService $delegationService,
) {
parent::__construct($appName, $request);
$this->userId = $userId;
@@ -120,7 +122,8 @@ public function send(
}
try {
- $mailAccount = $this->accountService->find($this->userId, $accountId);
+ $effectiveUserId = $this->delegationService->resolveAccountUserId($accountId, $this->userId);
+ $mailAccount = $this->accountService->find($effectiveUserId, $accountId);
} catch (ClientException $e) {
$this->logger->error("Mail account #$accountId not found", ['exception' => $e]);
return new DataResponse('Account not found.', Http::STATUS_NOT_FOUND);
@@ -128,7 +131,7 @@ public function send(
if ($fromEmail !== $mailAccount->getEmail()) {
try {
- $alias = $this->aliasesService->findByAliasAndUserId($fromEmail, $this->userId);
+ $alias = $this->aliasesService->findByAliasAndUserId($fromEmail, $effectiveUserId);
} catch (DoesNotExistException $e) {
$this->logger->error("Alias $fromEmail for mail account $accountId not found", ['exception' => $e]);
// Cannot send from this email as it is not configured as an alias
@@ -203,8 +206,16 @@ public function send(
$this->logger->error('SMTP error: could not send message', ['exception' => $e]);
return new DataResponse('Fatal SMTP error: could not send message, and no resending is possible. Please check the mail server logs.', Http::STATUS_INTERNAL_SERVER_ERROR);
}
-
- return match ($localMessage->getStatus()) {
+ $status = $localMessage->getStatus();
+ $this->delegationService->logDelegatedAction(match ($status) {
+ LocalMessage::STATUS_PROCESSED => "$this->userId sent a message on behalf of $effectiveUserId",
+ LocalMessage::STATUS_NO_SENT_MAILBOX => "$this->userId attempted sending a message on behalf of $effectiveUserId but no sent mailbox is configured",
+ LocalMessage::STATUS_SMPT_SEND_FAIL => "$this->userId attempted sending a message on behalf of $effectiveUserId but SMTP sending failed",
+ LocalMessage::STATUS_IMAP_SENT_MAILBOX_FAIL => "$this->userId sent a message on behalf of $effectiveUserId but copying to sent mailbox failed",
+ default => "$this->userId attempted sending a message on behalf of $effectiveUserId but an unknown error occurred",
+ });
+
+ return match ($status) {
LocalMessage::STATUS_PROCESSED => new DataResponse('', Http::STATUS_OK),
LocalMessage::STATUS_NO_SENT_MAILBOX => new DataResponse('Configuration error: Cannot send message without sent mailbox.', Http::STATUS_FORBIDDEN),
LocalMessage::STATUS_SMPT_SEND_FAIL => new DataResponse('SMTP error: could not send message. Message sending will be retried. Please check the logs.', Http::STATUS_INTERNAL_SERVER_ERROR),
@@ -234,9 +245,10 @@ public function get(int $id): DataResponse {
}
try {
- $message = $this->mailManager->getMessage($this->userId, $id);
- $mailbox = $this->mailManager->getMailbox($this->userId, $message->getMailboxId());
- $account = $this->accountService->find($this->userId, $mailbox->getAccountId());
+ $effectiveUserId = $this->delegationService->resolveMessageUserId($id, $this->userId);
+ $message = $this->mailManager->getMessage($effectiveUserId, $id);
+ $mailbox = $this->mailManager->getMailbox($effectiveUserId, $message->getMailboxId());
+ $account = $this->accountService->find($effectiveUserId, $mailbox->getAccountId());
} catch (ClientException|DoesNotExistException $e) {
$this->logger->error('Message, Account or Mailbox not found', ['exception' => $e->getMessage()]);
return new DataResponse('Account not found.', Http::STATUS_NOT_FOUND);
@@ -322,9 +334,10 @@ public function getRaw(int $id): DataResponse {
}
try {
- $message = $this->mailManager->getMessage($this->userId, $id);
- $mailbox = $this->mailManager->getMailbox($this->userId, $message->getMailboxId());
- $account = $this->accountService->find($this->userId, $mailbox->getAccountId());
+ $effectiveUserId = $this->delegationService->resolveMessageUserId($id, $this->userId);
+ $message = $this->mailManager->getMessage($effectiveUserId, $id);
+ $mailbox = $this->mailManager->getMailbox($effectiveUserId, $message->getMailboxId());
+ $account = $this->accountService->find($effectiveUserId, $mailbox->getAccountId());
} catch (ClientException|DoesNotExistException $e) {
$this->logger->error('Message, Account or Mailbox not found', ['exception' => $e->getMessage()]);
return new DataResponse('Message, Account or Mailbox not found', Http::STATUS_NOT_FOUND);
@@ -383,9 +396,10 @@ private function enrichDownloadUrl(int $id, array $attachment): array {
#[TrapError]
public function getAttachment(int $id, string $attachmentId): DataResponse {
try {
- $message = $this->mailManager->getMessage($this->userId, $id);
- $mailbox = $this->mailManager->getMailbox($this->userId, $message->getMailboxId());
- $account = $this->accountService->find($this->userId, $mailbox->getAccountId());
+ $effectiveUserId = $this->delegationService->resolveMessageUserId($id, $this->userId);
+ $message = $this->mailManager->getMessage($effectiveUserId, $id);
+ $mailbox = $this->mailManager->getMailbox($effectiveUserId, $message->getMailboxId());
+ $account = $this->accountService->find($effectiveUserId, $mailbox->getAccountId());
} catch (DoesNotExistException|ClientException $e) {
return new DataResponse('Message, Account or Mailbox not found', Http::STATUS_NOT_FOUND);
}
diff --git a/lib/Controller/MessagesController.php b/lib/Controller/MessagesController.php
index 84ebc9fab8..5a6c8624f0 100755
--- a/lib/Controller/MessagesController.php
+++ b/lib/Controller/MessagesController.php
@@ -29,6 +29,7 @@
use OCA\Mail\Model\SmimeData;
use OCA\Mail\Service\AccountService;
use OCA\Mail\Service\AiIntegrations\AiIntegrationsService;
+use OCA\Mail\Service\DelegationService;
use OCA\Mail\Service\ItineraryService;
use OCA\Mail\Service\SmimeService;
use OCA\Mail\Service\SnoozeService;
@@ -75,6 +76,7 @@ class MessagesController extends Controller {
private IUserPreferences $preferences;
private SnoozeService $snoozeService;
private AiIntegrationsService $aiIntegrationService;
+ private DelegationService $delegationService;
public function __construct(
string $appName,
@@ -99,6 +101,7 @@ public function __construct(
SnoozeService $snoozeService,
AiIntegrationsService $aiIntegrationService,
private ICacheFactory $cacheFactory,
+ DelegationService $delegationService,
) {
parent::__construct($appName, $request);
$this->accountService = $accountService;
@@ -120,6 +123,7 @@ public function __construct(
$this->preferences = $preferences;
$this->snoozeService = $snoozeService;
$this->aiIntegrationService = $aiIntegrationService;
+ $this->delegationService = $delegationService;
}
/**
@@ -150,8 +154,9 @@ public function index(int $mailboxId,
$limit = min(100, max(1, $limit));
try {
- $mailbox = $this->mailManager->getMailbox($this->currentUserId, $mailboxId);
- $account = $this->accountService->find($this->currentUserId, $mailbox->getAccountId());
+ $effectiveUserId = $this->delegationService->resolveMailboxUserId($mailboxId, $this->currentUserId);
+ $mailbox = $this->mailManager->getMailbox($effectiveUserId, $mailboxId);
+ $account = $this->accountService->find($effectiveUserId, $mailbox->getAccountId());
} catch (DoesNotExistException $e) {
return new JSONResponse([], Http::STATUS_FORBIDDEN);
}
@@ -168,7 +173,7 @@ public function index(int $mailboxId,
$filter === '' ? null : $filter,
$cursor,
$limit,
- $this->currentUserId,
+ $effectiveUserId,
$view
);
@@ -193,9 +198,10 @@ public function show(int $id): JSONResponse {
return new JSONResponse([], Http::STATUS_UNAUTHORIZED);
}
try {
- $message = $this->mailManager->getMessage($this->currentUserId, $id);
- $mailbox = $this->mailManager->getMailbox($this->currentUserId, $message->getMailboxId());
- $account = $this->accountService->find($this->currentUserId, $mailbox->getAccountId());
+ $effectiveUserId = $this->delegationService->resolveMessageUserId($id, $this->currentUserId);
+ $message = $this->mailManager->getMessage($effectiveUserId, $id);
+ $mailbox = $this->mailManager->getMailbox($effectiveUserId, $message->getMailboxId());
+ $account = $this->accountService->find($effectiveUserId, $mailbox->getAccountId());
} catch (DoesNotExistException $e) {
return new JSONResponse([], Http::STATUS_FORBIDDEN);
}
@@ -227,9 +233,10 @@ public function getBody(int $id): JSONResponse {
return new JSONResponse([], Http::STATUS_UNAUTHORIZED);
}
try {
- $message = $this->mailManager->getMessage($this->currentUserId, $id);
- $mailbox = $this->mailManager->getMailbox($this->currentUserId, $message->getMailboxId());
- $account = $this->accountService->find($this->currentUserId, $mailbox->getAccountId());
+ $effectiveUserId = $this->delegationService->resolveMessageUserId($id, $this->currentUserId);
+ $message = $this->mailManager->getMessage($effectiveUserId, $id);
+ $mailbox = $this->mailManager->getMailbox($effectiveUserId, $message->getMailboxId());
+ $account = $this->accountService->find($effectiveUserId, $mailbox->getAccountId());
} catch (DoesNotExistException $e) {
return new JSONResponse([], Http::STATUS_FORBIDDEN);
}
@@ -304,9 +311,10 @@ public function getItineraries(int $id): JSONResponse {
return new JSONResponse([], Http::STATUS_UNAUTHORIZED);
}
try {
- $message = $this->mailManager->getMessage($this->currentUserId, $id);
- $mailbox = $this->mailManager->getMailbox($this->currentUserId, $message->getMailboxId());
- $account = $this->accountService->find($this->currentUserId, $mailbox->getAccountId());
+ $effectiveUserId = $this->delegationService->resolveMessageUserId($id, $this->currentUserId);
+ $message = $this->mailManager->getMessage($effectiveUserId, $id);
+ $mailbox = $this->mailManager->getMailbox($effectiveUserId, $message->getMailboxId());
+ $account = $this->accountService->find($effectiveUserId, $mailbox->getAccountId());
} catch (DoesNotExistException $e) {
return new JSONResponse([], Http::STATUS_FORBIDDEN);
}
@@ -326,9 +334,10 @@ public function getDkim(int $id): JSONResponse {
return new JSONResponse([], Http::STATUS_UNAUTHORIZED);
}
try {
- $message = $this->mailManager->getMessage($this->currentUserId, $id);
- $mailbox = $this->mailManager->getMailbox($this->currentUserId, $message->getMailboxId());
- $account = $this->accountService->find($this->currentUserId, $mailbox->getAccountId());
+ $effectiveUserId = $this->delegationService->resolveMessageUserId($id, $this->currentUserId);
+ $message = $this->mailManager->getMessage($effectiveUserId, $id);
+ $mailbox = $this->mailManager->getMailbox($effectiveUserId, $message->getMailboxId());
+ $account = $this->accountService->find($effectiveUserId, $mailbox->getAccountId());
} catch (DoesNotExistException $e) {
return new JSONResponse([], Http::STATUS_FORBIDDEN);
}
@@ -369,9 +378,10 @@ public function getThread(int $id): JSONResponse {
return new JSONResponse([], Http::STATUS_UNAUTHORIZED);
}
try {
- $message = $this->mailManager->getMessage($this->currentUserId, $id);
- $mailbox = $this->mailManager->getMailbox($this->currentUserId, $message->getMailboxId());
- $account = $this->accountService->find($this->currentUserId, $mailbox->getAccountId());
+ $effectiveUserId = $this->delegationService->resolveMessageUserId($id, $this->currentUserId);
+ $message = $this->mailManager->getMessage($effectiveUserId, $id);
+ $mailbox = $this->mailManager->getMailbox($effectiveUserId, $message->getMailboxId());
+ $account = $this->accountService->find($effectiveUserId, $mailbox->getAccountId());
} catch (DoesNotExistException $e) {
return new JSONResponse([], Http::STATUS_FORBIDDEN);
}
@@ -400,11 +410,12 @@ public function move(int $id, int $destFolderId): JSONResponse {
return new JSONResponse([], Http::STATUS_UNAUTHORIZED);
}
try {
- $message = $this->mailManager->getMessage($this->currentUserId, $id);
- $srcMailbox = $this->mailManager->getMailbox($this->currentUserId, $message->getMailboxId());
- $dstMailbox = $this->mailManager->getMailbox($this->currentUserId, $destFolderId);
- $srcAccount = $this->accountService->find($this->currentUserId, $srcMailbox->getAccountId());
- $dstAccount = $this->accountService->find($this->currentUserId, $dstMailbox->getAccountId());
+ $effectiveUserId = $this->delegationService->resolveMessageUserId($id, $this->currentUserId);
+ $message = $this->mailManager->getMessage($effectiveUserId, $id);
+ $srcMailbox = $this->mailManager->getMailbox($effectiveUserId, $message->getMailboxId());
+ $dstMailbox = $this->mailManager->getMailbox($effectiveUserId, $destFolderId);
+ $srcAccount = $this->accountService->find($effectiveUserId, $srcMailbox->getAccountId());
+ $dstAccount = $this->accountService->find($effectiveUserId, $dstMailbox->getAccountId());
} catch (DoesNotExistException $e) {
return new JSONResponse([], Http::STATUS_FORBIDDEN);
}
@@ -416,6 +427,9 @@ public function move(int $id, int $destFolderId): JSONResponse {
$dstAccount,
$dstMailbox->getName()
);
+
+ $this->delegationService->logDelegatedAction("$this->currentUserId moved message <$id> to mailbox <$destFolderId> on behalf of $effectiveUserId");
+
return new JSONResponse();
}
@@ -436,16 +450,18 @@ public function snooze(int $id, int $unixTimestamp, int $destMailboxId): JSONRes
return new JSONResponse([], Http::STATUS_UNAUTHORIZED);
}
try {
- $message = $this->mailManager->getMessage($this->currentUserId, $id);
- $srcMailbox = $this->mailManager->getMailbox($this->currentUserId, $message->getMailboxId());
- $dstMailbox = $this->mailManager->getMailbox($this->currentUserId, $destMailboxId);
- $srcAccount = $this->accountService->find($this->currentUserId, $srcMailbox->getAccountId());
- $dstAccount = $this->accountService->find($this->currentUserId, $dstMailbox->getAccountId());
+ $effectiveUserId = $this->delegationService->resolveMessageUserId($id, $this->currentUserId);
+ $message = $this->mailManager->getMessage($effectiveUserId, $id);
+ $srcMailbox = $this->mailManager->getMailbox($effectiveUserId, $message->getMailboxId());
+ $dstMailbox = $this->mailManager->getMailbox($effectiveUserId, $destMailboxId);
+ $srcAccount = $this->accountService->find($effectiveUserId, $srcMailbox->getAccountId());
+ $dstAccount = $this->accountService->find($effectiveUserId, $dstMailbox->getAccountId());
} catch (DoesNotExistException $e) {
return new JSONResponse([], Http::STATUS_FORBIDDEN);
}
$this->snoozeService->snoozeMessage($message, $unixTimestamp, $srcAccount, $srcMailbox, $dstAccount, $dstMailbox);
+ $this->delegationService->logDelegatedAction("$this->currentUserId snoozed message <$id> to <$unixTimestamp> on behalf of $effectiveUserId");
return new JSONResponse();
}
@@ -465,12 +481,14 @@ public function unSnooze(int $id): JSONResponse {
return new JSONResponse([], Http::STATUS_UNAUTHORIZED);
}
try {
- $message = $this->mailManager->getMessage($this->currentUserId, $id);
+ $effectiveUserId = $this->delegationService->resolveMessageUserId($id, $this->currentUserId);
+ $message = $this->mailManager->getMessage($effectiveUserId, $id);
} catch (DoesNotExistException $e) {
return new JSONResponse([], Http::STATUS_FORBIDDEN);
}
- $this->snoozeService->unSnoozeMessage($message, $this->currentUserId);
+ $this->snoozeService->unSnoozeMessage($message, $effectiveUserId);
+ $this->delegationService->logDelegatedAction("$this->currentUserId unsnoozed message <$id> on behalf of $effectiveUserId");
return new JSONResponse();
}
@@ -491,9 +509,10 @@ public function mdn(int $id): JSONResponse {
return new JSONResponse([], Http::STATUS_UNAUTHORIZED);
}
try {
- $message = $this->mailManager->getMessage($this->currentUserId, $id);
- $mailbox = $this->mailManager->getMailbox($this->currentUserId, $message->getMailboxId());
- $account = $this->accountService->find($this->currentUserId, $mailbox->getAccountId());
+ $effectiveUserId = $this->delegationService->resolveMessageUserId($id, $this->currentUserId);
+ $message = $this->mailManager->getMessage($effectiveUserId, $id);
+ $mailbox = $this->mailManager->getMailbox($effectiveUserId, $message->getMailboxId());
+ $account = $this->accountService->find($effectiveUserId, $mailbox->getAccountId());
} catch (DoesNotExistException $e) {
return new JSONResponse([], Http::STATUS_FORBIDDEN);
}
@@ -525,9 +544,10 @@ public function getSource(int $id): JSONResponse {
return new JSONResponse([], Http::STATUS_UNAUTHORIZED);
}
try {
- $message = $this->mailManager->getMessage($this->currentUserId, $id);
- $mailbox = $this->mailManager->getMailbox($this->currentUserId, $message->getMailboxId());
- $account = $this->accountService->find($this->currentUserId, $mailbox->getAccountId());
+ $effectiveUserId = $this->delegationService->resolveMessageUserId($id, $this->currentUserId);
+ $message = $this->mailManager->getMessage($effectiveUserId, $id);
+ $mailbox = $this->mailManager->getMailbox($effectiveUserId, $message->getMailboxId());
+ $account = $this->accountService->find($effectiveUserId, $mailbox->getAccountId());
} catch (DoesNotExistException $e) {
return new JSONResponse([], Http::STATUS_FORBIDDEN);
}
@@ -569,9 +589,10 @@ public function export(int $id): Response {
return new JSONResponse([], Http::STATUS_UNAUTHORIZED);
}
try {
- $message = $this->mailManager->getMessage($this->currentUserId, $id);
- $mailbox = $this->mailManager->getMailbox($this->currentUserId, $message->getMailboxId());
- $account = $this->accountService->find($this->currentUserId, $mailbox->getAccountId());
+ $effectiveUserId = $this->delegationService->resolveMessageUserId($id, $this->currentUserId);
+ $message = $this->mailManager->getMessage($effectiveUserId, $id);
+ $mailbox = $this->mailManager->getMailbox($effectiveUserId, $message->getMailboxId());
+ $account = $this->accountService->find($effectiveUserId, $mailbox->getAccountId());
} catch (DoesNotExistException $e) {
return new JSONResponse([], Http::STATUS_FORBIDDEN);
}
@@ -619,9 +640,10 @@ public function getHtmlBody(int $id, bool $plain = false): Response {
}
try {
try {
- $message = $this->mailManager->getMessage($this->currentUserId, $id);
- $mailbox = $this->mailManager->getMailbox($this->currentUserId, $message->getMailboxId());
- $account = $this->accountService->find($this->currentUserId, $mailbox->getAccountId());
+ $effectiveUserId = $this->delegationService->resolveMessageUserId($id, $this->currentUserId);
+ $message = $this->mailManager->getMessage($effectiveUserId, $id);
+ $mailbox = $this->mailManager->getMailbox($effectiveUserId, $message->getMailboxId());
+ $account = $this->accountService->find($effectiveUserId, $mailbox->getAccountId());
} catch (DoesNotExistException) {
return new TemplateResponse(
$this->appName,
@@ -706,9 +728,10 @@ public function downloadAttachment(int $id,
return new JSONResponse([], Http::STATUS_UNAUTHORIZED);
}
try {
- $message = $this->mailManager->getMessage($this->currentUserId, $id);
- $mailbox = $this->mailManager->getMailbox($this->currentUserId, $message->getMailboxId());
- $account = $this->accountService->find($this->currentUserId, $mailbox->getAccountId());
+ $effectiveUserId = $this->delegationService->resolveMessageUserId($id, $this->currentUserId);
+ $message = $this->mailManager->getMessage($effectiveUserId, $id);
+ $mailbox = $this->mailManager->getMailbox($effectiveUserId, $message->getMailboxId());
+ $account = $this->accountService->find($effectiveUserId, $mailbox->getAccountId());
} catch (DoesNotExistException $e) {
return new JSONResponse([], Http::STATUS_FORBIDDEN);
}
@@ -755,9 +778,10 @@ public function downloadAttachments(int $id): Response {
return new JSONResponse([], Http::STATUS_UNAUTHORIZED);
}
try {
- $message = $this->mailManager->getMessage($this->currentUserId, $id);
- $mailbox = $this->mailManager->getMailbox($this->currentUserId, $message->getMailboxId());
- $account = $this->accountService->find($this->currentUserId, $mailbox->getAccountId());
+ $effectiveUserId = $this->delegationService->resolveMessageUserId($id, $this->currentUserId);
+ $message = $this->mailManager->getMessage($effectiveUserId, $id);
+ $mailbox = $this->mailManager->getMailbox($effectiveUserId, $message->getMailboxId());
+ $account = $this->accountService->find($effectiveUserId, $mailbox->getAccountId());
} catch (DoesNotExistException $e) {
return new JSONResponse([], Http::STATUS_FORBIDDEN);
}
@@ -801,9 +825,10 @@ public function saveAttachment(int $id,
return new JSONResponse([], Http::STATUS_UNAUTHORIZED);
}
try {
- $message = $this->mailManager->getMessage($this->currentUserId, $id);
- $mailbox = $this->mailManager->getMailbox($this->currentUserId, $message->getMailboxId());
- $account = $this->accountService->find($this->currentUserId, $mailbox->getAccountId());
+ $effectiveUserId = $this->delegationService->resolveMessageUserId($id, $this->currentUserId);
+ $message = $this->mailManager->getMessage($effectiveUserId, $id);
+ $mailbox = $this->mailManager->getMailbox($effectiveUserId, $message->getMailboxId());
+ $account = $this->accountService->find($effectiveUserId, $mailbox->getAccountId());
} catch (DoesNotExistException $e) {
return new JSONResponse([], Http::STATUS_FORBIDDEN);
}
@@ -862,17 +887,22 @@ public function setFlags(int $id, array $flags): JSONResponse {
return new JSONResponse([], Http::STATUS_UNAUTHORIZED);
}
try {
- $message = $this->mailManager->getMessage($this->currentUserId, $id);
- $mailbox = $this->mailManager->getMailbox($this->currentUserId, $message->getMailboxId());
- $account = $this->accountService->find($this->currentUserId, $mailbox->getAccountId());
+ $effectiveUserId = $this->delegationService->resolveMessageUserId($id, $this->currentUserId);
+ $message = $this->mailManager->getMessage($effectiveUserId, $id);
+ $mailbox = $this->mailManager->getMailbox($effectiveUserId, $message->getMailboxId());
+ $account = $this->accountService->find($effectiveUserId, $mailbox->getAccountId());
} catch (DoesNotExistException $e) {
return new JSONResponse([], Http::STATUS_FORBIDDEN);
}
+ $flagChanges = [];
foreach ($flags as $flag => $value) {
$value = filter_var($value, FILTER_VALIDATE_BOOLEAN);
$this->mailManager->flagMessage($account, $mailbox->getName(), $message->getUid(), $flag, $value);
+ $flagChanges[] = "$flag=" . ($value ? 'true' : 'false');
}
+ $flagsSummary = implode(', ', $flagChanges);
+ $this->delegationService->logDelegatedAction("$this->currentUserId updated flags on message <$id> with [$flagsSummary] on behalf of $effectiveUserId");
return new JSONResponse();
}
@@ -893,9 +923,10 @@ public function setTag(int $id, string $imapLabel): JSONResponse {
return new JSONResponse([], Http::STATUS_UNAUTHORIZED);
}
try {
- $message = $this->mailManager->getMessage($this->currentUserId, $id);
- $mailbox = $this->mailManager->getMailbox($this->currentUserId, $message->getMailboxId());
- $account = $this->accountService->find($this->currentUserId, $mailbox->getAccountId());
+ $effectiveUserId = $this->delegationService->resolveMessageUserId($id, $this->currentUserId);
+ $message = $this->mailManager->getMessage($effectiveUserId, $id);
+ $mailbox = $this->mailManager->getMailbox($effectiveUserId, $message->getMailboxId());
+ $account = $this->accountService->find($effectiveUserId, $mailbox->getAccountId());
} catch (DoesNotExistException $e) {
return new JSONResponse([], Http::STATUS_FORBIDDEN);
}
@@ -907,6 +938,7 @@ public function setTag(int $id, string $imapLabel): JSONResponse {
}
$this->mailManager->tagMessage($account, $mailbox->getName(), $message, $tag, true);
+ $this->delegationService->logDelegatedAction("$this->currentUserId added tag <$imapLabel> on message <$id> on behalf of $effectiveUserId");
return new JSONResponse($tag);
}
@@ -927,9 +959,10 @@ public function removeTag(int $id, string $imapLabel): JSONResponse {
return new JSONResponse([], Http::STATUS_UNAUTHORIZED);
}
try {
- $message = $this->mailManager->getMessage($this->currentUserId, $id);
- $mailbox = $this->mailManager->getMailbox($this->currentUserId, $message->getMailboxId());
- $account = $this->accountService->find($this->currentUserId, $mailbox->getAccountId());
+ $effectiveUserId = $this->delegationService->resolveMessageUserId($id, $this->currentUserId);
+ $message = $this->mailManager->getMessage($effectiveUserId, $id);
+ $mailbox = $this->mailManager->getMailbox($effectiveUserId, $message->getMailboxId());
+ $account = $this->accountService->find($effectiveUserId, $mailbox->getAccountId());
} catch (DoesNotExistException $e) {
return new JSONResponse([], Http::STATUS_FORBIDDEN);
}
@@ -941,6 +974,7 @@ public function removeTag(int $id, string $imapLabel): JSONResponse {
}
$this->mailManager->tagMessage($account, $mailbox->getName(), $message, $tag, false);
+ $this->delegationService->logDelegatedAction("$this->currentUserId removed tag <$imapLabel> on message <$id> on behalf of $effectiveUserId");
return new JSONResponse($tag);
}
@@ -958,9 +992,10 @@ public function destroy(int $id): JSONResponse {
return new JSONResponse([], Http::STATUS_UNAUTHORIZED);
}
try {
- $message = $this->mailManager->getMessage($this->currentUserId, $id);
- $mailbox = $this->mailManager->getMailbox($this->currentUserId, $message->getMailboxId());
- $account = $this->accountService->find($this->currentUserId, $mailbox->getAccountId());
+ $effectiveUserId = $this->delegationService->resolveMessageUserId($id, $this->currentUserId);
+ $message = $this->mailManager->getMessage($effectiveUserId, $id);
+ $mailbox = $this->mailManager->getMailbox($effectiveUserId, $message->getMailboxId());
+ $account = $this->accountService->find($effectiveUserId, $mailbox->getAccountId());
} catch (DoesNotExistException $e) {
return new JSONResponse([], Http::STATUS_FORBIDDEN);
}
@@ -972,6 +1007,7 @@ public function destroy(int $id): JSONResponse {
$mailbox->getName(),
$message->getUid()
);
+ $this->delegationService->logDelegatedAction("$this->currentUserId deleted message <$id> on behalf of $effectiveUserId");
return new JSONResponse();
}
@@ -988,14 +1024,16 @@ public function smartReply(int $messageId):JSONResponse {
return new JSONResponse([], Http::STATUS_UNAUTHORIZED);
}
try {
- $message = $this->mailManager->getMessage($this->currentUserId, $messageId);
- $mailbox = $this->mailManager->getMailbox($this->currentUserId, $message->getMailboxId());
- $account = $this->accountService->find($this->currentUserId, $mailbox->getAccountId());
+ $effectiveUserId = $this->delegationService->resolveMessageUserId($messageId, $this->currentUserId);
+ $message = $this->mailManager->getMessage($effectiveUserId, $messageId);
+ $mailbox = $this->mailManager->getMailbox($effectiveUserId, $message->getMailboxId());
+ $account = $this->accountService->find($effectiveUserId, $mailbox->getAccountId());
} catch (DoesNotExistException $e) {
return new JSONResponse([], Http::STATUS_FORBIDDEN);
}
try {
- $replies = array_values($this->aiIntegrationService->getSmartReply($account, $mailbox, $message, $this->currentUserId));
+ $smartReplies = $this->aiIntegrationService->getSmartReply($account, $mailbox, $message, $effectiveUserId);
+ $replies = $smartReplies !== null ? array_values($smartReplies) : [];
} catch (ServiceException $e) {
$this->logger->error('Smart reply failed: ' . $e->getMessage(), [
'exception' => $e,
@@ -1019,9 +1057,10 @@ public function needsTranslation(int $messageId): JSONResponse {
return new JSONResponse([], Http::STATUS_FORBIDDEN);
}
try {
- $message = $this->mailManager->getMessage($this->currentUserId, $messageId);
- $mailbox = $this->mailManager->getMailbox($this->currentUserId, $message->getMailboxId());
- $account = $this->accountService->find($this->currentUserId, $mailbox->getAccountId());
+ $effectiveUserId = $this->delegationService->resolveMessageUserId($messageId, $this->currentUserId);
+ $message = $this->mailManager->getMessage($effectiveUserId, $messageId);
+ $mailbox = $this->mailManager->getMailbox($effectiveUserId, $message->getMailboxId());
+ $account = $this->accountService->find($effectiveUserId, $mailbox->getAccountId());
} catch (DoesNotExistException $e) {
return new JSONResponse([], Http::STATUS_FORBIDDEN);
}
@@ -1037,7 +1076,7 @@ public function needsTranslation(int $messageId): JSONResponse {
$account,
$mailbox,
$message,
- $this->currentUserId
+ $effectiveUserId
);
$response = new JSONResponse(['requiresTranslation' => $requiresTranslation === true]);
$response->cacheFor(60 * 60 * 24, false, true);
diff --git a/lib/Controller/OutboxController.php b/lib/Controller/OutboxController.php
index f73c2551f6..21b73c8a3a 100644
--- a/lib/Controller/OutboxController.php
+++ b/lib/Controller/OutboxController.php
@@ -14,6 +14,7 @@
use OCA\Mail\Http\JsonResponse;
use OCA\Mail\Http\TrapError;
use OCA\Mail\Service\AccountService;
+use OCA\Mail\Service\DelegationService;
use OCA\Mail\Service\DraftsService;
use OCA\Mail\Service\OutboxService;
use OCA\Mail\Service\SmimeService;
@@ -29,18 +30,21 @@ class OutboxController extends Controller {
private string $userId;
private AccountService $accountService;
private SmimeService $smimeService;
+ private DelegationService $delegationService;
public function __construct(string $appName,
$userId,
IRequest $request,
OutboxService $service,
AccountService $accountService,
- SmimeService $smimeService) {
+ SmimeService $smimeService,
+ DelegationService $delegationService) {
parent::__construct($appName, $request);
$this->userId = $userId;
$this->service = $service;
$this->accountService = $accountService;
$this->smimeService = $smimeService;
+ $this->delegationService = $delegationService;
}
/**
@@ -61,7 +65,8 @@ public function index(): JsonResponse {
*/
#[TrapError]
public function show(int $id): JsonResponse {
- $message = $this->service->getMessage($id, $this->userId);
+ $effectiveUserId = $this->delegationService->resolveLocalMessageUserId($id, $this->userId);
+ $message = $this->service->getMessage($id, $effectiveUserId);
return JsonResponse::success($message);
}
@@ -108,7 +113,8 @@ public function create(
bool $requestMdn = false,
bool $isPgpMime = false,
): JsonResponse {
- $account = $this->accountService->find($this->userId, $accountId);
+ $effectiveUserId = $this->delegationService->resolveAccountUserId($accountId, $this->userId);
+ $account = $this->accountService->find($effectiveUserId, $accountId);
if ($draftId !== null) {
$this->service->handleDraft($account, $draftId);
@@ -136,6 +142,7 @@ public function create(
}
$this->service->saveMessage($account, $message, $to, $cc, $bcc, $attachments);
+ $this->delegationService->logDelegatedAction("$this->userId created an outbox message for account <$accountId> on behalf of $effectiveUserId");
return JsonResponse::success($message, Http::STATUS_CREATED);
}
@@ -147,11 +154,13 @@ public function create(
*/
#[TrapError]
public function createFromDraft(DraftsService $draftsService, int $id, int $sendAt): JsonResponse {
- $draftMessage = $draftsService->getMessage($id, $this->userId);
+ $effectiveUserId = $this->delegationService->resolveLocalMessageUserId($id, $this->userId);
+ $draftMessage = $draftsService->getMessage($id, $effectiveUserId);
// Locate the account to check authorization
- $this->accountService->find($this->userId, $draftMessage->getAccountId());
+ $this->accountService->find($effectiveUserId, $draftMessage->getAccountId());
$outboxMessage = $this->service->convertDraft($draftMessage, $sendAt);
+ $this->delegationService->logDelegatedAction("$this->userId created an outbox message from draft <$id> on behalf of $effectiveUserId");
return JsonResponse::success(
$outboxMessage,
@@ -201,11 +210,12 @@ public function update(
bool $requestMdn = false,
bool $isPgpMime = false,
): JsonResponse {
- $message = $this->service->getMessage($id, $this->userId);
+ $effectiveUserId = $this->delegationService->resolveAccountUserId($accountId, $this->userId);
+ $message = $this->service->getMessage($id, $effectiveUserId);
if ($message->getStatus() === LocalMessage::STATUS_PROCESSED) {
return JsonResponse::error('Cannot modify already sent message', Http::STATUS_FORBIDDEN, [$message]);
}
- $account = $this->accountService->find($this->userId, $accountId);
+ $account = $this->accountService->find($effectiveUserId, $accountId);
$message->setAccountId($accountId);
$message->setAliasId($aliasId);
@@ -227,6 +237,7 @@ public function update(
}
$message = $this->service->updateMessage($account, $message, $to, $cc, $bcc, $attachments);
+ $this->delegationService->logDelegatedAction("$this->userId updated outbox message <$id> for account <$accountId> on behalf of $effectiveUserId");
return JsonResponse::success($message, Http::STATUS_ACCEPTED);
}
@@ -239,12 +250,18 @@ public function update(
*/
#[TrapError]
public function send(int $id): JsonResponse {
- $message = $this->service->getMessage($id, $this->userId);
- $account = $this->accountService->find($this->userId, $message->getAccountId());
+ $effectiveUserId = $this->delegationService->resolveLocalMessageUserId($id, $this->userId);
+ $message = $this->service->getMessage($id, $effectiveUserId);
+ $account = $this->accountService->find($effectiveUserId, $message->getAccountId());
$message = $this->service->sendMessage($message, $account);
+ $status = $message->getStatus();
+ $this->delegationService->logDelegatedAction(match ($status) {
+ LocalMessage::STATUS_PROCESSED => "$this->userId sent outbox message <$id> on behalf of $effectiveUserId",
+ default => "$this->userId attempted sending outbox message <$id> on behalf of $effectiveUserId but sending failed",
+ });
- if ($message->getStatus() !== LocalMessage::STATUS_PROCESSED) {
+ if ($status !== LocalMessage::STATUS_PROCESSED) {
return JsonResponse::error('Could not send message', Http::STATUS_INTERNAL_SERVER_ERROR, [$message]);
}
return JsonResponse::success(
@@ -260,8 +277,10 @@ public function send(int $id): JsonResponse {
*/
#[TrapError]
public function destroy(int $id): JsonResponse {
- $message = $this->service->getMessage($id, $this->userId);
- $this->service->deleteMessage($this->userId, $message);
+ $effectiveUserId = $this->delegationService->resolveLocalMessageUserId($id, $this->userId);
+ $message = $this->service->getMessage($id, $effectiveUserId);
+ $this->service->deleteMessage($effectiveUserId, $message);
+ $this->delegationService->logDelegatedAction("$this->userId deleted outbox message <$id> on behalf of $effectiveUserId");
return JsonResponse::success('Message deleted', Http::STATUS_ACCEPTED);
}
}
diff --git a/lib/Controller/PageController.php b/lib/Controller/PageController.php
index 9c1f3ea6f0..0994fa9491 100644
--- a/lib/Controller/PageController.php
+++ b/lib/Controller/PageController.php
@@ -158,6 +158,7 @@ public function index(): TemplateResponse {
$accountsJson = [];
foreach ($mailAccounts as $mailAccount) {
$json = $mailAccount->jsonSerialize();
+ $json['isDelegated'] = false;
$json['aliases'] = $this->aliasesService->findAll($mailAccount->getId(),
$this->currentUserId);
try {
@@ -172,6 +173,26 @@ public function index(): TemplateResponse {
}
$accountsJson[] = $json;
}
+
+ $delegatedAccounts = $this->accountService->findDelegatedAccounts($this->currentUserId);
+ foreach ($delegatedAccounts as $delegatedAccount) {
+ $json = $delegatedAccount->jsonSerialize();
+ $json['isDelegated'] = true;
+ $json['aliases'] = $this->aliasesService->findAll($delegatedAccount->getId(),
+ $delegatedAccount->getUserId());
+ try {
+ $mailboxes = $this->mailManager->getMailboxes($delegatedAccount);
+ $json['mailboxes'] = $mailboxes;
+ } catch (Throwable $ex) {
+ $this->logger->critical('Could not load delegated account mailboxes: ' . $ex->getMessage(), [
+ 'exception' => $ex,
+ ]);
+ $json['mailboxes'] = [];
+ $json['error'] = true;
+ }
+ $accountsJson[] = $json;
+ }
+
$this->initialStateService->provideInitialState(
'accounts',
$accountsJson
diff --git a/lib/Controller/SieveController.php b/lib/Controller/SieveController.php
index e38988cccc..6a4f08139f 100644
--- a/lib/Controller/SieveController.php
+++ b/lib/Controller/SieveController.php
@@ -16,6 +16,7 @@
use OCA\Mail\Exception\CouldNotConnectException;
use OCA\Mail\Http\JsonResponse as MailJsonResponse;
use OCA\Mail\Http\TrapError;
+use OCA\Mail\Service\DelegationService;
use OCA\Mail\Service\SieveService;
use OCA\Mail\Sieve\SieveClientFactory;
use OCP\AppFramework\Controller;
@@ -46,6 +47,7 @@ public function __construct(
IRemoteHostValidator $hostValidator,
LoggerInterface $logger,
private SieveService $sieveService,
+ private DelegationService $delegationService,
) {
parent::__construct(Application::APP_ID, $request);
$this->currentUserId = $userId;
@@ -69,7 +71,8 @@ public function __construct(
*/
#[TrapError]
public function getActiveScript(int $id): JSONResponse {
- $activeScript = $this->sieveService->getActiveScript($this->currentUserId, $id);
+ $effectiveUserId = $this->delegationService->resolveAccountUserId($id, $this->currentUserId);
+ $activeScript = $this->sieveService->getActiveScript($effectiveUserId, $id);
return new JSONResponse([
'scriptName' => $activeScript->getName(),
'script' => $activeScript->getScript(),
@@ -89,12 +92,14 @@ public function getActiveScript(int $id): JSONResponse {
*/
#[TrapError]
public function updateActiveScript(int $id, string $script): JSONResponse {
+ $effectiveUserId = $this->delegationService->resolveAccountUserId($id, $this->currentUserId);
try {
- $this->sieveService->updateActiveScript($this->currentUserId, $id, $script);
+ $this->sieveService->updateActiveScript($effectiveUserId, $id, $script);
} catch (ManagesieveException $e) {
$this->logger->error('Installing sieve script failed: ' . $e->getMessage(), ['app' => 'mail', 'exception' => $e]);
return new JSONResponse(data: ['message' => $e->getMessage()], statusCode: Http::STATUS_UNPROCESSABLE_ENTITY);
}
+ $this->delegationService->logDelegatedAction("$this->currentUserId updated the active sieve script for account <$id> on behalf of $effectiveUserId");
return new JSONResponse();
}
@@ -135,7 +140,8 @@ public function updateAccount(int $id,
Http::STATUS_UNPROCESSABLE_ENTITY
);
}
- $mailAccount = $this->mailAccountMapper->find($this->currentUserId, $id);
+ $effectiveUserId = $this->delegationService->resolveAccountUserId($id, $this->currentUserId);
+ $mailAccount = $this->mailAccountMapper->find($effectiveUserId, $id);
if ($sieveEnabled === false) {
$mailAccount->setSieveEnabled(false);
@@ -177,6 +183,7 @@ public function updateAccount(int $id,
}
$this->mailAccountMapper->save($mailAccount);
+ $this->delegationService->logDelegatedAction("$this->currentUserId updated sieve settings for account <$id> on behalf of $effectiveUserId");
return new JSONResponse(['sieveEnabled' => $mailAccount->isSieveEnabled()]);
}
}
diff --git a/lib/Controller/ThreadController.php b/lib/Controller/ThreadController.php
index 96aa68977f..64933823c2 100755
--- a/lib/Controller/ThreadController.php
+++ b/lib/Controller/ThreadController.php
@@ -15,6 +15,7 @@
use OCA\Mail\Http\TrapError;
use OCA\Mail\Service\AccountService;
use OCA\Mail\Service\AiIntegrations\AiIntegrationsService;
+use OCA\Mail\Service\DelegationService;
use OCA\Mail\Service\SnoozeService;
use OCP\AppFramework\Controller;
use OCP\AppFramework\Db\DoesNotExistException;
@@ -32,6 +33,7 @@ class ThreadController extends Controller {
private SnoozeService $snoozeService;
private AiIntegrationsService $aiIntergrationsService;
private LoggerInterface $logger;
+ private DelegationService $delegationService;
public function __construct(string $appName,
@@ -41,7 +43,8 @@ public function __construct(string $appName,
IMailManager $mailManager,
SnoozeService $snoozeService,
AiIntegrationsService $aiIntergrationsService,
- LoggerInterface $logger) {
+ LoggerInterface $logger,
+ DelegationService $delegationService) {
parent::__construct($appName, $request);
$this->currentUserId = $userId;
$this->accountService = $accountService;
@@ -49,6 +52,7 @@ public function __construct(string $appName,
$this->snoozeService = $snoozeService;
$this->aiIntergrationsService = $aiIntergrationsService;
$this->logger = $logger;
+ $this->delegationService = $delegationService;
}
/**
@@ -64,11 +68,12 @@ public function __construct(string $appName,
#[TrapError]
public function move(int $id, int $destMailboxId): JSONResponse {
try {
- $message = $this->mailManager->getMessage($this->currentUserId, $id);
- $srcMailbox = $this->mailManager->getMailbox($this->currentUserId, $message->getMailboxId());
- $srcAccount = $this->accountService->find($this->currentUserId, $srcMailbox->getAccountId());
- $dstMailbox = $this->mailManager->getMailbox($this->currentUserId, $destMailboxId);
- $dstAccount = $this->accountService->find($this->currentUserId, $dstMailbox->getAccountId());
+ $effectiveUserId = $this->delegationService->resolveMessageUserId($id, $this->currentUserId);
+ $message = $this->mailManager->getMessage($effectiveUserId, $id);
+ $srcMailbox = $this->mailManager->getMailbox($effectiveUserId, $message->getMailboxId());
+ $srcAccount = $this->accountService->find($effectiveUserId, $srcMailbox->getAccountId());
+ $dstMailbox = $this->mailManager->getMailbox($effectiveUserId, $destMailboxId);
+ $dstAccount = $this->accountService->find($effectiveUserId, $dstMailbox->getAccountId());
} catch (DoesNotExistException $e) {
return new JSONResponse([], Http::STATUS_FORBIDDEN);
}
@@ -80,6 +85,7 @@ public function move(int $id, int $destMailboxId): JSONResponse {
$dstMailbox,
$message->getThreadRootId()
);
+ $this->delegationService->logDelegatedAction("$this->currentUserId moved thread <$id> to mailbox <$destMailboxId> on behalf of $effectiveUserId");
return new JSONResponse();
}
@@ -98,16 +104,18 @@ public function move(int $id, int $destMailboxId): JSONResponse {
#[TrapError]
public function snooze(int $id, int $unixTimestamp, int $destMailboxId): JSONResponse {
try {
- $selectedMessage = $this->mailManager->getMessage($this->currentUserId, $id);
- $srcMailbox = $this->mailManager->getMailbox($this->currentUserId, $selectedMessage->getMailboxId());
- $srcAccount = $this->accountService->find($this->currentUserId, $srcMailbox->getAccountId());
- $dstMailbox = $this->mailManager->getMailbox($this->currentUserId, $destMailboxId);
- $dstAccount = $this->accountService->find($this->currentUserId, $dstMailbox->getAccountId());
+ $effectiveUserId = $this->delegationService->resolveMessageUserId($id, $this->currentUserId);
+ $selectedMessage = $this->mailManager->getMessage($effectiveUserId, $id);
+ $srcMailbox = $this->mailManager->getMailbox($effectiveUserId, $selectedMessage->getMailboxId());
+ $srcAccount = $this->accountService->find($effectiveUserId, $srcMailbox->getAccountId());
+ $dstMailbox = $this->mailManager->getMailbox($effectiveUserId, $destMailboxId);
+ $dstAccount = $this->accountService->find($effectiveUserId, $dstMailbox->getAccountId());
} catch (DoesNotExistException $e) {
return new JSONResponse([], Http::STATUS_FORBIDDEN);
}
$this->snoozeService->snoozeThread($selectedMessage, $unixTimestamp, $srcAccount, $srcMailbox, $dstAccount, $dstMailbox);
+ $this->delegationService->logDelegatedAction("$this->currentUserId snoozed thread <$id> until <$unixTimestamp> in mailbox <$destMailboxId> on behalf of $effectiveUserId");
return new JSONResponse();
}
@@ -124,12 +132,14 @@ public function snooze(int $id, int $unixTimestamp, int $destMailboxId): JSONRes
#[TrapError]
public function unSnooze(int $id): JSONResponse {
try {
- $selectedMessage = $this->mailManager->getMessage($this->currentUserId, $id);
+ $effectiveUserId = $this->delegationService->resolveMessageUserId($id, $this->currentUserId);
+ $selectedMessage = $this->mailManager->getMessage($effectiveUserId, $id);
} catch (DoesNotExistException $e) {
return new JSONResponse([], Http::STATUS_FORBIDDEN);
}
- $this->snoozeService->unSnoozeThread($selectedMessage, $this->currentUserId);
+ $this->snoozeService->unSnoozeThread($selectedMessage, $effectiveUserId);
+ $this->delegationService->logDelegatedAction("$this->currentUserId unsnoozed thread <$id> on behalf of $effectiveUserId");
return new JSONResponse();
}
@@ -143,9 +153,10 @@ public function unSnooze(int $id): JSONResponse {
*/
public function summarize(int $id): JSONResponse {
try {
- $message = $this->mailManager->getMessage($this->currentUserId, $id);
- $mailbox = $this->mailManager->getMailbox($this->currentUserId, $message->getMailboxId());
- $account = $this->accountService->find($this->currentUserId, $mailbox->getAccountId());
+ $effectiveUserId = $this->delegationService->resolveMessageUserId($id, $this->currentUserId);
+ $message = $this->mailManager->getMessage($effectiveUserId, $id);
+ $mailbox = $this->mailManager->getMailbox($effectiveUserId, $message->getMailboxId());
+ $account = $this->accountService->find($effectiveUserId, $mailbox->getAccountId());
} catch (DoesNotExistException $e) {
return new JSONResponse([], Http::STATUS_FORBIDDEN);
}
@@ -175,9 +186,10 @@ public function summarize(int $id): JSONResponse {
*/
public function generateEventData(int $id): JSONResponse {
try {
- $message = $this->mailManager->getMessage($this->currentUserId, $id);
- $mailbox = $this->mailManager->getMailbox($this->currentUserId, $message->getMailboxId());
- $account = $this->accountService->find($this->currentUserId, $mailbox->getAccountId());
+ $effectiveUserId = $this->delegationService->resolveMessageUserId($id, $this->currentUserId);
+ $message = $this->mailManager->getMessage($effectiveUserId, $id);
+ $mailbox = $this->mailManager->getMailbox($effectiveUserId, $message->getMailboxId());
+ $account = $this->accountService->find($effectiveUserId, $mailbox->getAccountId());
} catch (DoesNotExistException $e) {
return new JSONResponse([], Http::STATUS_FORBIDDEN);
}
@@ -207,9 +219,10 @@ public function generateEventData(int $id): JSONResponse {
#[TrapError]
public function delete(int $id): JSONResponse {
try {
- $message = $this->mailManager->getMessage($this->currentUserId, $id);
- $mailbox = $this->mailManager->getMailbox($this->currentUserId, $message->getMailboxId());
- $account = $this->accountService->find($this->currentUserId, $mailbox->getAccountId());
+ $effectiveUserId = $this->delegationService->resolveMessageUserId($id, $this->currentUserId);
+ $message = $this->mailManager->getMessage($effectiveUserId, $id);
+ $mailbox = $this->mailManager->getMailbox($effectiveUserId, $message->getMailboxId());
+ $account = $this->accountService->find($effectiveUserId, $mailbox->getAccountId());
} catch (DoesNotExistException $e) {
return new JSONResponse([], Http::STATUS_FORBIDDEN);
}
@@ -219,6 +232,7 @@ public function delete(int $id): JSONResponse {
$mailbox,
$message->getThreadRootId()
);
+ $this->delegationService->logDelegatedAction("$this->currentUserId deleted thread <$id> on behalf of $effectiveUserId");
return new JSONResponse();
}
diff --git a/lib/Db/AliasMapper.php b/lib/Db/AliasMapper.php
index bae9b6d98a..2a3f2791b3 100644
--- a/lib/Db/AliasMapper.php
+++ b/lib/Db/AliasMapper.php
@@ -141,6 +141,18 @@ public function deleteProvisionedAliasesByUid(string $uid): void {
}
}
+ /**
+ * @throws DoesNotExistException
+ */
+ public function findAccountIdForAlias(int $aliasId): int {
+ $qb = $this->db->getQueryBuilder();
+ $qb->select('account_id')
+ ->from($this->getTableName())
+ ->where($qb->expr()->eq('id', $qb->createNamedParameter($aliasId)));
+ $row = $this->findOneQuery($qb);
+ return (int)$row['account_id'];
+ }
+
public function deleteOrphans(): void {
$qb1 = $this->db->getQueryBuilder();
$idsQuery = $qb1->select('a.id')
diff --git a/lib/Db/Delegation.php b/lib/Db/Delegation.php
new file mode 100644
index 0000000000..d72de2498e
--- /dev/null
+++ b/lib/Db/Delegation.php
@@ -0,0 +1,39 @@
+addType('userId', 'string');
+ $this->addType('accountId', 'integer');
+ }
+
+ #[ReturnTypeWillChange]
+ public function jsonSerialize() {
+ return [
+ 'id' => $this->getId(),
+ 'accountId' => $this->getAccountId(),
+ 'userId' => $this->getUserId(),
+ ];
+ }
+}
diff --git a/lib/Db/DelegationMapper.php b/lib/Db/DelegationMapper.php
new file mode 100644
index 0000000000..17d507c192
--- /dev/null
+++ b/lib/Db/DelegationMapper.php
@@ -0,0 +1,83 @@
+
+ */
+class DelegationMapper extends QBMapper {
+ public function __construct(IDBConnection $db) {
+ parent::__construct($db, 'mail_delegations');
+ }
+
+ public function findDelegatedAccountsForUser(string $uid): array {
+ $qb = $this->db->getQueryBuilder();
+
+ $select = $qb->select('*')
+ ->from($this->getTableName())
+ ->where(
+ $qb->expr()->eq('user_id', $qb->createNamedParameter($uid))
+ );
+
+ return $this->findEntities($select);
+ }
+
+ public function findDelegatedToUsers(int $accountId): array {
+ $qb = $this->db->getQueryBuilder();
+
+ $select = $qb->select('*')
+ ->from($this->getTableName())
+ ->where(
+ $qb->expr()->eq('account_id', $qb->createNamedParameter($accountId))
+ );
+
+ return $this->findEntities($select);
+ }
+
+ /**
+ * @throws DoesNotExistException
+ */
+ public function find(int $accountId, string $uid): Delegation {
+ $qb = $this->db->getQueryBuilder();
+ $qb->select('*')
+ ->from($this->getTableName())
+ ->where($qb->expr()->eq('user_id', $qb->createNamedParameter($uid)))
+ ->andWhere($qb->expr()->eq('account_id', $qb->createNamedParameter($accountId)));
+ return $this->findEntity($qb);
+ }
+
+ /**
+ * @throws DoesNotExistException
+ */
+ public function findAccountOwnerForDelegatedUser(int $accountId, string $delegatedUserId): string {
+ $qb = $this->db->getQueryBuilder();
+ $qb->select('a.user_id')
+ ->from($this->getTableName(), 'd')
+ ->join('d', 'mail_accounts', 'a',
+ $qb->expr()->eq('d.account_id', 'a.id', IQueryBuilder::PARAM_INT))
+ ->where($qb->expr()->eq('d.account_id', $qb->createNamedParameter($accountId, IQueryBuilder::PARAM_INT)))
+ ->andWhere($qb->expr()->eq('d.user_id', $qb->createNamedParameter($delegatedUserId)));
+
+ $result = $qb->executeQuery();
+ $row = $result->fetch();
+ $result->closeCursor();
+
+ if ($row === false) {
+ throw new DoesNotExistException("No delegation found for account $accountId and user $delegatedUserId");
+ }
+
+ return (string)$row['user_id'];
+ }
+}
diff --git a/lib/Db/LocalMessageMapper.php b/lib/Db/LocalMessageMapper.php
index 06ef03d023..3196ee1a87 100644
--- a/lib/Db/LocalMessageMapper.php
+++ b/lib/Db/LocalMessageMapper.php
@@ -104,6 +104,19 @@ public function findById(int $id, string $userId, int $type): LocalMessage {
return $entity;
}
+ /**
+ * @throws DoesNotExistException
+ */
+ public function findAccountIdForLocalMessage(int $localMessageId): int {
+ $qb = $this->db->getQueryBuilder();
+ $qb->select('account_id')
+ ->from($this->getTableName())
+ ->where($qb->expr()->eq('id', $qb->createNamedParameter($localMessageId, IQueryBuilder::PARAM_INT)));
+
+ $row = $this->findOneQuery($qb);
+ return (int)$row['account_id'];
+ }
+
/**
* Find all messages that should be sent
*
diff --git a/lib/Db/MailboxMapper.php b/lib/Db/MailboxMapper.php
index 36ebdfbbe3..ccae2d48ce 100644
--- a/lib/Db/MailboxMapper.php
+++ b/lib/Db/MailboxMapper.php
@@ -157,6 +157,19 @@ public function findByUid(int $id, string $uid): Mailbox {
}
}
+ /**
+ * @throws DoesNotExistException
+ */
+ public function findAccountIdForMailbox(int $mailboxId): int {
+ $qb = $this->db->getQueryBuilder();
+ $qb->select('account_id')
+ ->from($this->getTableName())
+ ->where($qb->expr()->eq('id', $qb->createNamedParameter($mailboxId, IQueryBuilder::PARAM_INT)));
+
+ $row = $this->findOneQuery($qb);
+ return (int)$row['account_id'];
+ }
+
/**
* @throws MailboxLockedException
*/
diff --git a/lib/Db/MessageMapper.php b/lib/Db/MessageMapper.php
index 63c961bfd3..75fa58011d 100644
--- a/lib/Db/MessageMapper.php
+++ b/lib/Db/MessageMapper.php
@@ -131,6 +131,21 @@ public function findByUserId(string $userId, int $id): Message {
return $results[0];
}
+ /**
+ * @throws DoesNotExistException
+ */
+ public function findAccountIdForMessage(int $messageId): int {
+ $qb = $this->db->getQueryBuilder();
+ $qb->select('mb.account_id')
+ ->from($this->getTableName(), 'm')
+ ->join('m', 'mail_mailboxes', 'mb',
+ $qb->expr()->eq('m.mailbox_id', 'mb.id', IQueryBuilder::PARAM_INT))
+ ->where($qb->expr()->eq('m.id', $qb->createNamedParameter($messageId, IQueryBuilder::PARAM_INT)));
+
+ $row = $this->findOneQuery($qb);
+ return (int)$row['account_id'];
+ }
+
public function findAllUids(Mailbox $mailbox): array {
$query = $this->db->getQueryBuilder();
diff --git a/lib/Exception/DelegationExistsException.php b/lib/Exception/DelegationExistsException.php
new file mode 100644
index 0000000000..de14e81a4a
--- /dev/null
+++ b/lib/Exception/DelegationExistsException.php
@@ -0,0 +1,18 @@
+hasTable('mail_delegations')) {
+ $table = $schema->createTable('mail_delegations');
+ $table->addColumn('id', Types::INTEGER, [
+ 'autoincrement' => true,
+ 'notnull' => true,
+ ]);
+ $table->addColumn('account_id', Types::INTEGER, [
+ 'notnull' => true,
+ 'length' => 20,
+ ]);
+ $table->addColumn('user_id', Types::STRING, [
+ 'notnull' => true,
+ 'length' => 64,
+ ]);
+ $table->setPrimaryKey(['id']);
+ $table->addUniqueIndex(['account_id', 'user_id'], 'mail_deleg_acc_user_uniq');
+ if ($schema->hasTable('mail_accounts')) {
+ $table->addForeignKeyConstraint(
+ $schema->getTable('mail_accounts'),
+ ['account_id'],
+ ['id'],
+ [
+ 'onDelete' => 'CASCADE',
+ ]
+ );
+ }
+ }
+
+ return $schema;
+ }
+
+}
diff --git a/lib/Notification/Notifier.php b/lib/Notification/Notifier.php
index dedd2e7ebd..b3f2d26b41 100644
--- a/lib/Notification/Notifier.php
+++ b/lib/Notification/Notifier.php
@@ -72,8 +72,57 @@ public function prepare(INotification $notification, string $languageCode): INot
]
]);
break;
+ case 'account_delegation':
+ $parameters = $notification->getSubjectParameters();
+ $messageParameters = $notification->getMessageParameters();
+ $delegated = $messageParameters['delegated'];
+ if ($delegated) {
+ $notification->setRichSubject($l->t('{account_email} has been delegated to you'), [
+ 'account_email' => [
+ 'type' => 'highlight',
+ 'id' => (string)$parameters['id'],
+ 'name' => $parameters['account_email']
+ ]
+ ]);
+ $notification->setRichMessage($l->t('{user} delegated {account} to you'),
+ [
+ 'user' => [
+ 'type' => 'user',
+ 'id' => $messageParameters['current_user_id'],
+ 'name' => $messageParameters['current_user_display_name'],
+ ],
+ 'account' => [
+ 'type' => 'highlight',
+ 'id' => (string)$messageParameters['id'],
+ 'name' => $messageParameters['account_email']
+ ]
+ ]);
+ } else {
+ $notification->setRichSubject($l->t('{account_email} is no longer delegated to you'), [
+ 'account_email' => [
+ 'type' => 'highlight',
+ 'id' => (string)$parameters['id'],
+ 'name' => $parameters['account_email']
+ ]
+ ]);
+ $notification->setRichMessage($l->t('{user} revoked delagation for {account}'),
+ [
+ 'user' => [
+ 'type' => 'user',
+ 'id' => $messageParameters['current_user_id'],
+ 'name' => $messageParameters['current_user_display_name'],
+ ],
+ 'account' => [
+ 'type' => 'highlight',
+ 'id' => (string)$messageParameters['id'],
+ 'name' => $messageParameters['account_email']
+ ]
+ ]);
+ }
+
+ break;
default:
- throw new UnknownNotificationException();
+ throw new UnknownNotificationException();
}
return $notification;
diff --git a/lib/ResponseDefinitions.php b/lib/ResponseDefinitions.php
index 024ddee9f8..f44206bb21 100644
--- a/lib/ResponseDefinitions.php
+++ b/lib/ResponseDefinitions.php
@@ -48,6 +48,7 @@
* @psalm-type MailAccountListResponse = array{
* id: int,
* email: string,
+ * isDelegated: bool,
* aliases: listmapper = $mapper;
$this->aliasesService = $aliasesService;
@@ -75,6 +77,23 @@ public function findByUserId(string $currentUserId): array {
return $this->accounts[$currentUserId];
}
+ /**
+ * @param string $userId
+ * @return list
+ */
+ public function findDelegatedAccounts(string $userId): array {
+ $delegations = $this->delegationMapper->findDelegatedAccountsForUser($userId);
+ $accounts = [];
+ foreach ($delegations as $delegation) {
+ try {
+ $accounts[] = new Account($this->mapper->findById($delegation->getAccountId()));
+ } catch (DoesNotExistException) {
+ // Account was deleted but delegation record remains — skip
+ }
+ }
+ return $accounts;
+ }
+
/**
* @param int $id
*
diff --git a/lib/Service/DelegationService.php b/lib/Service/DelegationService.php
new file mode 100644
index 0000000000..31ee748748
--- /dev/null
+++ b/lib/Service/DelegationService.php
@@ -0,0 +1,159 @@
+getId();
+ try {
+ $this->delegationMapper->find($accountId, $userId);
+ throw new DelegationExistsException("Delegation already exists for account $accountId and user $userId");
+ } catch (DoesNotExistException) {
+ // delegation doesn't exist, continue
+ }
+
+ $delegation = new Delegation();
+ $delegation->setAccountId($accountId);
+ $delegation->setUserId($userId);
+ $result = $this->delegationMapper->insert($delegation);
+ $this->notify($userId, $currentUserId, $account, true);
+ return $result;
+ }
+
+ public function findDelegatedToUsersForAccount(int $accountId): array {
+ return $this->delegationMapper->findDelegatedToUsers($accountId);
+ }
+
+ /**
+ * @throws DoesNotExistException
+ */
+ public function unDelegate(Account $account, string $userId, string $currentUserId): void {
+ $accountId = $account->getId();
+ $delegation = $this->delegationMapper->find($accountId, $userId);
+ $this->delegationMapper->delete($delegation);
+ $this->notify($userId, $currentUserId, $account, false);
+ }
+
+ /**
+ * @throws DoesNotExistException
+ */
+ public function resolveAccountUserId(int $accountId, string $currentUserId): string {
+ // Check if the current user owns the account
+ try {
+ $account = $this->mailAccountMapper->find($currentUserId, $accountId);
+ return $account->getUserId();
+ } catch (DoesNotExistException) {
+ // Not the owner — check delegation
+ }
+
+ return $this->delegationMapper->findAccountOwnerForDelegatedUser($accountId, $currentUserId);
+ }
+
+ /**
+ * @throws DoesNotExistException
+ */
+ public function resolveMailboxUserId(int $mailboxId, string $currentUserId): string {
+ $accountId = $this->mailboxMapper->findAccountIdForMailbox($mailboxId);
+ return $this->resolveAccountUserId($accountId, $currentUserId);
+ }
+
+ /**
+ * @throws DoesNotExistException
+ */
+ public function resolveMessageUserId(int $messageId, string $currentUserId): string {
+ $accountId = $this->messageMapper->findAccountIdForMessage($messageId);
+ return $this->resolveAccountUserId($accountId, $currentUserId);
+ }
+
+ /**
+ * @throws DoesNotExistException
+ */
+ public function resolveAliasUserId(int $aliasId, string $currentUserId): string {
+ $accountId = $this->aliasMapper->findAccountIdForAlias($aliasId);
+ return $this->resolveAccountUserId($accountId, $currentUserId);
+ }
+
+ /**
+ * @throws DoesNotExistException
+ */
+ public function resolveLocalMessageUserId(int $localMessageId, string $currentUserId): string {
+ $accountId = $this->localMessageMapper->findAccountIdForLocalMessage($localMessageId);
+ return $this->resolveAccountUserId($accountId, $currentUserId);
+ }
+
+
+ public function logDelegatedAction(string $logMessage) {
+ $this->eventDispatcher->dispatchTyped(new CriticalActionPerformedEvent($logMessage));
+ }
+
+ /**
+ * Send a notification on delegation
+ * @param string $userId The user the account is being delegated to
+ * @param string $currentUserId Current user
+ * @param Account $account The delegated account
+ * @param bool $delegated true for delegate|false for undelegate
+ * @return void
+ */
+ private function notify(string $userId, string $currentUserId, Account $account, bool $delegated) {
+ $notification = $this->notificationManager->createNotification();
+ $displayName = $this->userManager->get($currentUserId)?->getDisplayName() ?? $currentUserId;
+ $time = $this->time->getDateTime('now');
+ $notification
+ ->setApp('mail')
+ ->setUser($userId)
+ ->setObject('delegation', (string)$account->getId())
+ ->setSubject('account_delegation', [
+ 'id' => $account->getId(),
+ 'account_email' => $account->getEmail(),
+
+ ])
+ ->setDateTime($time)
+ ->setMessage('account_delegation_changed', [
+ 'id' => $account->getId(),
+ 'delegated' => $delegated,
+ 'current_user_id' => $currentUserId,
+ 'current_user_display_name' => $displayName,
+ 'account_email' => $account->getEmail(),
+ ]
+ );
+ $this->notificationManager->notify($notification);
+ }
+}
diff --git a/tests/Integration/MailboxSynchronizationTest.php b/tests/Integration/MailboxSynchronizationTest.php
index aa93fdf616..ba7c5b9a24 100644
--- a/tests/Integration/MailboxSynchronizationTest.php
+++ b/tests/Integration/MailboxSynchronizationTest.php
@@ -16,6 +16,7 @@
use OCA\Mail\Controller\MailboxesController;
use OCA\Mail\Db\MessageMapper as DbMessageMapper;
use OCA\Mail\Service\AccountService;
+use OCA\Mail\Service\DelegationService;
use OCA\Mail\Service\Sync\ImapToDbSynchronizer;
use OCA\Mail\Service\Sync\SyncService;
use OCA\Mail\Tests\Integration\Framework\ImapTest;
@@ -51,6 +52,7 @@ protected function setUp(): void {
Server::get(SyncService::class),
Server::get(IConfig::class),
Server::get(ITimeFactory::class),
+ Server::get(DelegationService::class),
);
$this->account = $this->createTestAccount('user12345');
diff --git a/tests/Unit/Controller/AccountApiControllerTest.php b/tests/Unit/Controller/AccountApiControllerTest.php
index c560c8e4f5..85808d99f6 100644
--- a/tests/Unit/Controller/AccountApiControllerTest.php
+++ b/tests/Unit/Controller/AccountApiControllerTest.php
@@ -16,6 +16,7 @@
use OCA\Mail\Db\MailAccount;
use OCA\Mail\Service\AccountService;
use OCA\Mail\Service\AliasesService;
+use OCA\Mail\Service\DelegationService;
use OCP\AppFramework\Http;
use OCP\IRequest;
use PHPUnit\Framework\MockObject\MockObject;
@@ -28,6 +29,7 @@ class AccountApiControllerTest extends TestCase {
private IRequest&MockObject $request;
private AccountService&MockObject $accountService;
private AliasesService&MockObject $aliasesService;
+ private DelegationService&MockObject $delegationService;
protected function setUp(): void {
parent::setUp();
@@ -35,6 +37,7 @@ protected function setUp(): void {
$this->request = $this->createMock(IRequest::class);
$this->accountService = $this->createMock(AccountService::class);
$this->aliasesService = $this->createMock(AliasesService::class);
+ $this->delegationService = $this->createMock(DelegationService::class);
$this->controller = new AccountApiController(
'mail',
@@ -42,6 +45,7 @@ protected function setUp(): void {
self::USER_ID,
$this->accountService,
$this->aliasesService,
+ $this->delegationService,
);
}
@@ -52,6 +56,7 @@ public function testListWithoutUser() {
null,
$this->accountService,
$this->aliasesService,
+ $this->delegationService,
);
$this->accountService->expects(self::never())
@@ -74,6 +79,10 @@ public function testList() {
->method('findByUserId')
->with(self::USER_ID)
->willReturn([$account]);
+ $this->accountService->expects(self::once())
+ ->method('findDelegatedAccounts')
+ ->with(self::USER_ID)
+ ->willReturn([]);
$alias = new Alias();
$alias->setId(10);
@@ -90,6 +99,7 @@ public function testList() {
[
'id' => 42,
'email' => 'foo@bar.com',
+ 'isDelegated' => false,
'aliases' => [
[
'id' => 10,
@@ -111,6 +121,10 @@ public function testListWithAliasWithoutName() {
->method('findByUserId')
->with(self::USER_ID)
->willReturn([$account]);
+ $this->accountService->expects(self::once())
+ ->method('findDelegatedAccounts')
+ ->with(self::USER_ID)
+ ->willReturn([]);
$alias = new Alias();
$alias->setId(10);
@@ -127,6 +141,7 @@ public function testListWithAliasWithoutName() {
[
'id' => 42,
'email' => 'foo@bar.com',
+ 'isDelegated' => false,
'aliases' => [
[
'id' => 10,
@@ -148,6 +163,10 @@ public function testListWithoutAliases() {
->method('findByUserId')
->with(self::USER_ID)
->willReturn([$account]);
+ $this->accountService->expects(self::once())
+ ->method('findDelegatedAccounts')
+ ->with(self::USER_ID)
+ ->willReturn([]);
$this->aliasesService->expects(self::once())
->method('findAll')
@@ -160,6 +179,7 @@ public function testListWithoutAliases() {
[
'id' => 42,
'email' => 'foo@bar.com',
+ 'isDelegated' => false,
'aliases' => [],
]
], $actual->getData());
diff --git a/tests/Unit/Controller/AccountsControllerTest.php b/tests/Unit/Controller/AccountsControllerTest.php
index b659555d3c..234ca139a6 100644
--- a/tests/Unit/Controller/AccountsControllerTest.php
+++ b/tests/Unit/Controller/AccountsControllerTest.php
@@ -21,6 +21,7 @@
use OCA\Mail\IMAP\Sync\Response;
use OCA\Mail\Service\AccountService;
use OCA\Mail\Service\AliasesService;
+use OCA\Mail\Service\DelegationService;
use OCA\Mail\Service\SetupService;
use OCA\Mail\Service\Sync\SyncService;
use OCP\AppFramework\Db\DoesNotExistException;
@@ -85,6 +86,9 @@ class AccountsControllerTest extends TestCase {
/** @var IConfig|(IConfig&MockObject)|MockObject */
private IConfig|MockObject $config;
+
+ /** @var DelegationService|MockObject */
+ private $delegationService;
/** @var IRemoteHostValidator|MockObject */
private $hostValidator;
@@ -107,6 +111,9 @@ protected function setUp(): void {
$this->hostValidator = $this->createMock(IRemoteHostValidator::class);
$this->hostValidator->method('isValid')->willReturn(true);
$this->timeFactory = $this->createMock(ITimeFactory::class);
+ $this->delegationService = $this->createMock(DelegationService::class);
+ $this->delegationService->method('resolveAccountUserId')
+ ->willReturn($this->userId);
$this->controller = new AccountsController(
$this->appName,
@@ -124,6 +131,7 @@ protected function setUp(): void {
$this->hostValidator,
$this->mailboxSync,
$this->timeFactory,
+ $this->delegationService,
);
$this->account = $this->createMock(Account::class);
$this->accountId = 123;
@@ -143,6 +151,10 @@ public function testIndex(): void {
->method('findAll')
->with(self::equalTo($this->accountId), self::equalTo($this->userId))
->will(self::returnValue(['a1', 'a2']));
+ $this->accountService->expects(self::once())
+ ->method('findDelegatedAccounts')
+ ->with(self::equalTo($this->userId))
+ ->willReturn([]);
$response = $this->controller->index();
@@ -153,6 +165,7 @@ public function testIndex(): void {
'a1',
'a2',
],
+ 'isDelegated' => false,
]
]);
self::assertEquals($expectedResponse, $response);
diff --git a/tests/Unit/Controller/AliasesControllerTest.php b/tests/Unit/Controller/AliasesControllerTest.php
index 51e36e7933..067cecda14 100644
--- a/tests/Unit/Controller/AliasesControllerTest.php
+++ b/tests/Unit/Controller/AliasesControllerTest.php
@@ -15,6 +15,7 @@
use OCA\Mail\Exception\ClientException;
use OCA\Mail\Exception\NotImplemented;
use OCA\Mail\Service\AliasesService;
+use OCA\Mail\Service\DelegationService;
use OCP\AppFramework\Db\DoesNotExistException;
use OCP\AppFramework\Http;
use OCP\AppFramework\Http\JSONResponse;
@@ -35,6 +36,9 @@ class AliasesControllerTest extends TestCase {
/** @var AliasesService */
private $aliasService;
+ /** @var DelegationService */
+ private $delegationService;
+
public function setUp(): void {
parent::setUp();
$this->request = $this->getMockBuilder('OCP\IRequest')
@@ -48,7 +52,12 @@ public function setUp(): void {
$this->mailAccountMapper = $this->createMock(MailAccountMapper::class);
$this->aliasService = new AliasesService($this->aliasMapper, $this->mailAccountMapper);
- $this->controller = new AliasesController($this->appName, $this->request, $this->aliasService, $this->userId);
+ $this->delegationService = $this->createMock(DelegationService::class);
+ $this->delegationService->method('resolveAccountUserId')
+ ->willReturn($this->userId);
+ $this->delegationService->method('resolveAliasUserId')
+ ->willReturn($this->userId);
+ $this->controller = new AliasesController($this->appName, $this->request, $this->aliasService, $this->userId, $this->delegationService);
}
public function testIndex(): void {
diff --git a/tests/Unit/Controller/DelegationControllerTest.php b/tests/Unit/Controller/DelegationControllerTest.php
new file mode 100644
index 0000000000..16ba90567e
--- /dev/null
+++ b/tests/Unit/Controller/DelegationControllerTest.php
@@ -0,0 +1,239 @@
+request = $this->createMock(IRequest::class);
+ $this->delegationService = $this->createMock(DelegationService::class);
+ $this->accountService = $this->createMock(AccountService::class);
+ $this->userManager = $this->createMock(IUserManager::class);
+
+ $this->controller = new DelegationController(
+ $this->appName,
+ $this->request,
+ $this->delegationService,
+ $this->accountService,
+ $this->userManager,
+ $this->currentUserId,
+ );
+
+ $ownMailAccount = new MailAccount();
+ $ownMailAccount->setId(1);
+ $ownMailAccount->setUserId($this->currentUserId);
+ $ownMailAccount->setEmail('owner@example.com');
+ $this->ownAccount = new Account($ownMailAccount);
+
+ $otherMailAccount = new MailAccount();
+ $otherMailAccount->setId(2);
+ $otherMailAccount->setUserId('other');
+ $otherMailAccount->setEmail('other@example.com');
+ $this->otherAccount = new Account($otherMailAccount);
+ }
+
+ public function testGetDelegatedUsersSuccess(): void {
+ $delegation = new Delegation();
+ $delegation->setId(10);
+ $delegation->setAccountId(1);
+ $delegation->setUserId('delegatee');
+
+ $this->accountService->expects($this->once())
+ ->method('findById')
+ ->with(1)
+ ->willReturn($this->ownAccount);
+
+ $this->delegationService->expects($this->once())
+ ->method('findDelegatedToUsersForAccount')
+ ->with(1)
+ ->willReturn([$delegation]);
+
+ $response = $this->controller->getDelegatedUsers(1);
+
+ $this->assertInstanceOf(JSONResponse::class, $response);
+ $this->assertEquals(Http::STATUS_OK, $response->getStatus());
+ $this->assertEquals([$delegation], $response->getData());
+ }
+
+ public function testGetDelegatedUsersUnauthorized(): void {
+ $this->accountService->expects($this->once())
+ ->method('findById')
+ ->with(2)
+ ->willReturn($this->otherAccount);
+
+ $this->delegationService->expects($this->never())
+ ->method('findDelegatedToUsersForAccount');
+
+ $response = $this->controller->getDelegatedUsers(2);
+
+ $this->assertInstanceOf(JSONResponse::class, $response);
+ $this->assertEquals(Http::STATUS_UNAUTHORIZED, $response->getStatus());
+ }
+
+ public function testDelegateSuccess(): void {
+ $delegation = new Delegation();
+ $delegation->setId(10);
+ $delegation->setAccountId(1);
+ $delegation->setUserId('delegatee');
+
+ $this->accountService->expects($this->once())
+ ->method('findById')
+ ->with(1)
+ ->willReturn($this->ownAccount);
+
+ $this->userManager->expects($this->once())
+ ->method('userExists')
+ ->with('delegatee')
+ ->willReturn(true);
+
+ $this->delegationService->expects($this->once())
+ ->method('delegate')
+ ->with($this->ownAccount, 'delegatee', $this->currentUserId)
+ ->willReturn($delegation);
+
+ $response = $this->controller->delegate(1, 'delegatee');
+
+ $this->assertInstanceOf(JSONResponse::class, $response);
+ $this->assertEquals(Http::STATUS_CREATED, $response->getStatus());
+ $this->assertEquals($delegation, $response->getData());
+ }
+
+ public function testDelegateUnauthorized(): void {
+ $this->accountService->expects($this->once())
+ ->method('findById')
+ ->with(2)
+ ->willReturn($this->otherAccount);
+
+ $this->delegationService->expects($this->never())
+ ->method('delegate');
+
+ $response = $this->controller->delegate(2, 'delegatee');
+
+ $this->assertInstanceOf(JSONResponse::class, $response);
+ $this->assertEquals(Http::STATUS_UNAUTHORIZED, $response->getStatus());
+ }
+
+ public function testDelegateToSelf(): void {
+ $this->accountService->expects($this->once())
+ ->method('findById')
+ ->with(1)
+ ->willReturn($this->ownAccount);
+
+ $this->delegationService->expects($this->never())
+ ->method('delegate');
+
+ $response = $this->controller->delegate(1, $this->currentUserId);
+
+ $this->assertInstanceOf(JSONResponse::class, $response);
+ $this->assertEquals(Http::STATUS_BAD_REQUEST, $response->getStatus());
+ $this->assertEquals(['message' => 'Cannot delegate to yourself'], $response->getData());
+ }
+
+ public function testDelegateUserNotFound(): void {
+ $this->accountService->expects($this->once())
+ ->method('findById')
+ ->with(1)
+ ->willReturn($this->ownAccount);
+
+ $this->userManager->expects($this->once())
+ ->method('userExists')
+ ->with('nonexistent')
+ ->willReturn(false);
+
+ $this->delegationService->expects($this->never())
+ ->method('delegate');
+
+ $response = $this->controller->delegate(1, 'nonexistent');
+
+ $this->assertInstanceOf(JSONResponse::class, $response);
+ $this->assertEquals(Http::STATUS_NOT_FOUND, $response->getStatus());
+ }
+
+ public function testDelegateAlreadyExists(): void {
+ $this->accountService->expects($this->once())
+ ->method('findById')
+ ->with(1)
+ ->willReturn($this->ownAccount);
+
+ $this->userManager->expects($this->once())
+ ->method('userExists')
+ ->with('delegatee')
+ ->willReturn(true);
+
+ $this->delegationService->expects($this->once())
+ ->method('delegate')
+ ->with($this->ownAccount, 'delegatee', $this->currentUserId)
+ ->willThrowException(new DelegationExistsException('Delegation already exists'));
+
+ $response = $this->controller->delegate(1, 'delegatee');
+
+ $this->assertInstanceOf(JSONResponse::class, $response);
+ $this->assertEquals(Http::STATUS_CONFLICT, $response->getStatus());
+ $this->assertEquals(['message' => 'Delegation already exists'], $response->getData());
+ }
+
+ public function testUnDelegateSuccess(): void {
+ $this->accountService->expects($this->once())
+ ->method('findById')
+ ->with(1)
+ ->willReturn($this->ownAccount);
+
+ $this->delegationService->expects($this->once())
+ ->method('unDelegate')
+ ->with($this->ownAccount, 'delegatee', $this->currentUserId);
+
+ $response = $this->controller->unDelegate(1, 'delegatee');
+
+ $this->assertInstanceOf(JSONResponse::class, $response);
+ $this->assertEquals(Http::STATUS_OK, $response->getStatus());
+ }
+
+ public function testUnDelegateUnauthorized(): void {
+ $this->accountService->expects($this->once())
+ ->method('findById')
+ ->with(2)
+ ->willReturn($this->otherAccount);
+
+ $this->delegationService->expects($this->never())
+ ->method('unDelegate');
+
+ $response = $this->controller->unDelegate(2, 'delegatee');
+
+ $this->assertInstanceOf(JSONResponse::class, $response);
+ $this->assertEquals(Http::STATUS_UNAUTHORIZED, $response->getStatus());
+ }
+}
diff --git a/tests/Unit/Controller/DraftsControllerTest.php b/tests/Unit/Controller/DraftsControllerTest.php
index e1f672aff1..d9651ff6fa 100644
--- a/tests/Unit/Controller/DraftsControllerTest.php
+++ b/tests/Unit/Controller/DraftsControllerTest.php
@@ -19,6 +19,7 @@
use OCA\Mail\Exception\ServiceException;
use OCA\Mail\Http\JsonResponse;
use OCA\Mail\Service\AccountService;
+use OCA\Mail\Service\DelegationService;
use OCA\Mail\Service\DraftsService;
use OCA\Mail\Service\SmimeService;
use OCP\AppFramework\Db\DoesNotExistException;
@@ -35,6 +36,7 @@ class DraftsControllerTest extends TestCase {
private AccountService $accountService;
private DraftsController $controller;
private SmimeService $smimeService;
+ private DelegationService $delegationService;
protected function setUp(): void {
parent::setUp();
@@ -46,6 +48,11 @@ protected function setUp(): void {
$this->accountService = $this->createMock(AccountService::class);
$this->timeFactory = $this->createMock(ITimeFactory::class);
$this->smimeService = $this->createMock(SmimeService::class);
+ $this->delegationService = $this->createMock(DelegationService::class);
+ $this->delegationService->method('resolveAccountUserId')
+ ->willReturn($this->userId);
+ $this->delegationService->method('resolveLocalMessageUserId')
+ ->willReturn($this->userId);
$this->controller = new DraftsController(
$this->appName,
@@ -54,7 +61,8 @@ protected function setUp(): void {
$this->service,
$this->accountService,
$this->timeFactory,
- $this->smimeService
+ $this->smimeService,
+ $this->delegationService,
);
}
diff --git a/tests/Unit/Controller/ListControllerTest.php b/tests/Unit/Controller/ListControllerTest.php
index 259ded1d51..b97b9dd482 100644
--- a/tests/Unit/Controller/ListControllerTest.php
+++ b/tests/Unit/Controller/ListControllerTest.php
@@ -34,6 +34,9 @@ protected function setUp(): void {
$this->serviceMock = $this->createServiceMock(ListController::class, [
'userId' => 'user123',
]);
+ $this->serviceMock->getParameter('delegationService')
+ ->method('resolveMessageUserId')
+ ->willReturn('user123');
$this->controller = $this->serviceMock->getService();
}
diff --git a/tests/Unit/Controller/MailboxesApiControllerTest.php b/tests/Unit/Controller/MailboxesApiControllerTest.php
index 221de5de87..f00754af70 100644
--- a/tests/Unit/Controller/MailboxesApiControllerTest.php
+++ b/tests/Unit/Controller/MailboxesApiControllerTest.php
@@ -19,6 +19,7 @@
use OCA\Mail\Db\Message as DbMessage;
use OCA\Mail\Folder;
use OCA\Mail\Service\AccountService;
+use OCA\Mail\Service\DelegationService;
use OCP\AppFramework\Db\DoesNotExistException;
use OCP\AppFramework\Http;
use OCP\IRequest;
@@ -33,6 +34,7 @@ class MailboxesApiControllerTest extends TestCase {
private IMailManager|MockObject $mailManager;
private AccountService&MockObject $accountService;
private MockObject|IMailSearch $mailSearch;
+ private DelegationService&MockObject $delegationService;
protected function setUp(): void {
parent::setUp();
@@ -41,6 +43,11 @@ protected function setUp(): void {
$this->accountService = $this->createMock(AccountService::class);
$this->mailManager = $this->createMock(IMailManager::class);
$this->mailSearch = $this->createMock(IMailSearch::class);
+ $this->delegationService = $this->createMock(DelegationService::class);
+ $this->delegationService->method('resolveAccountUserId')
+ ->willReturn(self::USER_ID);
+ $this->delegationService->method('resolveMailboxUserId')
+ ->willReturn(self::USER_ID);
$this->controller = new MailboxesApiController(
'mail',
@@ -49,7 +56,7 @@ protected function setUp(): void {
$this->mailManager,
$this->accountService,
$this->mailSearch,
-
+ $this->delegationService,
);
}
@@ -61,6 +68,7 @@ public function testListMailboxesWithoutUser() {
$this->mailManager,
$this->accountService,
$this->mailSearch,
+ $this->delegationService,
);
$this->accountService->expects(self::never())
@@ -101,6 +109,7 @@ public function testListMessagesWithoutUser() {
$this->mailManager,
$this->accountService,
$this->mailSearch,
+ $this->delegationService,
);
$this->accountService->expects(self::never())
diff --git a/tests/Unit/Controller/MailboxesControllerTest.php b/tests/Unit/Controller/MailboxesControllerTest.php
index 8dddb381d2..51ffc5d3ef 100644
--- a/tests/Unit/Controller/MailboxesControllerTest.php
+++ b/tests/Unit/Controller/MailboxesControllerTest.php
@@ -18,6 +18,7 @@
use OCA\Mail\Folder;
use OCA\Mail\IMAP\MailboxStats;
use OCA\Mail\Service\AccountService;
+use OCA\Mail\Service\DelegationService;
use OCA\Mail\Service\Sync\SyncService;
use OCP\AppFramework\Http\JSONResponse;
use OCP\AppFramework\Utility\ITimeFactory;
@@ -49,6 +50,7 @@ class MailboxesControllerTest extends TestCase {
private IConfig|MockObject $config;
private ITimeFactory|MockObject $timeFactory;
+ private DelegationService|MockObject $delegationService;
public function setUp(): void {
parent::setUp();
@@ -59,6 +61,9 @@ public function setUp(): void {
$this->syncService = $this->createMock(SyncService::class);
$this->config = $this->createMock(IConfig::class);
$this->timeFactory = $this->createMock(ITimeFactory::class);
+ $this->delegationService = $this->createMock(DelegationService::class);
+ $this->delegationService->method('resolveAccountUserId')->willReturn($this->userId);
+ $this->delegationService->method('resolveMailboxUserId')->willReturn($this->userId);
$this->controller = new MailboxesController(
$this->appName,
@@ -68,7 +73,8 @@ public function setUp(): void {
$this->mailManager,
$this->syncService,
$this->config,
- $this->timeFactory
+ $this->timeFactory,
+ $this->delegationService,
);
}
diff --git a/tests/Unit/Controller/MessageApiControllerTest.php b/tests/Unit/Controller/MessageApiControllerTest.php
index 7b06ac01d1..6ab43d2dea 100644
--- a/tests/Unit/Controller/MessageApiControllerTest.php
+++ b/tests/Unit/Controller/MessageApiControllerTest.php
@@ -27,6 +27,7 @@
use OCA\Mail\Service\AccountService;
use OCA\Mail\Service\AliasesService;
use OCA\Mail\Service\Attachment\AttachmentService;
+use OCA\Mail\Service\DelegationService;
use OCA\Mail\Service\DkimService;
use OCA\Mail\Service\ItineraryService;
use OCA\Mail\Service\MailManager;
@@ -58,6 +59,7 @@ class MessageApiControllerTest extends TestCase {
private DkimService|MockObject $dkimService;
private MockObject|ItineraryService $itineraryService;
private TrustedSenderService|MockObject $trustedSenderService;
+ private DelegationService|MockObject $delegationService;
private MessageApiController $controller;
private string $fromEmail = 'john@test.com';
private int $accountId = 1;
@@ -84,6 +86,9 @@ protected function setUp(): void {
$this->dkimService = $this->createMock(DkimService::class);
$this->itineraryService = $this->createMock(ItineraryService::class);
$this->trustedSenderService = $this->createMock(TrustedSenderService::class);
+ $this->delegationService = $this->createMock(DelegationService::class);
+ $this->delegationService->method('resolveAccountUserId')->willReturn($this->userId);
+ $this->delegationService->method('resolveMessageUserId')->willReturn($this->userId);
$this->controller = new MessageApiController($this->appName,
$this->userId,
@@ -100,6 +105,7 @@ protected function setUp(): void {
$this->dkimService,
$this->itineraryService,
$this->trustedSenderService,
+ $this->delegationService,
);
$mailAccount = new MailAccount();
diff --git a/tests/Unit/Controller/MessagesControllerTest.php b/tests/Unit/Controller/MessagesControllerTest.php
index 8577019883..1e443fe160 100644
--- a/tests/Unit/Controller/MessagesControllerTest.php
+++ b/tests/Unit/Controller/MessagesControllerTest.php
@@ -37,6 +37,7 @@
use OCA\Mail\Model\Message;
use OCA\Mail\Service\AccountService;
use OCA\Mail\Service\AiIntegrations\AiIntegrationsService;
+use OCA\Mail\Service\DelegationService;
use OCA\Mail\Service\ItineraryService;
use OCA\Mail\Service\MailManager;
use OCA\Mail\Service\SmimeService;
@@ -134,6 +135,8 @@ class MessagesControllerTest extends TestCase {
private ICacheFactory&MockObject $cacheFactory;
+ private DelegationService|MockObject $delegationService;
+
protected function setUp(): void {
parent::setUp();
@@ -164,6 +167,10 @@ protected function setUp(): void {
$this->cacheFactory->method('createDistributed')
->willReturn(new NullCache());
+ $this->delegationService = $this->createMock(DelegationService::class);
+ $this->delegationService->method('resolveMessageUserId')->willReturn($this->userId);
+ $this->delegationService->method('resolveMailboxUserId')->willReturn($this->userId);
+
$timeFactory = $this->createMocK(ITimeFactory::class);
$timeFactory->expects($this->any())
->method('getTime')
@@ -194,6 +201,7 @@ protected function setUp(): void {
$this->snoozeService,
$this->aiIntegrationsService,
$this->cacheFactory,
+ $this->delegationService,
);
$this->account = $this->createMock(Account::class);
@@ -1249,6 +1257,7 @@ public function testNeedsTranslationNoUser() {
$this->snoozeService,
$this->aiIntegrationsService,
$this->cacheFactory,
+ $this->delegationService,
);
$actualResponse = $controller->needsTranslation(100);
@@ -1421,6 +1430,7 @@ public function testSmartReplyNoUser(): void {
$this->snoozeService,
$this->aiIntegrationsService,
$this->cacheFactory,
+ $this->delegationService,
);
$actualResponse = $controller->smartReply(100);
diff --git a/tests/Unit/Controller/OutboxControllerTest.php b/tests/Unit/Controller/OutboxControllerTest.php
index 51f25603cf..ff31d91315 100644
--- a/tests/Unit/Controller/OutboxControllerTest.php
+++ b/tests/Unit/Controller/OutboxControllerTest.php
@@ -19,6 +19,7 @@
use OCA\Mail\Exception\ServiceException;
use OCA\Mail\Http\JsonResponse;
use OCA\Mail\Service\AccountService;
+use OCA\Mail\Service\DelegationService;
use OCA\Mail\Service\OutboxService;
use OCA\Mail\Service\SmimeService;
use OCP\AppFramework\Db\DoesNotExistException;
@@ -43,6 +44,8 @@ class OutboxControllerTest extends TestCase {
/** @var SmimeService&MockObject */
private $smimeService;
+ private DelegationService&MockObject $delegationService;
+
private OutboxController $controller;
protected function setUp(): void {
parent::setUp();
@@ -53,6 +56,11 @@ protected function setUp(): void {
$this->request = $this->createMock(IRequest::class);
$this->accountService = $this->createMock(AccountService::class);
$this->smimeService = $this->createMock(SmimeService::class);
+ $this->delegationService = $this->createMock(DelegationService::class);
+ $this->delegationService->method('resolveAccountUserId')
+ ->willReturn($this->userId);
+ $this->delegationService->method('resolveLocalMessageUserId')
+ ->willReturn($this->userId);
$this->controller = new OutboxController(
$this->appName,
@@ -61,6 +69,7 @@ protected function setUp(): void {
$this->service,
$this->accountService,
$this->smimeService,
+ $this->delegationService,
);
}
diff --git a/tests/Unit/Controller/PageControllerTest.php b/tests/Unit/Controller/PageControllerTest.php
index 804b9df2ef..9b84226810 100644
--- a/tests/Unit/Controller/PageControllerTest.php
+++ b/tests/Unit/Controller/PageControllerTest.php
@@ -205,6 +205,10 @@ public function testIndex(): void {
$account1,
$account2,
]));
+ $this->accountService->expects($this->once())
+ ->method('findDelegatedAccounts')
+ ->with($this->userId)
+ ->willReturn([]);
$this->mailManager->expects($this->exactly(2))
->method('getMailboxes')
->withConsecutive(
@@ -247,6 +251,7 @@ public function testIndex(): void {
'mailboxes' => [
$mailbox,
],
+ 'isDelegated' => false,
],
[
'accountId' => 2,
@@ -255,6 +260,7 @@ public function testIndex(): void {
'a22',
],
'mailboxes' => [],
+ 'isDelegated' => false,
],
];
diff --git a/tests/Unit/Controller/SieveControllerTest.php b/tests/Unit/Controller/SieveControllerTest.php
index c084c716ab..9a7bde01b9 100644
--- a/tests/Unit/Controller/SieveControllerTest.php
+++ b/tests/Unit/Controller/SieveControllerTest.php
@@ -43,6 +43,9 @@ protected function setUp(): void {
'userId' => '1',
]
);
+ $this->serviceMock->getParameter('delegationService')
+ ->method('resolveAccountUserId')
+ ->willReturn('1');
$this->sieveController = $this->serviceMock->getService();
}
diff --git a/tests/Unit/Controller/ThreadControllerTest.php b/tests/Unit/Controller/ThreadControllerTest.php
index bdd8220b7b..1dcf67c695 100644
--- a/tests/Unit/Controller/ThreadControllerTest.php
+++ b/tests/Unit/Controller/ThreadControllerTest.php
@@ -19,6 +19,7 @@
use OCA\Mail\Model\EventData;
use OCA\Mail\Service\AccountService;
use OCA\Mail\Service\AiIntegrations\AiIntegrationsService;
+use OCA\Mail\Service\DelegationService;
use OCA\Mail\Service\SnoozeService;
use OCP\AppFramework\Db\DoesNotExistException;
use OCP\AppFramework\Http;
@@ -54,6 +55,9 @@ class ThreadControllerTest extends TestCase {
/** @var LoggerInterface|MockObject */
private $logger;
+ /** @var DelegationService|MockObject */
+ private $delegationService;
+
protected function setUp(): void {
parent::setUp();
@@ -65,6 +69,9 @@ protected function setUp(): void {
$this->snoozeService = $this->createMock(SnoozeService::class);
$this->aiIntergrationsService = $this->createMock(AiIntegrationsService::class);
$this->logger = $this->createMock(LoggerInterface::class);
+ $this->delegationService = $this->createMock(DelegationService::class);
+ $this->delegationService->method('resolveMessageUserId')->willReturn($this->userId);
+ $this->delegationService->method('resolveMailboxUserId')->willReturn($this->userId);
$this->controller = new ThreadController(
$this->appName,
@@ -75,6 +82,7 @@ protected function setUp(): void {
$this->snoozeService,
$this->aiIntergrationsService,
$this->logger,
+ $this->delegationService,
);
}
diff --git a/tests/Unit/Service/AccountServiceTest.php b/tests/Unit/Service/AccountServiceTest.php
index e218a28fc6..242b2cb427 100644
--- a/tests/Unit/Service/AccountServiceTest.php
+++ b/tests/Unit/Service/AccountServiceTest.php
@@ -13,6 +13,7 @@
use OCA\Mail\Account;
use OCA\Mail\BackgroundJob\QuotaJob;
use OCA\Mail\BackgroundJob\SyncJob;
+use OCA\Mail\Db\DelegationMapper;
use OCA\Mail\Db\MailAccount;
use OCA\Mail\Db\MailAccountMapper;
use OCA\Mail\Exception\ClientException;
@@ -61,6 +62,7 @@ class AccountServiceTest extends TestCase {
private IConfig&MockObject $config;
private ITimeFactory&MockObject $time;
+ private DelegationMapper&MockObject $delegationMapper;
protected function setUp(): void {
parent::setUp();
@@ -72,6 +74,7 @@ protected function setUp(): void {
$this->imapClientFactory = $this->createMock(IMAPClientFactory::class);
$this->config = $this->createMock(IConfig::class);
$this->time = $this->createMock(ITimeFactory::class);
+ $this->delegationMapper = $this->createMock(DelegationMapper::class);
$this->accountService = new AccountService(
$this->mapper,
$this->aliasesService,
@@ -79,6 +82,7 @@ protected function setUp(): void {
$this->imapClientFactory,
$this->config,
$this->time,
+ $this->delegationMapper,
);
$this->account1 = new MailAccount();
diff --git a/tests/Unit/Service/DelegationServiceTest.php b/tests/Unit/Service/DelegationServiceTest.php
new file mode 100644
index 0000000000..dd2f1a0ac6
--- /dev/null
+++ b/tests/Unit/Service/DelegationServiceTest.php
@@ -0,0 +1,333 @@
+delegationMapper = $this->createMock(DelegationMapper::class);
+ $this->mailAccountMapper = $this->createMock(MailAccountMapper::class);
+ $this->mailboxMapper = $this->createMock(MailboxMapper::class);
+ $this->messageMapper = $this->createMock(MessageMapper::class);
+ $this->aliasMapper = $this->createMock(AliasMapper::class);
+ $this->localMessageMapper = $this->createMock(LocalMessageMapper::class);
+ $this->userManager = $this->createMock(IUserManager::class);
+ $this->notificationManager = $this->createMock(IManager::class);
+ $this->timeFactory = $this->createMock(ITimeFactory::class);
+ $this->eventDispatcher = $this->createMock(IEventDispatcher::class);
+
+ $this->service = new DelegationService(
+ $this->delegationMapper,
+ $this->mailAccountMapper,
+ $this->mailboxMapper,
+ $this->messageMapper,
+ $this->aliasMapper,
+ $this->localMessageMapper,
+ $this->userManager,
+ $this->notificationManager,
+ $this->timeFactory,
+ $this->eventDispatcher,
+ );
+
+ $mailAccount = new MailAccount();
+ $mailAccount->setId(1);
+ $mailAccount->setUserId('owner');
+ $mailAccount->setEmail('owner@example.com');
+ $this->account = new Account($mailAccount);
+ }
+
+ private function mockNotification(): void {
+ $notification = $this->createMock(INotification::class);
+ $notification->method('setApp')->willReturnSelf();
+ $notification->method('setUser')->willReturnSelf();
+ $notification->method('setObject')->willReturnSelf();
+ $notification->method('setSubject')->willReturnSelf();
+ $notification->method('setDateTime')->willReturnSelf();
+ $notification->method('setMessage')->willReturnSelf();
+
+ $this->notificationManager->method('createNotification')->willReturn($notification);
+
+ $user = $this->createMock(IUser::class);
+ $user->method('getDisplayName')->willReturn('Owner User');
+ $this->userManager->method('get')->with('owner')->willReturn($user);
+ $this->timeFactory->method('getDateTime')->willReturn(new \DateTime());
+ }
+
+ public function testDelegateSuccess(): void {
+ $this->mockNotification();
+
+ $this->delegationMapper->expects($this->once())
+ ->method('find')
+ ->with(1, 'delegatee')
+ ->willThrowException(new DoesNotExistException('Not found'));
+
+ $expected = new Delegation();
+ $expected->setAccountId(1);
+ $expected->setUserId('delegatee');
+
+ $this->delegationMapper->expects($this->once())
+ ->method('insert')
+ ->willReturnCallback(function (Delegation $d) {
+ $d->setId(10);
+ return $d;
+ });
+
+ $this->notificationManager->expects($this->once())
+ ->method('notify');
+
+ $result = $this->service->delegate($this->account, 'delegatee', 'owner');
+
+ $this->assertEquals(1, $result->getAccountId());
+ $this->assertEquals('delegatee', $result->getUserId());
+ }
+
+ public function testDelegateThrowsWhenAlreadyExists(): void {
+ $existing = new Delegation();
+ $existing->setAccountId(1);
+ $existing->setUserId('delegatee');
+
+ $this->delegationMapper->expects($this->once())
+ ->method('find')
+ ->with(1, 'delegatee')
+ ->willReturn($existing);
+
+ $this->delegationMapper->expects($this->never())
+ ->method('insert');
+
+ $this->expectException(DelegationExistsException::class);
+
+ $this->service->delegate($this->account, 'delegatee', 'owner');
+ }
+
+ public function testFindDelegatedToUsersForAccount(): void {
+ $delegation = new Delegation();
+ $delegation->setAccountId(1);
+ $delegation->setUserId('delegatee');
+
+ $this->delegationMapper->expects($this->once())
+ ->method('findDelegatedToUsers')
+ ->with(1)
+ ->willReturn([$delegation]);
+
+ $result = $this->service->findDelegatedToUsersForAccount(1);
+
+ $this->assertCount(1, $result);
+ $this->assertEquals('delegatee', $result[0]->getUserId());
+ }
+
+ public function testUnDelegateSuccess(): void {
+ $this->mockNotification();
+
+ $delegation = new Delegation();
+ $delegation->setId(10);
+ $delegation->setAccountId(1);
+ $delegation->setUserId('delegatee');
+
+ $this->delegationMapper->expects($this->once())
+ ->method('find')
+ ->with(1, 'delegatee')
+ ->willReturn($delegation);
+
+ $this->delegationMapper->expects($this->once())
+ ->method('delete')
+ ->with($delegation);
+
+ $this->notificationManager->expects($this->once())
+ ->method('notify');
+
+ $this->service->unDelegate($this->account, 'delegatee', 'owner');
+ }
+
+ public function testUnDelegateThrowsWhenNotFound(): void {
+ $this->delegationMapper->expects($this->once())
+ ->method('find')
+ ->with(1, 'delegatee')
+ ->willThrowException(new DoesNotExistException('Not found'));
+
+ $this->delegationMapper->expects($this->never())
+ ->method('delete');
+
+ $this->expectException(DoesNotExistException::class);
+
+ $this->service->unDelegate($this->account, 'delegatee', 'owner');
+ }
+
+ public function testResolveAccountUserIdOwner(): void {
+ $mailAccount = new MailAccount();
+ $mailAccount->setId(1);
+ $mailAccount->setUserId('owner');
+
+ $this->mailAccountMapper->expects($this->once())
+ ->method('find')
+ ->with('owner', 1)
+ ->willReturn($mailAccount);
+
+ $result = $this->service->resolveAccountUserId(1, 'owner');
+
+ $this->assertEquals('owner', $result);
+ }
+
+ public function testResolveAccountUserIdDelegated(): void {
+ $this->mailAccountMapper->expects($this->once())
+ ->method('find')
+ ->with('delegatee', 1)
+ ->willThrowException(new DoesNotExistException('Not found'));
+
+ $this->delegationMapper->expects($this->once())
+ ->method('findAccountOwnerForDelegatedUser')
+ ->with(1, 'delegatee')
+ ->willReturn('owner');
+
+ $result = $this->service->resolveAccountUserId(1, 'delegatee');
+
+ $this->assertEquals('owner', $result);
+ }
+
+ public function testResolveAccountUserIdNotFound(): void {
+ $this->mailAccountMapper->expects($this->once())
+ ->method('find')
+ ->with('stranger', 1)
+ ->willThrowException(new DoesNotExistException('Not found'));
+
+ $this->delegationMapper->expects($this->once())
+ ->method('findAccountOwnerForDelegatedUser')
+ ->with(1, 'stranger')
+ ->willThrowException(new DoesNotExistException('No delegation found'));
+
+ $this->expectException(DoesNotExistException::class);
+
+ $this->service->resolveAccountUserId(1, 'stranger');
+ }
+
+ public function testResolveMailboxUserId(): void {
+ $mailAccount = new MailAccount();
+ $mailAccount->setId(1);
+ $mailAccount->setUserId('owner');
+
+ $this->mailboxMapper->expects($this->once())
+ ->method('findAccountIdForMailbox')
+ ->with(42)
+ ->willReturn(1);
+
+ $this->mailAccountMapper->expects($this->once())
+ ->method('find')
+ ->with('owner', 1)
+ ->willReturn($mailAccount);
+
+ $result = $this->service->resolveMailboxUserId(42, 'owner');
+
+ $this->assertEquals('owner', $result);
+ }
+
+ public function testResolveMessageUserId(): void {
+ $mailAccount = new MailAccount();
+ $mailAccount->setId(1);
+ $mailAccount->setUserId('owner');
+
+ $this->messageMapper->expects($this->once())
+ ->method('findAccountIdForMessage')
+ ->with(99)
+ ->willReturn(1);
+
+ $this->mailAccountMapper->expects($this->once())
+ ->method('find')
+ ->with('owner', 1)
+ ->willReturn($mailAccount);
+
+ $result = $this->service->resolveMessageUserId(99, 'owner');
+
+ $this->assertEquals('owner', $result);
+ }
+
+ public function testResolveAliasUserId(): void {
+ $mailAccount = new MailAccount();
+ $mailAccount->setId(1);
+ $mailAccount->setUserId('owner');
+
+ $this->aliasMapper->expects($this->once())
+ ->method('findAccountIdForAlias')
+ ->with(7)
+ ->willReturn(1);
+
+ $this->mailAccountMapper->expects($this->once())
+ ->method('find')
+ ->with('owner', 1)
+ ->willReturn($mailAccount);
+
+ $result = $this->service->resolveAliasUserId(7, 'owner');
+
+ $this->assertEquals('owner', $result);
+ }
+
+ public function testResolveLocalMessageUserId(): void {
+ $mailAccount = new MailAccount();
+ $mailAccount->setId(1);
+ $mailAccount->setUserId('owner');
+
+ $this->localMessageMapper->expects($this->once())
+ ->method('findAccountIdForLocalMessage')
+ ->with(55)
+ ->willReturn(1);
+
+ $this->mailAccountMapper->expects($this->once())
+ ->method('find')
+ ->with('owner', 1)
+ ->willReturn($mailAccount);
+
+ $result = $this->service->resolveLocalMessageUserId(55, 'owner');
+
+ $this->assertEquals('owner', $result);
+ }
+
+ public function testLogDelegatedAction(): void {
+ $this->eventDispatcher->expects($this->once())
+ ->method('dispatchTyped')
+ ->with($this->isInstanceOf(CriticalActionPerformedEvent::class));
+
+ $this->service->logDelegatedAction('User owner read mailbox on behalf of owner');
+ }
+}