From 6b093d8dbc55ddc947e7240bf0c0ca0d55f03be8 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:45:20 +0300 Subject: [PATCH] fix(recording): name the owner as the actor when storing recordings MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Recordings and transcripts are written by a background job, outside of any session, so the folder and the file end up attributed to nobody and the activity stream renders them as "remote account" created … . Set the actor through the activity manager for the operations that create nodes, rather than swapping the session user around them: the session swap does not survive into the chunked upload, which happens in a separate request against a public share, and overwriting the session for unrelated code running in the same process is not something this service should do. This depends on nextcloud/activity#2889, which makes the activity app consult IManager::getCurrentUserId(). Without it the call here is a no-op and the behaviour is unchanged. The file created by the chunked upload itself is not covered: it is uploaded by the recording backend through the public share created in requestUpload(), in a request Talk does not take part in. The recording folder created for that upload is attributed correctly, as is everything on the direct-upload and transcript paths. Signed-off-by: Baki Burak Öğün <63836730+bakiburakogun@users.noreply.github.com> --- lib/Service/RecordingService.php | 47 +++++++++++++++++++++++++++++--- 1 file changed, 43 insertions(+), 4 deletions(-) diff --git a/lib/Service/RecordingService.php b/lib/Service/RecordingService.php index 0f70e3848aa..0a408bc35e7 100644 --- a/lib/Service/RecordingService.php +++ b/lib/Service/RecordingService.php @@ -20,6 +20,7 @@ use OCA\Talk\Recording\BackendNotifier; use OCA\Talk\Room; use OCA\Talk\Settings\UserPreference; +use OCP\Activity\IManager as IActivityManager; use OCP\AppFramework\Services\IAppConfig; use OCP\AppFramework\Utility\ITimeFactory; use OCP\Constants; @@ -94,6 +95,7 @@ public function __construct( private readonly IUserManager $userManager, private readonly IEventDispatcher $eventDispatcher, private readonly ISecureRandom $secureRandom, + private readonly IActivityManager $activityManager, ) { } @@ -157,7 +159,10 @@ public function store(Room $room, string $owner, array $file): void { try { $recordingFolder = $this->getRecordingFolder($owner, $room->getToken()); - $fileNode = $recordingFolder->newFile($fileName, $resource); + $fileNode = $this->runAsActor( + $owner, + static fn (): File => $recordingFolder->newFile($fileName, $resource), + ); } catch (NoUserException) { throw new InvalidArgumentException('owner_invalid'); } catch (NotPermittedException) { @@ -431,9 +436,12 @@ public function storeTranscript(string $owner, string $roomToken, int $recording } try { - $fileNode = $recordingFolder->newFile( - $transcriptFileName, - $output . "\n\n$warning\n", + $fileNode = $this->runAsActor( + $owner, + static fn (): File => $recordingFolder->newFile( + $transcriptFileName, + $output . "\n\n$warning\n", + ), ); $this->systemTagMapper->assignGeneratedByAITag((string)$fileNode->getId(), 'files'); $this->notifyStoredTranscript($room, $participant, $fileNode, $aiTask); @@ -623,6 +631,37 @@ private function validateMimeTypeAndExtension(string $fileName, string $mimeType * @throws NoUserException */ private function getRecordingFolder(string $owner, string $token): Folder { + return $this->runAsActor( + $owner, + fn (): Folder => $this->getRecordingFolderInternal($owner, $token), + ); + } + + /** + * Run the callback with the activity actor set to the given user. + * + * Recordings are stored by a background job, outside of any session, so + * without this the folder and the file are attributed to nobody and the + * activity stream renders them as coming from a "remote account". + * + * @template T + * @param callable():T $callback + * @return T + */ + private function runAsActor(string $userId, callable $callback) { + $this->activityManager->setCurrentUserId($userId); + try { + return $callback(); + } finally { + $this->activityManager->setCurrentUserId(null); + } + } + + /** + * @throws NotPermittedException + * @throws NoUserException + */ + private function getRecordingFolderInternal(string $owner, string $token): Folder { $userFolder = $this->rootFolder->getUserFolder($owner); $recordingRootFolderName = $this->config->getRecordingFolder($owner); try {