318 lines
10 KiB
PHP
318 lines
10 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
/**
|
|
* SPDX-FileCopyrightText: 2026 F7cloud / F7cloud GmbH and F7cloud contributors
|
|
* SPDX-License-Identifier: AGPL-3.0-or-later
|
|
*/
|
|
|
|
namespace OCA\Mail\Service;
|
|
|
|
use OCA\Mail\Account;
|
|
use OCA\Mail\Contracts\IMailManager;
|
|
use OCA\Mail\Db\Mailbox;
|
|
use OCA\Mail\Db\MailboxMapper;
|
|
use OCA\Mail\Db\MailboxShare;
|
|
use OCA\Mail\Db\MailboxShareMapper;
|
|
use OCP\AppFramework\Db\DoesNotExistException;
|
|
use OCP\AppFramework\Utility\ITimeFactory;
|
|
use OCP\IGroupManager;
|
|
use OCP\IUserManager;
|
|
|
|
class MailboxShareService {
|
|
|
|
public function __construct(
|
|
private MailboxShareMapper $mailboxShareMapper,
|
|
private MailboxMapper $mailboxMapper,
|
|
private AccountService $accountService,
|
|
private IMailManager $mailManager,
|
|
private IUserManager $userManager,
|
|
private IGroupManager $groupManager,
|
|
private ITimeFactory $timeFactory,
|
|
) {
|
|
}
|
|
|
|
/**
|
|
* Create a share for a mailbox. Caller must ensure the mailbox belongs to $ownerUserId.
|
|
*
|
|
* @throws \InvalidArgumentException if permission or shareType is invalid, or share already exists
|
|
*/
|
|
public function createShare(
|
|
string $ownerUserId,
|
|
int $accountId,
|
|
int $mailboxId,
|
|
string $shareType,
|
|
string $shareWith,
|
|
string $permission,
|
|
): MailboxShare {
|
|
if ($shareType !== MailboxShare::TYPE_USER && $shareType !== MailboxShare::TYPE_GROUP) {
|
|
throw new \InvalidArgumentException('Invalid share type');
|
|
}
|
|
if ($permission !== MailboxShare::PERMISSION_READ && $permission !== MailboxShare::PERMISSION_READ_WRITE) {
|
|
throw new \InvalidArgumentException('Invalid permission');
|
|
}
|
|
if ($this->mailboxShareMapper->shareExists($ownerUserId, $accountId, $mailboxId, $shareType, $shareWith)) {
|
|
throw new \InvalidArgumentException('Share already exists');
|
|
}
|
|
|
|
$share = new MailboxShare();
|
|
$share->setOwnerUserId($ownerUserId);
|
|
$share->setAccountId($accountId);
|
|
$share->setMailboxId($mailboxId);
|
|
$share->setShareType($shareType);
|
|
$share->setShareWith($shareWith);
|
|
$share->setPermission($permission);
|
|
$share->setCreatedAt($this->timeFactory->getTime());
|
|
|
|
return $this->mailboxShareMapper->insert($share);
|
|
}
|
|
|
|
/**
|
|
* Delete a share by id. Caller must ensure the current user is the owner.
|
|
*/
|
|
public function deleteShare(int $shareId, string $ownerUserId): void {
|
|
$share = $this->mailboxShareMapper->find($shareId);
|
|
if ($share->getOwnerUserId() !== $ownerUserId) {
|
|
throw new \InvalidArgumentException('Not the share owner');
|
|
}
|
|
$this->mailboxShareMapper->deleteById($shareId);
|
|
}
|
|
|
|
/**
|
|
* List shares for a mailbox (owner view: who has access).
|
|
*
|
|
* @return MailboxShare[]
|
|
*/
|
|
public function getSharesForMailbox(string $ownerUserId, int $accountId, int $mailboxId): array {
|
|
return $this->mailboxShareMapper->findByMailbox($ownerUserId, $accountId, $mailboxId);
|
|
}
|
|
|
|
/**
|
|
* Build list "shared with me" for the given user: each item includes owner info, shared mailbox, and subfolders.
|
|
* Resolves user + group ids for share_with, then loads owner's mailbox tree and attaches subfolders.
|
|
*
|
|
* @return array<int, array{shareId: int, ownerUserId: string, ownerDisplayName: string, accountId: int, mailbox: array, permission: string, subMailboxes: array}>
|
|
*/
|
|
public function getSharedWithMe(string $currentUserId): array {
|
|
$user = $this->userManager->get($currentUserId);
|
|
if ($user === null) {
|
|
return [];
|
|
}
|
|
$groupIds = $this->groupManager->getUserGroupIds($user);
|
|
$shareWithIds = array_merge([$currentUserId], $groupIds);
|
|
$shares = $this->mailboxShareMapper->findSharedWith($shareWithIds);
|
|
if ($shares === []) {
|
|
return [];
|
|
}
|
|
|
|
$result = [];
|
|
foreach ($shares as $share) {
|
|
try {
|
|
$ownerAccount = $this->accountService->find($share->getOwnerUserId(), $share->getAccountId());
|
|
} catch (DoesNotExistException $e) {
|
|
continue;
|
|
}
|
|
try {
|
|
$mailboxes = $this->mailManager->getMailboxes($ownerAccount);
|
|
} catch (\Throwable $e) {
|
|
continue;
|
|
}
|
|
$sharedMailbox = null;
|
|
foreach ($mailboxes as $mb) {
|
|
if ((int)$mb->getId() === $share->getMailboxId()) {
|
|
$sharedMailbox = $mb;
|
|
break;
|
|
}
|
|
}
|
|
if ($sharedMailbox === null) {
|
|
continue;
|
|
}
|
|
|
|
$delimiter = $sharedMailbox->getDelimiter() ?? '.';
|
|
$prefix = $sharedMailbox->getName() . $delimiter;
|
|
$subMailboxes = [];
|
|
foreach ($mailboxes as $mb) {
|
|
if ((int)$mb->getId() === (int)$sharedMailbox->getId()) {
|
|
continue;
|
|
}
|
|
$name = $mb->getName();
|
|
if ($name !== $sharedMailbox->getName() && str_starts_with($name, $prefix)) {
|
|
$subMailboxes[] = $this->mailboxToJson($mb);
|
|
}
|
|
}
|
|
|
|
$ownerDisplayName = $currentUserId;
|
|
$ownerUser = $this->userManager->get($share->getOwnerUserId());
|
|
if ($ownerUser !== null) {
|
|
$ownerDisplayName = $ownerUser->getDisplayName();
|
|
}
|
|
|
|
$result[] = [
|
|
'shareId' => $share->getId(),
|
|
'ownerUserId' => $share->getOwnerUserId(),
|
|
'ownerDisplayName' => $ownerDisplayName,
|
|
'accountId' => $share->getAccountId(),
|
|
'mailbox' => $this->mailboxToJson($sharedMailbox),
|
|
'permission' => $share->getPermission(),
|
|
'subMailboxes' => $subMailboxes,
|
|
];
|
|
}
|
|
|
|
return $result;
|
|
}
|
|
|
|
/**
|
|
* Find a share by id and verify the current user has access (as share_with user or group member).
|
|
* Returns the share entity or null.
|
|
*/
|
|
public function getShareForUser(int $shareId, string $currentUserId): ?MailboxShare {
|
|
try {
|
|
$share = $this->mailboxShareMapper->find($shareId);
|
|
} catch (DoesNotExistException $e) {
|
|
return null;
|
|
}
|
|
if ($share->getShareWith() === $currentUserId) {
|
|
return $share;
|
|
}
|
|
if ($share->getShareType() === MailboxShare::TYPE_GROUP) {
|
|
$user = $this->userManager->get($currentUserId);
|
|
if ($user !== null && $this->groupManager->isInGroup($currentUserId, $share->getShareWith())) {
|
|
return $share;
|
|
}
|
|
}
|
|
return null;
|
|
}
|
|
|
|
/**
|
|
* Check if the given mailbox is the shared root or a descendant of a shared mailbox for this user.
|
|
* Returns the MailboxShare that grants access, or null.
|
|
*/
|
|
public function getShareForMailboxAccess(string $currentUserId, int $accountId, int $mailboxId): ?MailboxShare {
|
|
$user = $this->userManager->get($currentUserId);
|
|
if ($user === null) {
|
|
return null;
|
|
}
|
|
try {
|
|
$targetMailbox = $this->mailboxMapper->findById($mailboxId);
|
|
} catch (DoesNotExistException $e) {
|
|
return null;
|
|
}
|
|
if ($targetMailbox->getAccountId() !== $accountId) {
|
|
return null;
|
|
}
|
|
$groupIds = $this->groupManager->getUserGroupIds($user);
|
|
$shareWithIds = array_merge([$currentUserId], $groupIds);
|
|
$shares = $this->mailboxShareMapper->findSharedWith($shareWithIds);
|
|
foreach ($shares as $share) {
|
|
if ($share->getAccountId() !== $accountId) {
|
|
continue;
|
|
}
|
|
if ($share->getMailboxId() === $mailboxId) {
|
|
return $share;
|
|
}
|
|
try {
|
|
$rootMailbox = $this->mailboxMapper->findById($share->getMailboxId());
|
|
} catch (DoesNotExistException $e) {
|
|
continue;
|
|
}
|
|
if ($rootMailbox->getAccountId() !== $accountId) {
|
|
continue;
|
|
}
|
|
$delimiter = $rootMailbox->getDelimiter() ?? '.';
|
|
$prefix = $rootMailbox->getName() . $delimiter;
|
|
$targetName = $targetMailbox->getName();
|
|
if ($targetName === $rootMailbox->getName() || str_starts_with($targetName, $prefix)) {
|
|
return $share;
|
|
}
|
|
}
|
|
return null;
|
|
}
|
|
|
|
/**
|
|
* Resolve mailbox and account for the current user, optionally in shared context.
|
|
* Returns ['mailbox' => Mailbox, 'account' => Account, 'share' => MailboxShare|null] or null if no access.
|
|
*
|
|
* @return array{mailbox: Mailbox, account: Account, share: MailboxShare|null}|null
|
|
*/
|
|
public function resolveMailboxForAccess(string $currentUserId, int $mailboxId, ?int $shareId = null): ?array {
|
|
if ($currentUserId === '') {
|
|
return null;
|
|
}
|
|
if ($shareId !== null) {
|
|
$share = $this->getShareForUser($shareId, $currentUserId);
|
|
if ($share === null) {
|
|
return null;
|
|
}
|
|
$shareForMailbox = $this->getShareForMailboxAccess($currentUserId, $share->getAccountId(), $mailboxId);
|
|
if ($shareForMailbox === null || $shareForMailbox->getId() !== $share->getId()) {
|
|
return null;
|
|
}
|
|
try {
|
|
$mailbox = $this->mailManager->getMailbox($share->getOwnerUserId(), $mailboxId);
|
|
$account = $this->accountService->find($share->getOwnerUserId(), $share->getAccountId());
|
|
} catch (\Throwable $e) {
|
|
return null;
|
|
}
|
|
return ['mailbox' => $mailbox, 'account' => $account, 'share' => $share];
|
|
}
|
|
try {
|
|
$mailbox = $this->mailManager->getMailbox($currentUserId, $mailboxId);
|
|
$account = $this->accountService->find($currentUserId, $mailbox->getAccountId());
|
|
} catch (\Throwable $e) {
|
|
return null;
|
|
}
|
|
return ['mailbox' => $mailbox, 'account' => $account, 'share' => null];
|
|
}
|
|
|
|
/**
|
|
* Resolve message, mailbox and account for the current user, optionally in shared context.
|
|
* Returns ['message' => Message, 'mailbox' => Mailbox, 'account' => Account, 'share' => MailboxShare|null] or null if no access.
|
|
*
|
|
* @return array{message: Message, mailbox: Mailbox, account: Account, share: MailboxShare|null}|null
|
|
*/
|
|
public function resolveMessageAccess(string $currentUserId, int $messageId, ?int $shareId = null): ?array {
|
|
if ($currentUserId === '') {
|
|
return null;
|
|
}
|
|
$ownerUserId = $currentUserId;
|
|
$share = null;
|
|
if ($shareId !== null) {
|
|
$share = $this->getShareForUser($shareId, $currentUserId);
|
|
if ($share === null) {
|
|
return null;
|
|
}
|
|
$ownerUserId = $share->getOwnerUserId();
|
|
}
|
|
try {
|
|
$message = $this->mailManager->getMessage($ownerUserId, $messageId);
|
|
} catch (DoesNotExistException $e) {
|
|
return null;
|
|
}
|
|
if ($share !== null) {
|
|
$shareForMailbox = $this->getShareForMailboxAccess($currentUserId, $share->getAccountId(), $message->getMailboxId());
|
|
if ($shareForMailbox === null || $shareForMailbox->getId() !== $share->getId()) {
|
|
return null;
|
|
}
|
|
}
|
|
try {
|
|
$mailbox = $this->mailManager->getMailbox($ownerUserId, $message->getMailboxId());
|
|
$account = $this->accountService->find($ownerUserId, $mailbox->getAccountId());
|
|
} catch (\Throwable $e) {
|
|
return null;
|
|
}
|
|
return ['message' => $message, 'mailbox' => $mailbox, 'account' => $account, 'share' => $share];
|
|
}
|
|
|
|
private function mailboxToJson(Mailbox $mailbox): array {
|
|
$json = $mailbox->jsonSerialize();
|
|
return [
|
|
'id' => $json['databaseId'],
|
|
'name' => $json['name'],
|
|
'displayName' => $json['displayName'],
|
|
'delimiter' => $json['delimiter'],
|
|
'unread' => $json['unread'],
|
|
];
|
|
}
|
|
}
|