From fff3c789b98aad08f8ea66da275ff82c71dd8f2b Mon Sep 17 00:00:00 2001 From: David Date: Fri, 12 Jun 2020 18:54:25 +0200 Subject: Move SessionService to Jellyfin.Api --- Jellyfin.Api/Controllers/SessionController.cs | 478 ++++++++++++++++++++++++++ 1 file changed, 478 insertions(+) create mode 100644 Jellyfin.Api/Controllers/SessionController.cs (limited to 'Jellyfin.Api/Controllers/SessionController.cs') diff --git a/Jellyfin.Api/Controllers/SessionController.cs b/Jellyfin.Api/Controllers/SessionController.cs new file mode 100644 index 000000000..5b60275eb --- /dev/null +++ b/Jellyfin.Api/Controllers/SessionController.cs @@ -0,0 +1,478 @@ +#pragma warning disable CA1801 + +using System; +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; +using System.Linq; +using System.Threading; +using Jellyfin.Api.Helpers; +using MediaBrowser.Controller.Devices; +using MediaBrowser.Controller.Library; +using MediaBrowser.Controller.Net; +using MediaBrowser.Controller.Session; +using MediaBrowser.Model.Dto; +using MediaBrowser.Model.Session; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; + +namespace Jellyfin.Api.Controllers +{ + /// + /// The session controller. + /// + public class SessionController : BaseJellyfinApiController + { + private readonly ISessionManager _sessionManager; + private readonly IUserManager _userManager; + private readonly IAuthorizationContext _authContext; + private readonly IDeviceManager _deviceManager; + private readonly ISessionContext _sessionContext; + + /// + /// Initializes a new instance of the class. + /// + /// Instance of interface. + /// Instance of interface. + /// Instance of interface. + /// Instance of interface. + /// Instance of interface. + public SessionController( + ISessionManager sessionManager, + IUserManager userManager, + IAuthorizationContext authContext, + IDeviceManager deviceManager, + ISessionContext sessionContext) + { + _sessionManager = sessionManager; + _userManager = userManager; + _authContext = authContext; + _deviceManager = deviceManager; + _sessionContext = sessionContext; + } + + /// + /// Gets a list of sessions. + /// + /// Filter by sessions that a given user is allowed to remote control. + /// Filter by device Id. + /// Optional. Filter by sessions that were active in the last n seconds. + /// List of sessions returned. + /// An with the available sessions. + [HttpGet("/Sessions")] + [Authorize] + [ProducesResponseType(StatusCodes.Status200OK)] + public ActionResult> GetSessions( + [FromQuery] Guid controllableByUserId, + [FromQuery] string deviceId, + [FromQuery] int? activeWithinSeconds) + { + var result = _sessionManager.Sessions; + + if (!string.IsNullOrEmpty(deviceId)) + { + result = result.Where(i => string.Equals(i.DeviceId, deviceId, StringComparison.OrdinalIgnoreCase)); + } + + if (!controllableByUserId.Equals(Guid.Empty)) + { + result = result.Where(i => i.SupportsRemoteControl); + + var user = _userManager.GetUserById(controllableByUserId); + + if (!user.Policy.EnableRemoteControlOfOtherUsers) + { + result = result.Where(i => i.UserId.Equals(Guid.Empty) || i.ContainsUser(controllableByUserId)); + } + + if (!user.Policy.EnableSharedDeviceControl) + { + result = result.Where(i => !i.UserId.Equals(Guid.Empty)); + } + + if (activeWithinSeconds.HasValue && activeWithinSeconds.Value > 0) + { + var minActiveDate = DateTime.UtcNow.AddSeconds(0 - activeWithinSeconds.Value); + result = result.Where(i => i.LastActivityDate >= minActiveDate); + } + + result = result.Where(i => + { + if (!string.IsNullOrWhiteSpace(i.DeviceId)) + { + if (!_deviceManager.CanAccessDevice(user, i.DeviceId)) + { + return false; + } + } + + return true; + }); + } + + return Ok(result); + } + + /// + /// Instructs a session to browse to an item or view. + /// + /// The session Id. + /// The type of item to browse to. + /// The Id of the item. + /// The name of the item. + /// Instruction sent to session. + /// A . + [HttpPost("/Sessions/{id}/Viewing")] + [ProducesResponseType(StatusCodes.Status204NoContent)] + public ActionResult DisplayContent( + [FromRoute] string id, + [FromQuery] string itemType, + [FromQuery] string itemId, + [FromQuery] string itemName) + { + var command = new BrowseRequest + { + ItemId = itemId, + ItemName = itemName, + ItemType = itemType + }; + + _sessionManager.SendBrowseCommand( + RequestHelpers.GetSession(_sessionContext).Id, + id, + command, + CancellationToken.None); + + return NoContent(); + } + + /// + /// Instructs a session to play an item. + /// + /// The session id. + /// The ids of the items to play, comma delimited. + /// The starting position of the first item. + /// The type of play command to issue (PlayNow, PlayNext, PlayLast). Clients who have not yet implemented play next and play last may play now. + /// The . + /// Instruction sent to session. + /// A . + [HttpPost("/Sessions/{id}/Playing")] + [ProducesResponseType(StatusCodes.Status204NoContent)] + public ActionResult Play( + [FromRoute] string id, + [FromQuery] Guid[] itemIds, + [FromQuery] long? startPositionTicks, + [FromQuery] PlayCommand playCommand, + [FromBody, Required] PlayRequest playRequest) + { + if (playRequest == null) + { + throw new ArgumentException("Request Body may not be null"); + } + + playRequest.ItemIds = itemIds; + playRequest.StartPositionTicks = startPositionTicks; + playRequest.PlayCommand = playCommand; + + _sessionManager.SendPlayCommand( + RequestHelpers.GetSession(_sessionContext).Id, + id, + playRequest, + CancellationToken.None); + + return NoContent(); + } + + /// + /// Issues a playstate command to a client. + /// + /// The session id. + /// The . + /// Playstate command sent to session. + /// A . + [HttpPost("/Sessions/{id}/Playing/{command}")] + [ProducesResponseType(StatusCodes.Status204NoContent)] + public ActionResult SendPlaystateCommand( + [FromRoute] string id, + [FromBody] PlaystateRequest playstateRequest) + { + _sessionManager.SendPlaystateCommand( + RequestHelpers.GetSession(_sessionContext).Id, + id, + playstateRequest, + CancellationToken.None); + + return NoContent(); + } + + /// + /// Issues a system command to a client. + /// + /// The session id. + /// The command to send. + /// System command sent to session. + /// A . + [HttpPost("/Sessions/{id}/System/{Command}")] + [ProducesResponseType(StatusCodes.Status204NoContent)] + public ActionResult SendSystemCommand( + [FromRoute] string id, + [FromRoute] string command) + { + var name = command; + if (Enum.TryParse(name, true, out GeneralCommandType commandType)) + { + name = commandType.ToString(); + } + + var currentSession = RequestHelpers.GetSession(_sessionContext); + var generalCommand = new GeneralCommand + { + Name = name, + ControllingUserId = currentSession.UserId + }; + + _sessionManager.SendGeneralCommand(currentSession.Id, id, generalCommand, CancellationToken.None); + + return NoContent(); + } + + /// + /// Issues a general command to a client. + /// + /// The session id. + /// The command to send. + /// General command sent to session. + /// A . + [HttpPost("/Sessions/{id}/Command/{Command}")] + [ProducesResponseType(StatusCodes.Status204NoContent)] + public ActionResult SendGeneralCommand( + [FromRoute] string id, + [FromRoute] string command) + { + var currentSession = RequestHelpers.GetSession(_sessionContext); + + var generalCommand = new GeneralCommand + { + Name = command, + ControllingUserId = currentSession.UserId + }; + + _sessionManager.SendGeneralCommand(currentSession.Id, id, generalCommand, CancellationToken.None); + + return NoContent(); + } + + /// + /// Issues a full general command to a client. + /// + /// The session id. + /// The . + /// Full general command sent to session. + /// A . + [HttpPost("/Sessions/{id}/Command")] + [ProducesResponseType(StatusCodes.Status204NoContent)] + public ActionResult SendFullGeneralCommand( + [FromRoute] string id, + [FromBody, Required] GeneralCommand command) + { + var currentSession = RequestHelpers.GetSession(_sessionContext); + + if (command == null) + { + throw new ArgumentException("Request body may not be null"); + } + + command.ControllingUserId = currentSession.UserId; + + _sessionManager.SendGeneralCommand( + currentSession.Id, + id, + command, + CancellationToken.None); + + return NoContent(); + } + + /// + /// Issues a command to a client to display a message to the user. + /// + /// The session id. + /// The message test. + /// The message header. + /// The message timeout. If omitted the user will have to confirm viewing the message. + /// Message sent. + /// A . + [HttpPost("/Sessions/{id}/Message")] + [ProducesResponseType(StatusCodes.Status204NoContent)] + public ActionResult SendMessageCommand( + [FromRoute] string id, + [FromQuery] string text, + [FromQuery] string header, + [FromQuery] long? timeoutMs) + { + var command = new MessageCommand + { + Header = string.IsNullOrEmpty(header) ? "Message from Server" : header, + TimeoutMs = timeoutMs, + Text = text + }; + + _sessionManager.SendMessageCommand(RequestHelpers.GetSession(_sessionContext).Id, id, command, CancellationToken.None); + + return NoContent(); + } + + /// + /// Adds an additional user to a session. + /// + /// The session id. + /// The user id. + /// User added to session. + /// A . + [HttpPost("/Sessions/{id}/User/{userId}")] + [ProducesResponseType(StatusCodes.Status204NoContent)] + public ActionResult AddUserToSession( + [FromRoute] string id, + [FromRoute] Guid userId) + { + _sessionManager.AddAdditionalUser(id, userId); + return NoContent(); + } + + /// + /// Removes an additional user from a session. + /// + /// The session id. + /// The user id. + /// User removed from session. + /// A . + [HttpDelete("/Sessions/{id}/User/{userId}")] + [ProducesResponseType(StatusCodes.Status204NoContent)] + public ActionResult RemoveUserFromSession( + [FromRoute] string id, + [FromRoute] Guid userId) + { + _sessionManager.RemoveAdditionalUser(id, userId); + return NoContent(); + } + + /// + /// Updates capabilities for a device. + /// + /// The session id. + /// A list of playable media types, comma delimited. Audio, Video, Book, Photo. + /// A list of supported remote control commands, comma delimited. + /// Determines whether media can be played remotely.. + /// Determines whether sync is supported. + /// Determines whether the device supports a unique identifier. + /// Capabilities posted. + /// A . + [HttpPost("/Sessions/Capabilities")] + [ProducesResponseType(StatusCodes.Status204NoContent)] + public ActionResult PostCapabilities( + [FromQuery] string id, + [FromQuery] string playableMediaTypes, + [FromQuery] string supportedCommands, + [FromQuery] bool supportsMediaControl, + [FromQuery] bool supportsSync, + [FromQuery] bool supportsPersistentIdentifier = true) + { + if (string.IsNullOrWhiteSpace(id)) + { + id = RequestHelpers.GetSession(_sessionContext).Id; + } + + _sessionManager.ReportCapabilities(id, new ClientCapabilities + { + PlayableMediaTypes = RequestHelpers.Split(playableMediaTypes, ',', true), + SupportedCommands = RequestHelpers.Split(supportedCommands, ',', true), + SupportsMediaControl = supportsMediaControl, + SupportsSync = supportsSync, + SupportsPersistentIdentifier = supportsPersistentIdentifier + }); + return NoContent(); + } + + /// + /// Updates capabilities for a device. + /// + /// The session id. + /// The . + /// Capabilities updated. + /// A . + [HttpPost("/Sessions/Capabilities/Full")] + [ProducesResponseType(StatusCodes.Status204NoContent)] + public ActionResult PostFullCapabilities( + [FromQuery] string id, + [FromBody, Required] ClientCapabilities capabilities) + { + if (string.IsNullOrWhiteSpace(id)) + { + id = RequestHelpers.GetSession(_sessionContext).Id; + } + + _sessionManager.ReportCapabilities(id, capabilities); + + return NoContent(); + } + + /// + /// Reports that a session is viewing an item. + /// + /// The session id. + /// The item id. + /// Session reported to server. + /// A . + [HttpPost("/Sessions/Viewing")] + [ProducesResponseType(StatusCodes.Status204NoContent)] + public ActionResult ReportViewing( + [FromQuery] string sessionId, + [FromQuery] string itemId) + { + string session = RequestHelpers.GetSession(_sessionContext).Id; + + _sessionManager.ReportNowViewingItem(session, itemId); + return NoContent(); + } + + /// + /// Reports that a session has ended. + /// + /// Session end reported to server. + /// A . + [HttpPost("/Sessions/Logout")] + [ProducesResponseType(StatusCodes.Status204NoContent)] + public ActionResult ReportSessionEnded() + { + // TODO: how do we get AuthorizationInfo without an IRequest? + AuthorizationInfo auth = _authContext.GetAuthorizationInfo(Request); + + _sessionManager.Logout(auth.Token); + return NoContent(); + } + + /// + /// Get all auth providers. + /// + /// Auth providers retrieved. + /// An with the auth providers. + [HttpGet("/Auth/Providers")] + [ProducesResponseType(StatusCodes.Status200OK)] + public ActionResult> GetAuthProviders() + { + return _userManager.GetAuthenticationProviders(); + } + + /// + /// Get all password reset providers. + /// + /// Password reset providers retrieved. + /// An with the password reset providers. + [HttpGet("/Auto/PasswordResetProviders")] + [ProducesResponseType(StatusCodes.Status200OK)] + public ActionResult> GetPasswordResetProviders() + { + return _userManager.GetPasswordResetProviders(); + } + } +} -- cgit v1.2.3 From a5bd7f2d6ee0fef67c34f61db9be36167c30d890 Mon Sep 17 00:00:00 2001 From: David Date: Fri, 19 Jun 2020 13:03:53 +0200 Subject: Use new authorization and session functions --- Jellyfin.Api/Controllers/SessionController.cs | 32 ++++++++++++--------------- Jellyfin.Api/Helpers/RequestHelpers.cs | 14 +++++++++--- 2 files changed, 25 insertions(+), 21 deletions(-) (limited to 'Jellyfin.Api/Controllers/SessionController.cs') diff --git a/Jellyfin.Api/Controllers/SessionController.cs b/Jellyfin.Api/Controllers/SessionController.cs index 5b60275eb..4f259536a 100644 --- a/Jellyfin.Api/Controllers/SessionController.cs +++ b/Jellyfin.Api/Controllers/SessionController.cs @@ -6,6 +6,7 @@ using System.ComponentModel.DataAnnotations; using System.Linq; using System.Threading; using Jellyfin.Api.Helpers; +using Jellyfin.Data.Enums; using MediaBrowser.Controller.Devices; using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Net; @@ -27,7 +28,6 @@ namespace Jellyfin.Api.Controllers private readonly IUserManager _userManager; private readonly IAuthorizationContext _authContext; private readonly IDeviceManager _deviceManager; - private readonly ISessionContext _sessionContext; /// /// Initializes a new instance of the class. @@ -36,19 +36,16 @@ namespace Jellyfin.Api.Controllers /// Instance of interface. /// Instance of interface. /// Instance of interface. - /// Instance of interface. public SessionController( ISessionManager sessionManager, IUserManager userManager, IAuthorizationContext authContext, - IDeviceManager deviceManager, - ISessionContext sessionContext) + IDeviceManager deviceManager) { _sessionManager = sessionManager; _userManager = userManager; _authContext = authContext; _deviceManager = deviceManager; - _sessionContext = sessionContext; } /// @@ -80,12 +77,12 @@ namespace Jellyfin.Api.Controllers var user = _userManager.GetUserById(controllableByUserId); - if (!user.Policy.EnableRemoteControlOfOtherUsers) + if (!user.HasPermission(PermissionKind.EnableRemoteControlOfOtherUsers)) { result = result.Where(i => i.UserId.Equals(Guid.Empty) || i.ContainsUser(controllableByUserId)); } - if (!user.Policy.EnableSharedDeviceControl) + if (!user.HasPermission(PermissionKind.EnableSharedDeviceControl)) { result = result.Where(i => !i.UserId.Equals(Guid.Empty)); } @@ -138,7 +135,7 @@ namespace Jellyfin.Api.Controllers }; _sessionManager.SendBrowseCommand( - RequestHelpers.GetSession(_sessionContext).Id, + RequestHelpers.GetSession(_sessionManager, _authContext, Request).Id, id, command, CancellationToken.None); @@ -175,7 +172,7 @@ namespace Jellyfin.Api.Controllers playRequest.PlayCommand = playCommand; _sessionManager.SendPlayCommand( - RequestHelpers.GetSession(_sessionContext).Id, + RequestHelpers.GetSession(_sessionManager, _authContext, Request).Id, id, playRequest, CancellationToken.None); @@ -197,7 +194,7 @@ namespace Jellyfin.Api.Controllers [FromBody] PlaystateRequest playstateRequest) { _sessionManager.SendPlaystateCommand( - RequestHelpers.GetSession(_sessionContext).Id, + RequestHelpers.GetSession(_sessionManager, _authContext, Request).Id, id, playstateRequest, CancellationToken.None); @@ -224,7 +221,7 @@ namespace Jellyfin.Api.Controllers name = commandType.ToString(); } - var currentSession = RequestHelpers.GetSession(_sessionContext); + var currentSession = RequestHelpers.GetSession(_sessionManager, _authContext, Request); var generalCommand = new GeneralCommand { Name = name, @@ -249,7 +246,7 @@ namespace Jellyfin.Api.Controllers [FromRoute] string id, [FromRoute] string command) { - var currentSession = RequestHelpers.GetSession(_sessionContext); + var currentSession = RequestHelpers.GetSession(_sessionManager, _authContext, Request); var generalCommand = new GeneralCommand { @@ -275,7 +272,7 @@ namespace Jellyfin.Api.Controllers [FromRoute] string id, [FromBody, Required] GeneralCommand command) { - var currentSession = RequestHelpers.GetSession(_sessionContext); + var currentSession = RequestHelpers.GetSession(_sessionManager, _authContext, Request); if (command == null) { @@ -317,7 +314,7 @@ namespace Jellyfin.Api.Controllers Text = text }; - _sessionManager.SendMessageCommand(RequestHelpers.GetSession(_sessionContext).Id, id, command, CancellationToken.None); + _sessionManager.SendMessageCommand(RequestHelpers.GetSession(_sessionManager, _authContext, Request).Id, id, command, CancellationToken.None); return NoContent(); } @@ -379,7 +376,7 @@ namespace Jellyfin.Api.Controllers { if (string.IsNullOrWhiteSpace(id)) { - id = RequestHelpers.GetSession(_sessionContext).Id; + id = RequestHelpers.GetSession(_sessionManager, _authContext, Request).Id; } _sessionManager.ReportCapabilities(id, new ClientCapabilities @@ -408,7 +405,7 @@ namespace Jellyfin.Api.Controllers { if (string.IsNullOrWhiteSpace(id)) { - id = RequestHelpers.GetSession(_sessionContext).Id; + id = RequestHelpers.GetSession(_sessionManager, _authContext, Request).Id; } _sessionManager.ReportCapabilities(id, capabilities); @@ -429,7 +426,7 @@ namespace Jellyfin.Api.Controllers [FromQuery] string sessionId, [FromQuery] string itemId) { - string session = RequestHelpers.GetSession(_sessionContext).Id; + string session = RequestHelpers.GetSession(_sessionManager, _authContext, Request).Id; _sessionManager.ReportNowViewingItem(session, itemId); return NoContent(); @@ -444,7 +441,6 @@ namespace Jellyfin.Api.Controllers [ProducesResponseType(StatusCodes.Status204NoContent)] public ActionResult ReportSessionEnded() { - // TODO: how do we get AuthorizationInfo without an IRequest? AuthorizationInfo auth = _authContext.GetAuthorizationInfo(Request); _sessionManager.Logout(auth.Token); diff --git a/Jellyfin.Api/Helpers/RequestHelpers.cs b/Jellyfin.Api/Helpers/RequestHelpers.cs index ae8ab37e8..2aa700de3 100644 --- a/Jellyfin.Api/Helpers/RequestHelpers.cs +++ b/Jellyfin.Api/Helpers/RequestHelpers.cs @@ -1,6 +1,7 @@ using System; using MediaBrowser.Controller.Net; using MediaBrowser.Controller.Session; +using Microsoft.AspNetCore.Http; namespace Jellyfin.Api.Helpers { @@ -28,10 +29,17 @@ namespace Jellyfin.Api.Helpers : value.Split(separator); } - internal static SessionInfo GetSession(ISessionContext sessionContext) + internal static SessionInfo GetSession(ISessionManager sessionManager, IAuthorizationContext authContext, HttpRequest request) { - // TODO: how do we get a SessionInfo without IRequest? - SessionInfo session = sessionContext.GetSession("Request"); + var authorization = authContext.GetAuthorizationInfo(request); + var user = authorization.User; + var session = sessionManager.LogSessionActivity( + authorization.Client, + authorization.Version, + authorization.DeviceId, + authorization.Device, + request.HttpContext.Connection.RemoteIpAddress.ToString(), + user); if (session == null) { -- cgit v1.2.3