diff --git a/lib/Controller/DelegationController.php b/lib/Controller/DelegationController.php index 487e094ef9..5e5502372e 100644 --- a/lib/Controller/DelegationController.php +++ b/lib/Controller/DelegationController.php @@ -86,7 +86,7 @@ public function delegate(int $accountId, string $userId): JSONResponse { } try { - $delegation = $this->delegationService->delegate($accountId, $userId); + $delegation = $this->delegationService->delegate($account, $userId, $this->currentUserId); } catch (DelegationExistsException) { return new JSONResponse(['message' => 'Delegation already exists'], Http::STATUS_CONFLICT); } @@ -111,7 +111,7 @@ public function unDelegate(int $accountId, string $userId): JSONResponse { return new JSONResponse([], Http::STATUS_UNAUTHORIZED); } - $this->delegationService->unDelegate($accountId, $userId); + $this->delegationService->unDelegate($account, $userId, $this->currentUserId); return new JSONResponse([], Http::STATUS_OK); } } diff --git a/lib/Notification/Notifier.php b/lib/Notification/Notifier.php index dedd2e7ebd..f3402b3cee 100644 --- a/lib/Notification/Notifier.php +++ b/lib/Notification/Notifier.php @@ -72,8 +72,60 @@ public function prepare(INotification $notification, string $languageCode): INot ] ]); break; + case 'account_delegation': + $notification->setIcon($this->url->getAbsoluteURL( + $this->url->linkTo('mail', 'img/delegation.svg') + )); + $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/Service/DelegationService.php b/lib/Service/DelegationService.php index d94e9fdbf8..87f80bd573 100644 --- a/lib/Service/DelegationService.php +++ b/lib/Service/DelegationService.php @@ -9,6 +9,7 @@ namespace OCA\Mail\Service; +use OCA\Mail\Account; use OCA\Mail\Db\AliasMapper; use OCA\Mail\Db\Delegation; use OCA\Mail\Db\DelegationMapper; @@ -18,6 +19,9 @@ use OCA\Mail\Exception\ClientException; use OCA\Mail\Exception\DelegationExistsException; use OCP\AppFramework\Db\DoesNotExistException; +use OCP\AppFramework\Utility\ITimeFactory; +use OCP\IUserManager; +use OCP\Notification\IManager; class DelegationService { @@ -28,10 +32,14 @@ public function __construct( private MessageMapper $messageMapper, private AliasMapper $aliasMapper, private LocalMessageMapper $localMessageMapper, + private IUserManager $userManager, + private IManager $notificationManager, + private ITimeFactory $time, ) { } - public function delegate(int $accountId, string $userId): Delegation { + public function delegate(Account $account, string $userId, string $currentUserId): Delegation { + $accountId = $account->getId(); try { $this->delegationMapper->find($accountId, $userId); throw new DelegationExistsException("Delegation already exists for account $accountId and user $userId"); @@ -42,17 +50,21 @@ public function delegate(int $accountId, string $userId): Delegation { $delegation = new Delegation(); $delegation->setAccountId($accountId); $delegation->setUserId($userId); - return $this->delegationMapper->insert($delegation); + $result = $this->delegationMapper->insert($delegation); + $this->notify($userId, $currentUserId, $account, true); + return $result; } public function findDelegatedToUsersForAccount(int $accountId): array { return $this->delegationMapper->findDelegatedToUsers($accountId); } - public function unDelegate(int $accountId, string $userId): void { + public function unDelegate(Account $account, string $userId, string $currentUserId): void { try { + $accountId = $account->getId(); $delegation = $this->delegationMapper->find($accountId, $userId); $this->delegationMapper->delete($delegation); + $this->notify($userId, $currentUserId, $account, false); } catch (DoesNotExistException $e) { // shouldn't end up here // delegation not found nothing to undelegate @@ -112,4 +124,37 @@ public function resolveLocalMessageUserId(int $localMessageId, string $currentUs $accountId = $this->localMessageMapper->findAccountIdForLocalMessage($localMessageId); return $this->resolveAccountUserId($accountId, $currentUserId); } + + /** + * 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/Unit/Controller/DelegationControllerTest.php b/tests/Unit/Controller/DelegationControllerTest.php index e0a6798c71..582ff6ce91 100644 --- a/tests/Unit/Controller/DelegationControllerTest.php +++ b/tests/Unit/Controller/DelegationControllerTest.php @@ -122,7 +122,7 @@ public function testDelegateSuccess(): void { $this->delegationService->expects($this->once()) ->method('delegate') - ->with(1, 'delegatee') + ->with($this->ownAccount, 'delegatee', $this->currentUserId) ->willReturn($delegation); $response = $this->controller->delegate(1, 'delegatee'); @@ -217,7 +217,7 @@ public function testDelegateAlreadyExists(): void { $this->delegationService->expects($this->once()) ->method('delegate') - ->with(1, 'delegatee') + ->with($this->ownAccount, 'delegatee', $this->currentUserId) ->willThrowException(new DelegationExistsException('Delegation already exists')); $response = $this->controller->delegate(1, 'delegatee'); @@ -235,7 +235,7 @@ public function testUnDelegateSuccess(): void { $this->delegationService->expects($this->once()) ->method('unDelegate') - ->with(1, 'delegatee'); + ->with($this->ownAccount, 'delegatee', $this->currentUserId); $response = $this->controller->unDelegate(1, 'delegatee'); diff --git a/tests/Unit/Notification/NotifierTest.php b/tests/Unit/Notification/NotifierTest.php new file mode 100644 index 0000000000..ac8dd04c31 --- /dev/null +++ b/tests/Unit/Notification/NotifierTest.php @@ -0,0 +1,185 @@ +factory = $this->createMock(IFactory::class); + $this->url = $this->createMock(IURLGenerator::class); + $this->l10n = $this->createMock(IL10N::class); + $this->l10n->method('t')->willReturnArgument(0); + $this->factory->method('get')->willReturn($this->l10n); + + $this->notifier = new Notifier($this->factory, $this->url); + } + + public function testGetID(): void { + $this->assertEquals('mail', $this->notifier->getID()); + } + + public function testGetName(): void { + $this->assertEquals('Mail', $this->notifier->getName()); + } + + public function testPrepareForeignAppThrows(): void { + $notification = $this->createMock(INotification::class); + $notification->method('getApp')->willReturn('other'); + + $this->expectException(UnknownNotificationException::class); + + $this->notifier->prepare($notification, 'en'); + } + + public function testPrepareUnknownSubjectThrows(): void { + $notification = $this->createMock(INotification::class); + $notification->method('getApp')->willReturn('mail'); + $notification->method('getSubject')->willReturn('something_unknown'); + + $this->expectException(UnknownNotificationException::class); + + $this->notifier->prepare($notification, 'en'); + } + + public function testPrepareAccountDelegationDelegated(): void { + $notification = $this->createMock(INotification::class); + $notification->method('getApp')->willReturn('mail'); + $notification->method('getSubject')->willReturn('account_delegation'); + $notification->method('getSubjectParameters')->willReturn([ + 'id' => 1, + 'account_email' => 'owner@example.com', + ]); + $notification->method('getMessageParameters')->willReturn([ + 'id' => 1, + 'delegated' => true, + 'current_user_id' => 'owner', + 'current_user_display_name' => 'Owner User', + 'account_email' => 'owner@example.com', + ]); + + $this->url->method('linkTo')->with('mail', 'img/delegation.svg')->willReturn('/apps/mail/img/delegation.svg'); + $this->url->method('getAbsoluteURL')->with('/apps/mail/img/delegation.svg')->willReturn('https://example.com/apps/mail/img/delegation.svg'); + + $notification->expects($this->once()) + ->method('setIcon') + ->with('https://example.com/apps/mail/img/delegation.svg') + ->willReturnSelf(); + $notification->expects($this->once()) + ->method('setRichSubject') + ->with( + '{account_email} has been delegated to you', + [ + 'account_email' => [ + 'type' => 'highlight', + 'id' => '1', + 'name' => 'owner@example.com', + ], + ] + ) + ->willReturnSelf(); + $notification->expects($this->once()) + ->method('setRichMessage') + ->with( + '{user} delegated {account} to you', + [ + 'user' => [ + 'type' => 'user', + 'id' => 'owner', + 'name' => 'Owner User', + ], + 'account' => [ + 'type' => 'highlight', + 'id' => '1', + 'name' => 'owner@example.com', + ], + ] + ) + ->willReturnSelf(); + + $result = $this->notifier->prepare($notification, 'en'); + + $this->assertSame($notification, $result); + } + + public function testPrepareAccountDelegationRevoked(): void { + $notification = $this->createMock(INotification::class); + $notification->method('getApp')->willReturn('mail'); + $notification->method('getSubject')->willReturn('account_delegation'); + $notification->method('getSubjectParameters')->willReturn([ + 'id' => 1, + 'account_email' => 'owner@example.com', + ]); + $notification->method('getMessageParameters')->willReturn([ + 'id' => 1, + 'delegated' => false, + 'current_user_id' => 'owner', + 'current_user_display_name' => 'Owner User', + 'account_email' => 'owner@example.com', + ]); + + $this->url->method('linkTo')->with('mail', 'img/delegation.svg')->willReturn('/apps/mail/img/delegation.svg'); + $this->url->method('getAbsoluteURL')->with('/apps/mail/img/delegation.svg')->willReturn('https://example.com/apps/mail/img/delegation.svg'); + + $notification->expects($this->once()) + ->method('setIcon') + ->with('https://example.com/apps/mail/img/delegation.svg') + ->willReturnSelf(); + $notification->expects($this->once()) + ->method('setRichSubject') + ->with( + '{account_email} is no longer delegated to you', + [ + 'account_email' => [ + 'type' => 'highlight', + 'id' => '1', + 'name' => 'owner@example.com', + ], + ] + ) + ->willReturnSelf(); + $notification->expects($this->once()) + ->method('setRichMessage') + ->with( + '{user} revoked delagation for {account}', + [ + 'user' => [ + 'type' => 'user', + 'id' => 'owner', + 'name' => 'Owner User', + ], + 'account' => [ + 'type' => 'highlight', + 'id' => '1', + 'name' => 'owner@example.com', + ], + ] + ) + ->willReturnSelf(); + + $result = $this->notifier->prepare($notification, 'en'); + + $this->assertSame($notification, $result); + } +} diff --git a/tests/Unit/Service/DelegationServiceTest.php b/tests/Unit/Service/DelegationServiceTest.php index c0ab17becf..6843a35fa6 100644 --- a/tests/Unit/Service/DelegationServiceTest.php +++ b/tests/Unit/Service/DelegationServiceTest.php @@ -24,7 +24,6 @@ use OCA\Mail\Service\DelegationService; use OCP\AppFramework\Db\DoesNotExistException; use OCP\AppFramework\Utility\ITimeFactory; -use OCP\EventDispatcher\IEventDispatcher; use OCP\IUser; use OCP\IUserManager; use OCP\Notification\IManager; @@ -41,7 +40,6 @@ class DelegationServiceTest extends TestCase { private IUserManager&MockObject $userManager; private IManager&MockObject $notificationManager; private ITimeFactory&MockObject $timeFactory; - private IEventDispatcher&MockObject $eventDispatcher; private DelegationService $service; private Account $account; @@ -58,7 +56,6 @@ protected function setUp(): void { $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, @@ -67,6 +64,9 @@ protected function setUp(): void { $this->messageMapper, $this->aliasMapper, $this->localMessageMapper, + $this->userManager, + $this->notificationManager, + $this->timeFactory, ); $mailAccount = new MailAccount(); @@ -76,7 +76,7 @@ protected function setUp(): void { $this->account = new Account($mailAccount); } - private function mockNotification(): void { + private function mockNotification(): INotification&MockObject { $notification = $this->createMock(INotification::class); $notification->method('setApp')->willReturnSelf(); $notification->method('setUser')->willReturnSelf(); @@ -91,6 +91,8 @@ private function mockNotification(): void { $user->method('getDisplayName')->willReturn('Owner User'); $this->userManager->method('get')->with('owner')->willReturn($user); $this->timeFactory->method('getDateTime')->willReturn(new \DateTime()); + + return $notification; } public function testDelegateSuccess(): void { @@ -112,7 +114,7 @@ public function testDelegateSuccess(): void { return $d; }); - $result = $this->service->delegate($this->account->getId(), 'delegatee'); + $result = $this->service->delegate($this->account, 'delegatee', 'owner'); $this->assertEquals(1, $result->getAccountId()); $this->assertEquals('delegatee', $result->getUserId()); @@ -133,7 +135,7 @@ public function testDelegateThrowsWhenAlreadyExists(): void { $this->expectException(DelegationExistsException::class); - $this->service->delegate($this->account->getId(), 'delegatee'); + $this->service->delegate($this->account, 'delegatee', 'owner'); } public function testFindDelegatedToUsersForAccount(): void { @@ -169,10 +171,12 @@ public function testUnDelegateSuccess(): void { ->method('delete') ->with($delegation); - $this->service->unDelegate($this->account->getId(), 'delegatee'); + $this->service->unDelegate($this->account, 'delegatee', 'owner'); } public function testUnDelegateWhenNotFound(): void { + $this->mockNotification(); + $this->delegationMapper->expects($this->once()) ->method('find') ->with(1, 'delegatee') @@ -181,7 +185,7 @@ public function testUnDelegateWhenNotFound(): void { $this->delegationMapper->expects($this->never()) ->method('delete'); - $this->service->unDelegate($this->account->getId(), 'delegatee'); + $this->service->unDelegate($this->account, 'delegatee', 'owner'); } public function testResolveAccountUserIdOwner(): void { @@ -310,4 +314,83 @@ public function testResolveLocalMessageUserId(): void { $this->assertEquals('owner', $result); } + + public function testDelegateSendsNotification(): void { + $notification = $this->mockNotification(); + $now = new \DateTime(); + $this->timeFactory = $this->createMock(ITimeFactory::class); + $this->timeFactory->method('getDateTime')->willReturn($now); + + $this->delegationMapper->method('find') + ->willThrowException(new DoesNotExistException('Not found')); + $this->delegationMapper->method('insert') + ->willReturnCallback(function (Delegation $d) { + $d->setId(10); + return $d; + }); + + $notification->expects($this->once())->method('setApp')->with('mail')->willReturnSelf(); + $notification->expects($this->once())->method('setUser')->with('delegatee')->willReturnSelf(); + $notification->expects($this->once())->method('setObject')->with('delegation', '1')->willReturnSelf(); + $notification->expects($this->once()) + ->method('setSubject') + ->with('account_delegation', [ + 'id' => 1, + 'account_email' => 'owner@example.com', + ]) + ->willReturnSelf(); + $notification->expects($this->once())->method('setDateTime')->willReturnSelf(); + $notification->expects($this->once()) + ->method('setMessage') + ->with('account_delegation_changed', [ + 'id' => 1, + 'delegated' => true, + 'current_user_id' => 'owner', + 'current_user_display_name' => 'Owner User', + 'account_email' => 'owner@example.com', + ]) + ->willReturnSelf(); + $this->notificationManager->expects($this->once()) + ->method('notify') + ->with($notification); + + $this->service->delegate($this->account, 'delegatee', 'owner'); + } + + public function testUnDelegateSendsRevokedNotification(): void { + $notification = $this->mockNotification(); + + $delegation = new Delegation(); + $delegation->setId(10); + $delegation->setAccountId(1); + $delegation->setUserId('delegatee'); + + $this->delegationMapper->method('find')->willReturn($delegation); + + $notification->expects($this->once())->method('setApp')->with('mail')->willReturnSelf(); + $notification->expects($this->once())->method('setUser')->with('delegatee')->willReturnSelf(); + $notification->expects($this->once())->method('setObject')->with('delegation', '1')->willReturnSelf(); + $notification->expects($this->once()) + ->method('setSubject') + ->with('account_delegation', [ + 'id' => 1, + 'account_email' => 'owner@example.com', + ]) + ->willReturnSelf(); + $notification->expects($this->once()) + ->method('setMessage') + ->with('account_delegation_changed', [ + 'id' => 1, + 'delegated' => false, + 'current_user_id' => 'owner', + 'current_user_display_name' => 'Owner User', + 'account_email' => 'owner@example.com', + ]) + ->willReturnSelf(); + $this->notificationManager->expects($this->once()) + ->method('notify') + ->with($notification); + + $this->service->unDelegate($this->account, 'delegatee', 'owner'); + } }