From 4de43d36ddedfdc6622d03435130aa0c178d6525 Mon Sep 17 00:00:00 2001 From: Shadowghost Date: Fri, 14 Aug 2026 13:47:52 +0200 Subject: Require session ownership for additional users, capabilities and viewing reports --- .../Session/SessionManager.cs | 60 +++++++++++++++++++--- 1 file changed, 53 insertions(+), 7 deletions(-) (limited to 'Emby.Server.Implementations/Session') diff --git a/Emby.Server.Implementations/Session/SessionManager.cs b/Emby.Server.Implementations/Session/SessionManager.cs index 8864ef6df1..16f39f1759 100644 --- a/Emby.Server.Implementations/Session/SessionManager.cs +++ b/Emby.Server.Implementations/Session/SessionManager.cs @@ -1569,6 +1569,25 @@ namespace Emby.Server.Implementations.Session } } + private void AssertCanAttachUser(SessionInfo controllingSession, Guid userId) + { + var controllingUserId = controllingSession.UserId; + + // Playback reported by a session is also written to the user data of its additional users, + // so attaching anyone but the calling user requires administrative privileges. + if (controllingUserId.IsEmpty() || controllingUserId.Equals(userId)) + { + return; + } + + var controllingUser = _userManager.GetUserById(controllingUserId); + if (controllingUser is null + || !controllingUser.HasPermission(PermissionKind.IsAdministrator)) + { + throw new SecurityException("The current user does not have permission to attach another user to a session."); + } + } + /// /// Sends the restart required message. /// @@ -1584,16 +1603,24 @@ namespace Emby.Server.Implementations.Session /// /// Adds the additional user. /// + /// The controlling session identifier. /// The session identifier. /// The user identifier. - /// Cannot modify additional users without authenticating first. + /// The controlling user is not allowed to attach the user to the session. /// The requested user is already the primary user of the session. - public void AddAdditionalUser(string sessionId, Guid userId) + public void AddAdditionalUser(string controllingSessionId, string sessionId, Guid userId) { CheckDisposed(); var session = GetSession(sessionId); + if (!string.IsNullOrEmpty(controllingSessionId)) + { + var controllingSession = GetSession(controllingSessionId); + AssertCanControl(session, controllingSession); + AssertCanAttachUser(controllingSession, userId); + } + if (session.UserId.Equals(userId)) { throw new ArgumentException("The requested user is already the primary user of the session."); @@ -1601,7 +1628,8 @@ namespace Emby.Server.Implementations.Session if (session.AdditionalUsers.All(i => !i.UserId.Equals(userId))) { - var user = _userManager.GetUserById(userId); + var user = _userManager.GetUserById(userId) + ?? throw new ArgumentException("The requested user does not exist."); var newUser = new SessionUserInfo { UserId = userId, @@ -1615,16 +1643,22 @@ namespace Emby.Server.Implementations.Session /// /// Removes the additional user. /// + /// The controlling session identifier. /// The session identifier. /// The user identifier. - /// Cannot modify additional users without authenticating first. + /// The controlling user is not allowed to control the session. /// The requested user is already the primary user of the session. - public void RemoveAdditionalUser(string sessionId, Guid userId) + public void RemoveAdditionalUser(string controllingSessionId, string sessionId, Guid userId) { CheckDisposed(); var session = GetSession(sessionId); + if (!string.IsNullOrEmpty(controllingSessionId)) + { + AssertCanControl(session, GetSession(controllingSessionId)); + } + if (session.UserId.Equals(userId)) { throw new ArgumentException("The requested user is already the primary user of the session."); @@ -1828,14 +1862,21 @@ namespace Emby.Server.Implementations.Session /// /// Reports the capabilities. /// + /// The controlling session identifier. /// The session identifier. /// The capabilities. - public void ReportCapabilities(string sessionId, ClientCapabilities capabilities) + /// The controlling user is not allowed to control the session. + public void ReportCapabilities(string controllingSessionId, string sessionId, ClientCapabilities capabilities) { CheckDisposed(); var session = GetSession(sessionId); + if (!string.IsNullOrEmpty(controllingSessionId)) + { + AssertCanControl(session, GetSession(controllingSessionId)); + } + ReportCapabilities(session, capabilities, true); } @@ -1930,13 +1971,18 @@ namespace Emby.Server.Implementations.Session } /// - public void ReportNowViewingItem(string sessionId, string itemId) + public void ReportNowViewingItem(string controllingSessionId, string sessionId, string itemId) { ArgumentException.ThrowIfNullOrEmpty(itemId); var item = _libraryManager.GetItemById(new Guid(itemId)); var session = GetSession(sessionId); + if (!string.IsNullOrEmpty(controllingSessionId)) + { + AssertCanControl(session, GetSession(controllingSessionId)); + } + session.NowViewingItem = GetItemInfo(item, null); } -- cgit v1.2.3