From 14ab0800ed927ec7097d08f9cd87c3cf0dfbbbd4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Baki=20Burak=20=C3=96=C4=9F=C3=BCn?= <63836730+bakiburakogun@users.noreply.github.com> Date: Sat, 29 Aug 2026 05:41:36 +0300 Subject: [PATCH] feat: let apps define the actor of an activity via the activity manager MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit CurrentUser::getUID() read the user straight from the session, so an action performed outside of a user's session could not be attributed to anybody. File activities created from a background job end up rendered as "remote account" did something, because FilesHooks asks CurrentUser for the actor. IManager::setCurrentUserId() already exists for exactly this, and OC\Activity\Manager::getCurrentUserId() honours it, but the activity app never consulted it. Ask the activity manager first and fall back to the session when it has nothing to offer. With no override the result is unchanged: the manager itself returns the session user when one is logged in, and when there is neither a session nor a valid feed token it throws, which is caught here so the previous behaviour of returning null is preserved. Signed-off-by: Baki Burak Öğün <63836730+bakiburakogun@users.noreply.github.com> --- lib/CurrentUser.php | 18 +++++++++++++++++- tests/CurrentUserTest.php | 27 +++++++++++++++++++++++++++ 2 files changed, 44 insertions(+), 1 deletion(-) diff --git a/lib/CurrentUser.php b/lib/CurrentUser.php index e9fe71f7b..24760545c 100644 --- a/lib/CurrentUser.php +++ b/lib/CurrentUser.php @@ -7,6 +7,7 @@ namespace OCA\Activity; +use OCP\Activity\IManager as IActivityManager; use OCP\IRequest; use OCP\IUser; use OCP\IUserSession; @@ -22,6 +23,7 @@ public function __construct( protected readonly IRequest $request, protected readonly IManager $shareManager, protected readonly IFactory $l10nFactory, + protected readonly IActivityManager $activityManager, ) { } @@ -53,9 +55,23 @@ public function getUserIdentifier(): string { } /** - * Get the current user id from the session + * Get the current user id + * + * Apps can override who an action is attributed to with + * IManager::setCurrentUserId(). That is the only way to name an actor when the + * action happens outside of that user's session, e.g. from a background job. + * Without an override this is the user of the session, as before. */ public function getUID(): ?string { + try { + $userId = $this->activityManager->getCurrentUserId(); + if ($userId !== '') { + return $userId; + } + } catch (\UnexpectedValueException) { + // Neither a session nor a valid feed token, fall back to the session below + } + $user = $this->userSession->getUser(); if ($user instanceof IUser) { return $user->getUID(); diff --git a/tests/CurrentUserTest.php b/tests/CurrentUserTest.php index 2c7151ea7..c64e98481 100644 --- a/tests/CurrentUserTest.php +++ b/tests/CurrentUserTest.php @@ -24,6 +24,7 @@ use Exception; use OCA\Activity\CurrentUser; +use OCP\Activity\IManager as IActivityManager; use OCP\IRequest; use OCP\IUser; use OCP\IUserSession; @@ -49,6 +50,7 @@ class CurrentUserTest extends TestCase { protected IUserSession&MockObject $userSession; protected IManager&MockObject $shareManager; protected IFactory&MockObject $l10nFactory; + protected IActivityManager&MockObject $activityManager; protected function setUp(): void { parent::setUp(); @@ -57,6 +59,7 @@ protected function setUp(): void { $this->userSession = $this->createMock(IUserSession::class); $this->shareManager = $this->createMock(IManager::class); $this->l10nFactory = $this->createMock(IFactory::class); + $this->activityManager = $this->createMock(IActivityManager::class); $this->request->method('getScriptName')->willReturn('/public.php'); } @@ -68,6 +71,7 @@ protected function getInstance(array $methods = []): CurrentUser|MockObject { $this->request, $this->shareManager, $this->l10nFactory, + $this->activityManager, ); } @@ -77,6 +81,7 @@ protected function getInstance(array $methods = []): CurrentUser|MockObject { $this->request, $this->shareManager, $this->l10nFactory, + $this->activityManager, ]) ->onlyMethods($methods) ->getMock(); @@ -141,6 +146,28 @@ public function testGetUID(?string $uid, ?string $expected): void { $this->assertSame($expected, $instance->getUID()); } + public function testGetUIDUsesTheActivityManagerOverride(): void { + $this->activityManager->method('getCurrentUserId') + ->willReturn('attributed-user'); + $this->userSession->expects($this->never()) + ->method('getUser'); + + $instance = $this->getInstance(); + $this->assertSame('attributed-user', $instance->getUID()); + } + + public function testGetUIDFallsBackToTheSessionWithoutAToken(): void { + $this->activityManager->method('getCurrentUserId') + ->willThrowException(new \UnexpectedValueException('The token is invalid')); + + $instance = $this->getInstance(); + $this->userSession->expects($this->once()) + ->method('getUser') + ->willReturn($this->getUserMock('session-user')); + + $this->assertSame('session-user', $instance->getUID()); + } + protected function getShareMock(array $share): IShare|Exception|null { if (empty($share)) { return null;