diff --git a/lib/Capabilities.php b/lib/Capabilities.php index 58cadcba1..e8d0a07c6 100644 --- a/lib/Capabilities.php +++ b/lib/Capabilities.php @@ -60,6 +60,7 @@ public function getCapabilities(): array { 'admin-notifications' => [ 'ocs', 'cli', + 'self', ], ], ]; diff --git a/lib/Controller/APIController.php b/lib/Controller/APIController.php index dafb21ff4..138846ee2 100644 --- a/lib/Controller/APIController.php +++ b/lib/Controller/APIController.php @@ -18,6 +18,7 @@ use OCP\AppFramework\Http\DataResponse; use OCP\AppFramework\OCSController; use OCP\AppFramework\Utility\ITimeFactory; +use OCP\IGroupManager; use OCP\IL10N; use OCP\IRequest; use OCP\IUser; @@ -47,6 +48,7 @@ public function __construct( protected IValidator $richValidator, protected IL10N $l, protected LoggerInterface $logger, + protected IGroupManager $groupManager, ) { parent::__construct($appName, $request); } @@ -96,6 +98,7 @@ public function generateNotification(string $userId, string $shortMessage, strin * 200: Notification generated successfully, returned id is the notification ID for future delete requests * 400: Provided data was invalid, check error field of the response of log file for details */ + #[NoAdminRequired] #[OpenAPI(scope: OpenAPI::SCOPE_ADMINISTRATION)] #[ApiRoute(verb: 'POST', url: '/api/{apiVersion3}/admin_notifications/{userId}', requirements: ['apiVersion3' => '(v3)'])] public function generateNotificationV3( @@ -105,6 +108,12 @@ public function generateNotificationV3( array $subjectParameters = [], array $messageParameters = [], ): DataResponse { + $actor = $this->userSession->getUser(); + if (!$this->groupManager->isAdmin($actor?->getUID() ?? '') + && $userId !== $actor?->getUID()) { + return new DataResponse(['error' => 'user'], Http::STATUS_BAD_REQUEST); + } + $user = $this->userManager->get($userId); if (!$user instanceof IUser) { diff --git a/openapi-administration.json b/openapi-administration.json index 6c5dc7d9f..218777ec1 100644 --- a/openapi-administration.json +++ b/openapi-administration.json @@ -454,7 +454,6 @@ "post": { "operationId": "api-generate-notification-v3", "summary": "Generate a notification with rich object parameters for a user", - "description": "This endpoint requires admin access", "tags": [ "api" ], @@ -633,34 +632,6 @@ } } } - }, - "403": { - "description": "Logged in account must be an admin", - "content": { - "application/json": { - "schema": { - "type": "object", - "required": [ - "ocs" - ], - "properties": { - "ocs": { - "type": "object", - "required": [ - "meta", - "data" - ], - "properties": { - "meta": { - "$ref": "#/components/schemas/OCSMeta" - }, - "data": {} - } - } - } - } - } - } } } } diff --git a/openapi-full.json b/openapi-full.json index 5b988068f..7f8022afd 100644 --- a/openapi-full.json +++ b/openapi-full.json @@ -565,7 +565,6 @@ "post": { "operationId": "api-generate-notification-v3", "summary": "Generate a notification with rich object parameters for a user", - "description": "This endpoint requires admin access", "tags": [ "api" ], @@ -744,34 +743,6 @@ } } } - }, - "403": { - "description": "Logged in account must be an admin", - "content": { - "application/json": { - "schema": { - "type": "object", - "required": [ - "ocs" - ], - "properties": { - "ocs": { - "type": "object", - "required": [ - "meta", - "data" - ], - "properties": { - "meta": { - "$ref": "#/components/schemas/OCSMeta" - }, - "data": {} - } - } - } - } - } - } } } } diff --git a/tests/Integration/features/admin-notification-v3.feature b/tests/Integration/features/admin-notification-v3.feature new file mode 100644 index 000000000..dc82f75e7 --- /dev/null +++ b/tests/Integration/features/admin-notification-v3.feature @@ -0,0 +1,38 @@ +# SPDX-FileCopyrightText: 2026 Nextcloud GmbH and Nextcloud contributors +# SPDX-License-Identifier: CC0-1.0 +Feature: admin-notification-v3 + Background: + Given user "test1" exists + Given user "test2" exists + Given as user "test1" + + Scenario: Non-admin user creates a notification for themselves + When sending "POST" to "/apps/notifications/api/v3/admin_notifications/test1?format=json" with + | subject | self notification | + | message | this is the message | + Then status code is 200 + And user "test1" has 1 notifications on v2 + And last notification on v2 matches + | app | admin_notifications | + | subject | self notification | + | message | this is the message | + | object_type | admin_notifications | + + Scenario: Non-admin user can not create a notification for another user + When sending "POST" to "/apps/notifications/api/v3/admin_notifications/test2?format=json" with + | subject | foreign notification | + Then status code is 400 + Given as user "test2" + Then user "test2" has 0 notifications on v2 + + Scenario: Admin creates a notification for another user + Given as user "admin" + When sending "POST" to "/apps/notifications/api/v3/admin_notifications/test1?format=json" with + | subject | admin notification | + Then status code is 200 + Given as user "test1" + Then user "test1" has 1 notifications on v2 + And last notification on v2 matches + | app | admin_notifications | + | subject | admin notification | + | object_type | admin_notifications | diff --git a/tests/Unit/CapabilitiesTest.php b/tests/Unit/CapabilitiesTest.php index 11586c39b..e7eb3e459 100644 --- a/tests/Unit/CapabilitiesTest.php +++ b/tests/Unit/CapabilitiesTest.php @@ -73,6 +73,7 @@ public function testGetCapabilities(bool $webpush, bool $webpushBrowser, array $ 'admin-notifications' => [ 'ocs', 'cli', + 'self', ], ], ], $capabilities->getCapabilities()); diff --git a/tests/Unit/Controller/APIControllerTest.php b/tests/Unit/Controller/APIControllerTest.php index b24449501..37169dd61 100644 --- a/tests/Unit/Controller/APIControllerTest.php +++ b/tests/Unit/Controller/APIControllerTest.php @@ -13,6 +13,7 @@ use OCP\AppFramework\Http; use OCP\AppFramework\Http\DataResponse; use OCP\AppFramework\Utility\ITimeFactory; +use OCP\IGroupManager; use OCP\IL10N; use OCP\IRequest; use OCP\IUser; @@ -38,6 +39,7 @@ class APIControllerTest extends TestCase { protected IValidator&MockObject $richValidator; protected IL10N&MockObject $l; protected LoggerInterface&MockObject $logger; + protected IGroupManager&MockObject $groupManager; protected APIController $controller; @@ -54,6 +56,7 @@ protected function setUp(): void { $this->richValidator = $this->createMock(IValidator::class); $this->l = $this->createMock(IL10N::class); $this->logger = $this->createMock(LoggerInterface::class); + $this->groupManager = $this->createMock(IGroupManager::class); $this->controller = new APIController( 'notifications', @@ -66,6 +69,7 @@ protected function setUp(): void { $this->richValidator, $this->l, $this->logger, + $this->groupManager, ); } @@ -95,6 +99,9 @@ public function testGenerateNotification(string $userId, string $short, string $ ->method('get') ->with($userId) ->willReturn($u); + $this->userSession->expects($this->any()) + ->method('getUser') + ->willReturn($this->mockUser($userId)); $dateTime = new \DateTime(); $dateTime->setTimestamp($time); @@ -156,4 +163,10 @@ public function testGenerateNotification(string $userId, string $short, string $ $this->assertInstanceOf(DataResponse::class, $response); $this->assertSame($statusCode, $response->getStatus()); } + + private function mockUser(string $uid): IUser&MockObject { + $user = $this->createMock(IUser::class); + $user->method('getUID')->willReturn($uid); + return $user; + } }