Skip to content

fix(recording): store recordings and transcripts as the owning user - #19078

Open
bakiburakogun wants to merge 1 commit into
nextcloud:mainfrom
bakiburakogun:fix/recording-file-owner-attribution
Open

fix(recording): store recordings and transcripts as the owning user#19078
bakiburakogun wants to merge 1 commit into
nextcloud:mainfrom
bakiburakogun:fix/recording-file-owner-attribution

Conversation

@bakiburakogun

Copy link
Copy Markdown

Fixes #19077

Summary

Call recordings are stored through POST /ocs/v2.php/apps/spreed/api/v1/recording/{token}/store, which is a #[PublicPage] endpoint authenticated with the recording shared secret only. There is no active user during that request, so when RecordingService::store() writes the file the activity subsystem cannot resolve an actor and falls back to an empty one. The Files activity provider renders that as "remote account", so users see

"remote account" created Recording 2026-08-23 23-23-09.mp4

instead of their own name, even though the file is in their own recording folder.

This sets the owner as active user while the file is created and restores the previous user afterwards, so the file events and the resulting activity are attributed to them. The same helper is used for the transcript and summary files written in storeTranscript(), which have the same problem.

Notes

  • The owner parameter that the recording backend sends is already validated in store() (getParticipant() throws owner_participant for an unknown owner), the helper additionally falls back to the previous behaviour if the user cannot be loaded.
  • The active user is restored in a finally block, so a failing write cannot leak the session user into the rest of the request.
  • Sharing the recording to the chat is unaffected, that path already posts with the participant actor.

Testing

Reproduced on Nextcloud 34.0.3 with Talk 24.0.4 and nextcloud-talk-recording 0.2.1 (four node signaling cluster). The patch is being rolled out on that instance now and I will confirm here once a recording has been stored with it.

@bakiburakogun

Copy link
Copy Markdown
Author

Deployed on the instance from the report (Nextcloud 34.0.3, Talk 24.0.4, nextcloud-talk-recording 0.2.1) and recorded a call with it.

The activity entry for the new recording now reads You created Recording 2026-08-24 00-02-34.mp4, while the recordings stored before the patch still show "remote account" created .... The file, the "recording available" notification and sharing it to the chat behave as before.

@bakiburakogun

Copy link
Copy Markdown
Author

Pushed a follow-up commit.

After running the first version on the instance from the report, the recordings themselves are attributed correctly, but the activity stream still shows an entry like

"remote account" created Recording 2026-08-24 13-55-16.mp4 and <conversation token>

for the first recording of a conversation. The reason is that getRecordingFolder() creates the attachment folder and the per-conversation folder with newFolder() outside of the user context that newFileAsUser() sets up, so the folder creation is still attributed to nobody.

The new commit moves the user switch into a small runAsUser() helper and uses it for the folder lookup as well, so the folder and the file end up with the same actor. No behaviour changes beyond the attribution.

@bakiburakogun

Copy link
Copy Markdown
Author

Verified on the instance from the report (Nextcloud 34.0.3, Talk 24.0.4, nextcloud-talk-recording 0.2.1, four signaling servers, seven application servers).

Recorded a call in a conversation that had never been recorded before, so the per-conversation folder was created as part of storing the recording. The activity stream now attributes both the folder and the .mp4 to the user who started the recording. Before this commit the same case produced a single entry reading "remote account" created Recording ....mp4 and <conversation token>, with the file and the folder both unattributed.

Recording, the "recording available" notification and sharing it into the chat behave as before.

Comment thread lib/Service/RecordingService.php Outdated
}

$previousUser = $this->userSession->getUser();
$this->userSession->setUser($user);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure it's the best idea to overwrite the session user like this.
Also it's not going to work on the new chunked-uploading of recordings we implemented recently.

I think instead we need work in the activity app to allow defining different actors:
https://github.com/nextcloud/activity/blob/f95186c14cb89cb8e9f7f367e9ac394b2c398c50/lib/CurrentUser.php#L35

The activity manager has code for this already: https://github.com/nextcloud/server/blob/1ab09ec753f106661beadee1794b390ebec455dd/lib/private/Activity/Manager.php#L346-L354
but it's not used in the activity app itself. I think CurrentUser should first check IManager::getCurrentUserId() that would allow Talk to overwrite the user for the action

@nickvergessen nickvergessen left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As per comment

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>
@bakiburakogun
bakiburakogun force-pushed the fix/recording-file-owner-attribution branch from e033e8e to 6b093d8 Compare August 29, 2026 02:45
@bakiburakogun

Copy link
Copy Markdown
Author

Reworked along the lines you suggested — thank you, the session swap was the wrong tool and the chunked upload made that obvious.

What changed

The service no longer touches IUserSession. It sets the actor through the activity manager instead:

private function runAsActor(string $userId, callable $callback) {
	$this->activityManager->setCurrentUserId($userId);
	try {
		return $callback();
	} finally {
		$this->activityManager->setCurrentUserId(null);
	}
}

and wraps the operations that create nodes with it: the recording folder (which covers all four call sites — store(), requestUpload(), finishUpload() and storeTranscript()), the recording file on the direct-upload path, and the transcript file.

The branch is rebased onto current main, so it now sits on top of the chunked upload work.

Dependency

This needs nextcloud/activity#2889, which makes CurrentUser consult IManager::getCurrentUserId() as you described. OC\Activity\Manager already honours the override; the activity app was the piece that never asked. Until that lands the call here is a no-op, so this PR is safe to sit behind it but pointless on its own.

The chunked upload

You were right that the previous approach could not work there, and I want to be explicit that this one only partly does. The file created by a chunked upload is written by the recording backend through the public share created in requestUpload(), in a request Talk takes no part in, so nothing set in this process reaches it. What this PR does cover for that path is the recording folder, which is created here and was previously unattributed — that is the entry that produced the "remote account" created … and <token> line for the first recording in a conversation.

Making the uploaded file itself land on the owner would mean doing something about the public-share upload — either attributing it at share level or re-attributing in finishUpload(). Happy to look into that as a follow-up if you think it is worth it, but it felt like a separate change rather than something to bolt onto this one.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug feature: api 🛠️ OCS API for conversations, chats and participants feature: recordings ⏺️ Including the recording server

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Call recordings are attributed to "remote account" in the Files activity stream

2 participants