From 36f3e933a23d802d154c16fd304a82c3fe3f453d Mon Sep 17 00:00:00 2001 From: ConfusedPolarBear <33811686+ConfusedPolarBear@users.noreply.github.com> Date: Wed, 15 Apr 2020 14:28:42 -0500 Subject: Add quick connect --- .../QuickConnect/QuickConnectResult.cs | 50 ++++++++++++++++++++++ 1 file changed, 50 insertions(+) create mode 100644 MediaBrowser.Model/QuickConnect/QuickConnectResult.cs (limited to 'MediaBrowser.Model/QuickConnect/QuickConnectResult.cs') diff --git a/MediaBrowser.Model/QuickConnect/QuickConnectResult.cs b/MediaBrowser.Model/QuickConnect/QuickConnectResult.cs new file mode 100644 index 000000000..bc3fd0046 --- /dev/null +++ b/MediaBrowser.Model/QuickConnect/QuickConnectResult.cs @@ -0,0 +1,50 @@ +using System; + +namespace MediaBrowser.Model.QuickConnect +{ + /// + /// Stores the result of an incoming quick connect request. + /// + public class QuickConnectResult + { + /// + /// Gets a value indicating whether this request is authorized. + /// + public bool Authenticated => !string.IsNullOrEmpty(Authentication); + + /// + /// Gets or sets the secret value used to uniquely identify this request. Can be used to retrieve authentication information. + /// + public string Secret { get; set; } + + /// + /// Gets or sets the public value used to uniquely identify this request. Can only be used to authorize the request. + /// + public string Lookup { get; set; } + + /// + /// Gets or sets the user facing code used so the user can quickly differentiate this request from others. + /// + public string Code { get; set; } + + /// + /// Gets or sets the device friendly name. + /// + public string FriendlyName { get; set; } + + /// + /// Gets or sets the private access token. + /// + public string Authentication { get; set; } + + /// + /// Gets or sets an error message. + /// + public string Error { get; set; } + + /// + /// Gets or sets the DateTime that this request was created. + /// + public DateTime DateAdded { get; set; } + } +} -- cgit v1.2.3 From 0d6a63bf84d7ad971128c6ba6cad77e76e023536 Mon Sep 17 00:00:00 2001 From: ConfusedPolarBear <33811686+ConfusedPolarBear@users.noreply.github.com> Date: Mon, 8 Jun 2020 15:48:18 -0500 Subject: Make all properties nullable --- .../QuickConnect/ConfigurationExtension.cs | 2 ++ .../QuickConnect/QuickConnectConfiguration.cs | 2 ++ .../QuickConnect/QuickConnectManager.cs | 10 ++++++---- MediaBrowser.Model/QuickConnect/QuickConnectResult.cs | 14 +++++++------- MediaBrowser.Model/QuickConnect/QuickConnectResultDto.cs | 8 ++++---- 5 files changed, 21 insertions(+), 15 deletions(-) (limited to 'MediaBrowser.Model/QuickConnect/QuickConnectResult.cs') diff --git a/Emby.Server.Implementations/QuickConnect/ConfigurationExtension.cs b/Emby.Server.Implementations/QuickConnect/ConfigurationExtension.cs index 458bb7614..0e35ba80a 100644 --- a/Emby.Server.Implementations/QuickConnect/ConfigurationExtension.cs +++ b/Emby.Server.Implementations/QuickConnect/ConfigurationExtension.cs @@ -1,3 +1,5 @@ +#pragma warning disable CS1591 + using System.Collections.Generic; using MediaBrowser.Common.Configuration; diff --git a/Emby.Server.Implementations/QuickConnect/QuickConnectConfiguration.cs b/Emby.Server.Implementations/QuickConnect/QuickConnectConfiguration.cs index befc46379..11e558bae 100644 --- a/Emby.Server.Implementations/QuickConnect/QuickConnectConfiguration.cs +++ b/Emby.Server.Implementations/QuickConnect/QuickConnectConfiguration.cs @@ -1,3 +1,5 @@ +#pragma warning disable CS1591 + using MediaBrowser.Model.QuickConnect; namespace Emby.Server.Implementations.QuickConnect diff --git a/Emby.Server.Implementations/QuickConnect/QuickConnectManager.cs b/Emby.Server.Implementations/QuickConnect/QuickConnectManager.cs index b8b51adb6..929e021a3 100644 --- a/Emby.Server.Implementations/QuickConnect/QuickConnectManager.cs +++ b/Emby.Server.Implementations/QuickConnect/QuickConnectManager.cs @@ -234,7 +234,8 @@ namespace Emby.Server.Implementations.QuickConnect result.Authentication = Guid.NewGuid().ToString("N", CultureInfo.InvariantCulture); // Advance the time on the request so it expires sooner as the client will pick up the changes in a few seconds - result.DateAdded = result.DateAdded.Subtract(new TimeSpan(0, RequestExpiry - 1, 0)); + var added = result.DateAdded ?? DateTime.Now.Subtract(new TimeSpan(0, RequestExpiry, 0)); + result.DateAdded = added.Subtract(new TimeSpan(0, RequestExpiry - 1, 0)); _authenticationRepository.Create(new AuthenticationInfo { @@ -284,7 +285,7 @@ namespace Emby.Server.Implementations.QuickConnect { bool expireAll = false; - // check if quick connect should be deactivated + // Check if quick connect should be deactivated if (TemporaryActivation && DateTime.Now > DateActivated.AddMinutes(10) && State == QuickConnectState.Active) { _logger.LogDebug("Quick connect time expired, deactivating"); @@ -293,13 +294,14 @@ namespace Emby.Server.Implementations.QuickConnect TemporaryActivation = false; } - // expire stale connection requests + // Expire stale connection requests var delete = new List(); var values = _currentRequests.Values.ToList(); for (int i = 0; i < _currentRequests.Count; i++) { - if (DateTime.Now > values[i].DateAdded.AddMinutes(RequestExpiry) || expireAll) + var added = values[i].DateAdded ?? DateTime.UnixEpoch; + if (DateTime.Now > added.AddMinutes(RequestExpiry) || expireAll) { delete.Add(values[i].Lookup); } diff --git a/MediaBrowser.Model/QuickConnect/QuickConnectResult.cs b/MediaBrowser.Model/QuickConnect/QuickConnectResult.cs index bc3fd0046..32d7f6aba 100644 --- a/MediaBrowser.Model/QuickConnect/QuickConnectResult.cs +++ b/MediaBrowser.Model/QuickConnect/QuickConnectResult.cs @@ -15,36 +15,36 @@ namespace MediaBrowser.Model.QuickConnect /// /// Gets or sets the secret value used to uniquely identify this request. Can be used to retrieve authentication information. /// - public string Secret { get; set; } + public string? Secret { get; set; } /// /// Gets or sets the public value used to uniquely identify this request. Can only be used to authorize the request. /// - public string Lookup { get; set; } + public string? Lookup { get; set; } /// /// Gets or sets the user facing code used so the user can quickly differentiate this request from others. /// - public string Code { get; set; } + public string? Code { get; set; } /// /// Gets or sets the device friendly name. /// - public string FriendlyName { get; set; } + public string? FriendlyName { get; set; } /// /// Gets or sets the private access token. /// - public string Authentication { get; set; } + public string? Authentication { get; set; } /// /// Gets or sets an error message. /// - public string Error { get; set; } + public string? Error { get; set; } /// /// Gets or sets the DateTime that this request was created. /// - public DateTime DateAdded { get; set; } + public DateTime? DateAdded { get; set; } } } diff --git a/MediaBrowser.Model/QuickConnect/QuickConnectResultDto.cs b/MediaBrowser.Model/QuickConnect/QuickConnectResultDto.cs index 671b7cc94..19acc7cd8 100644 --- a/MediaBrowser.Model/QuickConnect/QuickConnectResultDto.cs +++ b/MediaBrowser.Model/QuickConnect/QuickConnectResultDto.cs @@ -15,22 +15,22 @@ namespace MediaBrowser.Model.QuickConnect /// /// Gets the user facing code used so the user can quickly differentiate this request from others. /// - public string Code { get; private set; } + public string? Code { get; private set; } /// /// Gets the public value used to uniquely identify this request. Can only be used to authorize the request. /// - public string Lookup { get; private set; } + public string? Lookup { get; private set; } /// /// Gets the device friendly name. /// - public string FriendlyName { get; private set; } + public string? FriendlyName { get; private set; } /// /// Gets the DateTime that this request was created. /// - public DateTime DateAdded { get; private set; } + public DateTime? DateAdded { get; private set; } /// /// Cast an internal quick connect result to a DTO by removing all sensitive properties. -- cgit v1.2.3 From 4be476ec5312387f87134915d0fd132b2ad5fa3f Mon Sep 17 00:00:00 2001 From: ConfusedPolarBear <33811686+ConfusedPolarBear@users.noreply.github.com> Date: Thu, 18 Jun 2020 01:29:47 -0500 Subject: Move all settings into the main server configuration Decreased the timeout from 30 minutes to 5. Public lookup values have been replaced with the short code. --- .../QuickConnect/ConfigurationExtension.cs | 20 ------- .../QuickConnect/QuickConnectConfiguration.cs | 15 ----- .../QuickConnectConfigurationFactory.cs | 27 --------- .../QuickConnect/QuickConnectManager.cs | 66 ++++++++++------------ .../QuickConnect/IQuickConnect.cs | 8 +-- .../Configuration/ServerConfiguration.cs | 6 ++ .../QuickConnect/QuickConnectResult.cs | 5 -- .../QuickConnect/QuickConnectResultDto.cs | 14 +---- 8 files changed, 41 insertions(+), 120 deletions(-) delete mode 100644 Emby.Server.Implementations/QuickConnect/ConfigurationExtension.cs delete mode 100644 Emby.Server.Implementations/QuickConnect/QuickConnectConfiguration.cs delete mode 100644 Emby.Server.Implementations/QuickConnect/QuickConnectConfigurationFactory.cs (limited to 'MediaBrowser.Model/QuickConnect/QuickConnectResult.cs') diff --git a/Emby.Server.Implementations/QuickConnect/ConfigurationExtension.cs b/Emby.Server.Implementations/QuickConnect/ConfigurationExtension.cs deleted file mode 100644 index 2a19fc36c..000000000 --- a/Emby.Server.Implementations/QuickConnect/ConfigurationExtension.cs +++ /dev/null @@ -1,20 +0,0 @@ -using MediaBrowser.Common.Configuration; - -namespace Emby.Server.Implementations.QuickConnect -{ - /// - /// Configuration extension to support persistent quick connect configuration. - /// - public static class ConfigurationExtension - { - /// - /// Return the current quick connect configuration. - /// - /// Configuration manager. - /// Current quick connect configuration. - public static QuickConnectConfiguration GetQuickConnectConfiguration(this IConfigurationManager manager) - { - return manager.GetConfiguration("quickconnect"); - } - } -} diff --git a/Emby.Server.Implementations/QuickConnect/QuickConnectConfiguration.cs b/Emby.Server.Implementations/QuickConnect/QuickConnectConfiguration.cs deleted file mode 100644 index 2302ddbc3..000000000 --- a/Emby.Server.Implementations/QuickConnect/QuickConnectConfiguration.cs +++ /dev/null @@ -1,15 +0,0 @@ -using MediaBrowser.Model.QuickConnect; - -namespace Emby.Server.Implementations.QuickConnect -{ - /// - /// Persistent quick connect configuration. - /// - public class QuickConnectConfiguration - { - /// - /// Gets or sets persistent quick connect availability state. - /// - public QuickConnectState State { get; set; } - } -} diff --git a/Emby.Server.Implementations/QuickConnect/QuickConnectConfigurationFactory.cs b/Emby.Server.Implementations/QuickConnect/QuickConnectConfigurationFactory.cs deleted file mode 100644 index d7bc84c5e..000000000 --- a/Emby.Server.Implementations/QuickConnect/QuickConnectConfigurationFactory.cs +++ /dev/null @@ -1,27 +0,0 @@ -using System.Collections.Generic; -using MediaBrowser.Common.Configuration; - -namespace Emby.Server.Implementations.QuickConnect -{ - /// - /// Configuration factory for quick connect. - /// - public class QuickConnectConfigurationFactory : IConfigurationFactory - { - /// - /// Returns the current quick connect configuration. - /// - /// Current quick connect configuration. - public IEnumerable GetConfigurations() - { - return new[] - { - new ConfigurationStore - { - Key = "quickconnect", - ConfigurationType = typeof(QuickConnectConfiguration) - } - }; - } - } -} diff --git a/Emby.Server.Implementations/QuickConnect/QuickConnectManager.cs b/Emby.Server.Implementations/QuickConnect/QuickConnectManager.cs index 7a584c7cd..8d704f32b 100644 --- a/Emby.Server.Implementations/QuickConnect/QuickConnectManager.cs +++ b/Emby.Server.Implementations/QuickConnect/QuickConnectManager.cs @@ -11,7 +11,9 @@ using MediaBrowser.Controller.QuickConnect; using MediaBrowser.Controller.Security; using MediaBrowser.Model.QuickConnect; using MediaBrowser.Model.Services; +using MediaBrowser.Common; using Microsoft.Extensions.Logging; +using MediaBrowser.Common.Extensions; namespace Emby.Server.Implementations.QuickConnect { @@ -64,9 +66,7 @@ namespace Emby.Server.Implementations.QuickConnect public QuickConnectState State { get; private set; } = QuickConnectState.Unavailable; /// - public int RequestExpiry { get; set; } = 30; - - private bool TemporaryActivation { get; set; } = false; + public int Timeout { get; set; } = 5; private DateTime DateActivated { get; set; } @@ -82,10 +82,9 @@ namespace Emby.Server.Implementations.QuickConnect /// public QuickConnectResult Activate() { - // This should not call SetEnabled since that would persist the "temporary" activation to the configuration file - State = QuickConnectState.Active; + SetEnabled(QuickConnectState.Active); + DateActivated = DateTime.Now; - TemporaryActivation = true; return new QuickConnectResult(); } @@ -96,12 +95,10 @@ namespace Emby.Server.Implementations.QuickConnect _logger.LogDebug("Changed quick connect state from {0} to {1}", State, newState); ExpireRequests(true); - State = newState; - _config.SaveConfiguration("quickconnect", new QuickConnectConfiguration() - { - State = State - }); + State = newState; + _config.Configuration.QuickConnectAvailable = newState == QuickConnectState.Available || newState == QuickConnectState.Active; + _config.SaveConfiguration(); _logger.LogDebug("Configuration saved"); } @@ -123,17 +120,16 @@ namespace Emby.Server.Implementations.QuickConnect _logger.LogDebug("Got new quick connect request from {friendlyName}", friendlyName); - var lookup = GenerateSecureRandom(); + var code = GenerateCode(); var result = new QuickConnectResult() { - Lookup = lookup, Secret = GenerateSecureRandom(), FriendlyName = friendlyName, DateAdded = DateTime.Now, - Code = GenerateCode() + Code = code }; - _currentRequests[lookup] = result; + _currentRequests[code] = result; return result; } @@ -143,17 +139,16 @@ namespace Emby.Server.Implementations.QuickConnect ExpireRequests(); AssertActive(); - string lookup = _currentRequests.Where(x => x.Value.Secret == secret).Select(x => x.Value.Lookup).DefaultIfEmpty(string.Empty).First(); + string code = _currentRequests.Where(x => x.Value.Secret == secret).Select(x => x.Value.Code).DefaultIfEmpty(string.Empty).First(); - if (!_currentRequests.TryGetValue(lookup, out QuickConnectResult result)) + if (!_currentRequests.TryGetValue(code, out QuickConnectResult result)) { - throw new KeyNotFoundException("Unable to find request with provided identifier"); + throw new ResourceNotFoundException("Unable to find request with provided secret"); } return result; } - /// public List GetCurrentRequests() { return GetCurrentRequestsInternal().Select(x => (QuickConnectResultDto)x).ToList(); @@ -186,16 +181,16 @@ namespace Emby.Server.Implementations.QuickConnect } /// - public bool AuthorizeRequest(IRequest request, string lookup) + public bool AuthorizeRequest(IRequest request, string code) { ExpireRequests(); AssertActive(); var auth = _authContext.GetAuthorizationInfo(request); - if (!_currentRequests.TryGetValue(lookup, out QuickConnectResult result)) + if (!_currentRequests.TryGetValue(code, out QuickConnectResult result)) { - throw new KeyNotFoundException("Unable to find request"); + throw new ResourceNotFoundException("Unable to find request"); } if (result.Authenticated) @@ -205,9 +200,9 @@ namespace Emby.Server.Implementations.QuickConnect result.Authentication = Guid.NewGuid().ToString("N", CultureInfo.InvariantCulture); - // Advance the time on the request so it expires sooner as the client will pick up the changes in a few seconds - var added = result.DateAdded ?? DateTime.Now.Subtract(new TimeSpan(0, RequestExpiry, 0)); - result.DateAdded = added.Subtract(new TimeSpan(0, RequestExpiry - 1, 0)); + // Change the time on the request so it expires one minute into the future. It can't expire immediately as otherwise some clients wouldn't ever see that they have been authenticated. + var added = result.DateAdded ?? DateTime.Now.Subtract(new TimeSpan(0, Timeout, 0)); + result.DateAdded = added.Subtract(new TimeSpan(0, Timeout - 1, 0)); _authenticationRepository.Create(new AuthenticationInfo { @@ -271,7 +266,7 @@ namespace Emby.Server.Implementations.QuickConnect var bytes = new byte[length]; _rng.GetBytes(bytes); - return string.Join(string.Empty, bytes.Select(x => x.ToString("x2", CultureInfo.InvariantCulture))); + return Hex.Encode(bytes); } /// @@ -281,12 +276,11 @@ namespace Emby.Server.Implementations.QuickConnect private void ExpireRequests(bool expireAll = false) { // Check if quick connect should be deactivated - if (TemporaryActivation && DateTime.Now > DateActivated.AddMinutes(10) && State == QuickConnectState.Active && !expireAll) + if (State == QuickConnectState.Active && DateTime.Now > DateActivated.AddMinutes(Timeout) && !expireAll) { _logger.LogDebug("Quick connect time expired, deactivating"); SetEnabled(QuickConnectState.Available); expireAll = true; - TemporaryActivation = false; } // Expire stale connection requests @@ -296,28 +290,28 @@ namespace Emby.Server.Implementations.QuickConnect for (int i = 0; i < values.Count; i++) { var added = values[i].DateAdded ?? DateTime.UnixEpoch; - if (DateTime.Now > added.AddMinutes(RequestExpiry) || expireAll) + if (DateTime.Now > added.AddMinutes(Timeout) || expireAll) { - delete.Add(values[i].Lookup); + delete.Add(values[i].Code); } } - foreach (var lookup in delete) + foreach (var code in delete) { - _logger.LogDebug("Removing expired request {lookup}", lookup); + _logger.LogDebug("Removing expired request {code}", code); - if (!_currentRequests.TryRemove(lookup, out _)) + if (!_currentRequests.TryRemove(code, out _)) { - _logger.LogWarning("Request {lookup} already expired", lookup); + _logger.LogWarning("Request {code} already expired", code); } } } private void ReloadConfiguration() { - var config = _config.GetQuickConnectConfiguration(); + var available = _config.Configuration.QuickConnectAvailable; - State = config.State; + State = available ? QuickConnectState.Available : QuickConnectState.Unavailable; } } } diff --git a/MediaBrowser.Controller/QuickConnect/IQuickConnect.cs b/MediaBrowser.Controller/QuickConnect/IQuickConnect.cs index d44765e11..d31d0e509 100644 --- a/MediaBrowser.Controller/QuickConnect/IQuickConnect.cs +++ b/MediaBrowser.Controller/QuickConnect/IQuickConnect.cs @@ -26,9 +26,9 @@ namespace MediaBrowser.Controller.QuickConnect public QuickConnectState State { get; } /// - /// Gets or sets the time (in minutes) before a pending request will expire. + /// Gets or sets the time (in minutes) before quick connect will automatically deactivate. /// - public int RequestExpiry { get; set; } + public int Timeout { get; set; } /// /// Assert that quick connect is currently active and throws an exception if it is not. @@ -77,9 +77,9 @@ namespace MediaBrowser.Controller.QuickConnect /// Authorizes a quick connect request to connect as the calling user. /// /// HTTP request object. - /// Public request lookup value. + /// Identifying code for the request.. /// A boolean indicating if the authorization completed successfully. - bool AuthorizeRequest(IRequest request, string lookup); + bool AuthorizeRequest(IRequest request, string code); /// /// Deletes all quick connect access tokens for the provided user. diff --git a/MediaBrowser.Model/Configuration/ServerConfiguration.cs b/MediaBrowser.Model/Configuration/ServerConfiguration.cs index afbe02dd3..76b290606 100644 --- a/MediaBrowser.Model/Configuration/ServerConfiguration.cs +++ b/MediaBrowser.Model/Configuration/ServerConfiguration.cs @@ -76,6 +76,11 @@ namespace MediaBrowser.Model.Configuration /// true if this instance is port authorized; otherwise, false. public bool IsPortAuthorized { get; set; } + /// + /// Gets or sets if quick connect is available for use on this server. + /// + public bool QuickConnectAvailable { get; set; } + public bool AutoRunWebApp { get; set; } public bool EnableRemoteAccess { get; set; } @@ -281,6 +286,7 @@ namespace MediaBrowser.Model.Configuration AutoRunWebApp = true; EnableRemoteAccess = true; + QuickConnectAvailable = false; EnableUPnP = false; MinResumePct = 5; diff --git a/MediaBrowser.Model/QuickConnect/QuickConnectResult.cs b/MediaBrowser.Model/QuickConnect/QuickConnectResult.cs index 32d7f6aba..a10d60d57 100644 --- a/MediaBrowser.Model/QuickConnect/QuickConnectResult.cs +++ b/MediaBrowser.Model/QuickConnect/QuickConnectResult.cs @@ -17,11 +17,6 @@ namespace MediaBrowser.Model.QuickConnect /// public string? Secret { get; set; } - /// - /// Gets or sets the public value used to uniquely identify this request. Can only be used to authorize the request. - /// - public string? Lookup { get; set; } - /// /// Gets or sets the user facing code used so the user can quickly differentiate this request from others. /// diff --git a/MediaBrowser.Model/QuickConnect/QuickConnectResultDto.cs b/MediaBrowser.Model/QuickConnect/QuickConnectResultDto.cs index 19acc7cd8..26084caf1 100644 --- a/MediaBrowser.Model/QuickConnect/QuickConnectResultDto.cs +++ b/MediaBrowser.Model/QuickConnect/QuickConnectResultDto.cs @@ -17,25 +17,15 @@ namespace MediaBrowser.Model.QuickConnect /// public string? Code { get; private set; } - /// - /// Gets the public value used to uniquely identify this request. Can only be used to authorize the request. - /// - public string? Lookup { get; private set; } - /// /// Gets the device friendly name. /// public string? FriendlyName { get; private set; } - /// - /// Gets the DateTime that this request was created. - /// - public DateTime? DateAdded { get; private set; } - /// /// Cast an internal quick connect result to a DTO by removing all sensitive properties. /// - /// QuickConnectResult object to cast + /// QuickConnectResult object to cast. public static implicit operator QuickConnectResultDto(QuickConnectResult result) { QuickConnectResultDto resultDto = new QuickConnectResultDto @@ -43,8 +33,6 @@ namespace MediaBrowser.Model.QuickConnect Authenticated = result.Authenticated, Code = result.Code, FriendlyName = result.FriendlyName, - DateAdded = result.DateAdded, - Lookup = result.Lookup }; return resultDto; -- cgit v1.2.3 From 5f1a86324170387f12602d77dad7249faf30548f Mon Sep 17 00:00:00 2001 From: Matt Montgomery <33811686+ConfusedPolarBear@users.noreply.github.com> Date: Mon, 17 Aug 2020 16:36:45 -0500 Subject: Apply suggestions from code review --- .../QuickConnect/QuickConnectManager.cs | 38 +++++++++------------- .../Session/SessionManager.cs | 2 +- Jellyfin.Api/Controllers/QuickConnectController.cs | 34 +++++++++---------- Jellyfin.Api/Controllers/UserController.cs | 4 +-- .../QuickConnect/IQuickConnect.cs | 12 +++---- .../QuickConnect/QuickConnectResult.cs | 5 --- 6 files changed, 40 insertions(+), 55 deletions(-) (limited to 'MediaBrowser.Model/QuickConnect/QuickConnectResult.cs') diff --git a/Emby.Server.Implementations/QuickConnect/QuickConnectManager.cs b/Emby.Server.Implementations/QuickConnect/QuickConnectManager.cs index 949c3b505..52e934229 100644 --- a/Emby.Server.Implementations/QuickConnect/QuickConnectManager.cs +++ b/Emby.Server.Implementations/QuickConnect/QuickConnectManager.cs @@ -3,17 +3,16 @@ using System.Collections.Concurrent; using System.Globalization; using System.Linq; using System.Security.Cryptography; +using MediaBrowser.Common; +using MediaBrowser.Common.Extensions; using MediaBrowser.Controller; +using MediaBrowser.Controller.Authentication; using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Net; using MediaBrowser.Controller.QuickConnect; using MediaBrowser.Controller.Security; using MediaBrowser.Model.QuickConnect; -using Microsoft.AspNetCore.Http; -using MediaBrowser.Common; using Microsoft.Extensions.Logging; -using MediaBrowser.Common.Extensions; -using MediaBrowser.Controller.Authentication; namespace Emby.Server.Implementations.QuickConnect { @@ -60,7 +59,7 @@ namespace Emby.Server.Implementations.QuickConnect public int CodeLength { get; set; } = 6; /// - public string TokenNamePrefix { get; set; } = "QuickConnect-"; + public string TokenName { get; set; } = "QuickConnect"; /// public QuickConnectState State { get; private set; } = QuickConnectState.Unavailable; @@ -82,7 +81,7 @@ namespace Emby.Server.Implementations.QuickConnect /// public void Activate() { - DateActivated = DateTime.Now; + DateActivated = DateTime.UtcNow; SetState(QuickConnectState.Active); } @@ -101,7 +100,7 @@ namespace Emby.Server.Implementations.QuickConnect } /// - public QuickConnectResult TryConnect(string friendlyName) + public QuickConnectResult TryConnect() { ExpireRequests(); @@ -111,14 +110,11 @@ namespace Emby.Server.Implementations.QuickConnect throw new AuthenticationException("Quick connect is not active on this server"); } - _logger.LogDebug("Got new quick connect request from {friendlyName}", friendlyName); - var code = GenerateCode(); var result = new QuickConnectResult() { Secret = GenerateSecureRandom(), - FriendlyName = friendlyName, - DateAdded = DateTime.Now, + DateAdded = DateTime.UtcNow, Code = code }; @@ -162,13 +158,11 @@ namespace Emby.Server.Implementations.QuickConnect } /// - public bool AuthorizeRequest(HttpRequest request, string code) + public bool AuthorizeRequest(Guid userId, string code) { ExpireRequests(); AssertActive(); - var auth = _authContext.GetAuthorizationInfo(request); - if (!_currentRequests.TryGetValue(code, out QuickConnectResult result)) { throw new ResourceNotFoundException("Unable to find request"); @@ -182,21 +176,21 @@ namespace Emby.Server.Implementations.QuickConnect result.Authentication = Guid.NewGuid().ToString("N", CultureInfo.InvariantCulture); // Change the time on the request so it expires one minute into the future. It can't expire immediately as otherwise some clients wouldn't ever see that they have been authenticated. - var added = result.DateAdded ?? DateTime.Now.Subtract(new TimeSpan(0, Timeout, 0)); - result.DateAdded = added.Subtract(new TimeSpan(0, Timeout - 1, 0)); + var added = result.DateAdded ?? DateTime.UtcNow.Subtract(TimeSpan.FromMinutes(Timeout)); + result.DateAdded = added.Subtract(TimeSpan.FromMinutes(Timeout - 1)); _authenticationRepository.Create(new AuthenticationInfo { - AppName = TokenNamePrefix + result.FriendlyName, + AppName = TokenName, AccessToken = result.Authentication, DateCreated = DateTime.UtcNow, DeviceId = _appHost.SystemId, DeviceName = _appHost.FriendlyName, AppVersion = _appHost.ApplicationVersionString, - UserId = auth.UserId + UserId = userId }); - _logger.LogInformation("Allowing device {FriendlyName} to login as user {Username} with quick connect code {Code}", result.FriendlyName, auth.User.Username, result.Code); + _logger.LogDebug("Authorizing device with code {Code} to login as user {userId}", code, userId); return true; } @@ -210,7 +204,7 @@ namespace Emby.Server.Implementations.QuickConnect UserId = user }); - var tokens = raw.Items.Where(x => x.AppName.StartsWith(TokenNamePrefix, StringComparison.CurrentCulture)); + var tokens = raw.Items.Where(x => x.AppName.StartsWith(TokenName, StringComparison.CurrentCulture)); var removed = 0; foreach (var token in tokens) @@ -256,7 +250,7 @@ namespace Emby.Server.Implementations.QuickConnect public void ExpireRequests(bool expireAll = false) { // Check if quick connect should be deactivated - if (State == QuickConnectState.Active && DateTime.Now > DateActivated.AddMinutes(Timeout) && !expireAll) + if (State == QuickConnectState.Active && DateTime.UtcNow > DateActivated.AddMinutes(Timeout) && !expireAll) { _logger.LogDebug("Quick connect time expired, deactivating"); SetState(QuickConnectState.Available); @@ -270,7 +264,7 @@ namespace Emby.Server.Implementations.QuickConnect for (int i = 0; i < values.Count; i++) { var added = values[i].DateAdded ?? DateTime.UnixEpoch; - if (DateTime.Now > added.AddMinutes(Timeout) || expireAll) + if (DateTime.UtcNow > added.AddMinutes(Timeout) || expireAll) { code = values[i].Code; _logger.LogDebug("Removing expired request {code}", code); diff --git a/Emby.Server.Implementations/Session/SessionManager.cs b/Emby.Server.Implementations/Session/SessionManager.cs index 8a8223ee7..fbe8e065c 100644 --- a/Emby.Server.Implementations/Session/SessionManager.cs +++ b/Emby.Server.Implementations/Session/SessionManager.cs @@ -1433,7 +1433,7 @@ namespace Emby.Server.Implementations.Session Limit = 1 }); - if (result.TotalRecordCount < 1) + if (result.TotalRecordCount == 0) { throw new SecurityException("Unknown quick connect token"); } diff --git a/Jellyfin.Api/Controllers/QuickConnectController.cs b/Jellyfin.Api/Controllers/QuickConnectController.cs index 1625bcffe..b1ee2ff53 100644 --- a/Jellyfin.Api/Controllers/QuickConnectController.cs +++ b/Jellyfin.Api/Controllers/QuickConnectController.cs @@ -1,8 +1,8 @@ +using System; using System.ComponentModel.DataAnnotations; using Jellyfin.Api.Constants; using Jellyfin.Api.Helpers; using MediaBrowser.Common.Extensions; -using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Net; using MediaBrowser.Controller.QuickConnect; using MediaBrowser.Model.QuickConnect; @@ -18,22 +18,18 @@ namespace Jellyfin.Api.Controllers public class QuickConnectController : BaseJellyfinApiController { private readonly IQuickConnect _quickConnect; - private readonly IUserManager _userManager; private readonly IAuthorizationContext _authContext; /// /// Initializes a new instance of the class. /// /// Instance of the interface. - /// Instance of the interface. /// Instance of the interface. public QuickConnectController( IQuickConnect quickConnect, - IUserManager userManager, IAuthorizationContext authContext) { _quickConnect = quickConnect; - _userManager = userManager; _authContext = authContext; } @@ -53,15 +49,14 @@ namespace Jellyfin.Api.Controllers /// /// Initiate a new quick connect request. /// - /// Device friendly name. /// Quick connect request successfully created. /// Quick connect is not active on this server. /// A with a secret and code for future use or an error message. [HttpGet("Initiate")] [ProducesResponseType(StatusCodes.Status200OK)] - public ActionResult Initiate([FromQuery] string? friendlyName) + public ActionResult Initiate() { - return _quickConnect.TryConnect(friendlyName); + return _quickConnect.TryConnect(); } /// @@ -74,12 +69,11 @@ namespace Jellyfin.Api.Controllers [HttpGet("Connect")] [ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status404NotFound)] - public ActionResult Connect([FromQuery] string? secret) + public ActionResult Connect([FromQuery, Required] string secret) { try { - var result = _quickConnect.CheckRequestStatus(secret); - return result; + return _quickConnect.CheckRequestStatus(secret); } catch (ResourceNotFoundException) { @@ -117,9 +111,9 @@ namespace Jellyfin.Api.Controllers [HttpPost("Available")] [Authorize(Policy = Policies.RequiresElevation)] [ProducesResponseType(StatusCodes.Status204NoContent)] - public ActionResult Available([FromQuery] QuickConnectState? status) + public ActionResult Available([FromQuery] QuickConnectState status = QuickConnectState.Available) { - _quickConnect.SetState(status ?? QuickConnectState.Available); + _quickConnect.SetState(status); return NoContent(); } @@ -127,16 +121,22 @@ namespace Jellyfin.Api.Controllers /// Authorizes a pending quick connect request. /// /// Quick connect code to authorize. + /// User id. /// Quick connect result authorized successfully. - /// Missing quick connect code. + /// User is not allowed to authorize quick connect requests. /// Boolean indicating if the authorization was successful. [HttpPost("Authorize")] [Authorize(Policy = Policies.DefaultAuthorization)] [ProducesResponseType(StatusCodes.Status200OK)] - [ProducesResponseType(StatusCodes.Status400BadRequest)] - public ActionResult Authorize([FromQuery, Required] string? code) + [ProducesResponseType(StatusCodes.Status403Forbidden)] + public ActionResult Authorize([FromQuery, Required] string code, [FromQuery, Required] Guid userId) { - return _quickConnect.AuthorizeRequest(Request, code); + if (!RequestHelpers.AssertCanUpdateUser(_authContext, HttpContext.Request, userId, true)) + { + return Forbid("User is not allowed to authorize quick connect requests."); + } + + return _quickConnect.AuthorizeRequest(userId, code); } /// diff --git a/Jellyfin.Api/Controllers/UserController.cs b/Jellyfin.Api/Controllers/UserController.cs index 355816bd3..d67f82219 100644 --- a/Jellyfin.Api/Controllers/UserController.cs +++ b/Jellyfin.Api/Controllers/UserController.cs @@ -239,11 +239,9 @@ namespace Jellyfin.Api.Controllers DeviceName = auth.Device, }; - var result = await _sessionManager.AuthenticateQuickConnect( + return await _sessionManager.AuthenticateQuickConnect( authRequest, request.Token).ConfigureAwait(false); - - return result; } catch (SecurityException e) { diff --git a/MediaBrowser.Controller/QuickConnect/IQuickConnect.cs b/MediaBrowser.Controller/QuickConnect/IQuickConnect.cs index fd7e973f6..959a2d771 100644 --- a/MediaBrowser.Controller/QuickConnect/IQuickConnect.cs +++ b/MediaBrowser.Controller/QuickConnect/IQuickConnect.cs @@ -1,6 +1,5 @@ using System; using MediaBrowser.Model.QuickConnect; -using Microsoft.AspNetCore.Http; namespace MediaBrowser.Controller.QuickConnect { @@ -15,9 +14,9 @@ namespace MediaBrowser.Controller.QuickConnect int CodeLength { get; set; } /// - /// Gets or sets the string to prefix internal access tokens with. + /// Gets or sets the name of internal access tokens. /// - string TokenNamePrefix { get; set; } + string TokenName { get; set; } /// /// Gets the current state of quick connect. @@ -48,9 +47,8 @@ namespace MediaBrowser.Controller.QuickConnect /// /// Initiates a new quick connect request. /// - /// Friendly device name to display in the request UI. /// A quick connect result with tokens to proceed or throws an exception if not active. - QuickConnectResult TryConnect(string friendlyName); + QuickConnectResult TryConnect(); /// /// Checks the status of an individual request. @@ -62,10 +60,10 @@ namespace MediaBrowser.Controller.QuickConnect /// /// Authorizes a quick connect request to connect as the calling user. /// - /// HTTP request object. + /// User id. /// Identifying code for the request. /// A boolean indicating if the authorization completed successfully. - bool AuthorizeRequest(HttpRequest request, string code); + bool AuthorizeRequest(Guid userId, string code); /// /// Expire quick connect requests that are over the time limit. If is true, all requests are unconditionally expired. diff --git a/MediaBrowser.Model/QuickConnect/QuickConnectResult.cs b/MediaBrowser.Model/QuickConnect/QuickConnectResult.cs index a10d60d57..0fa40b6a7 100644 --- a/MediaBrowser.Model/QuickConnect/QuickConnectResult.cs +++ b/MediaBrowser.Model/QuickConnect/QuickConnectResult.cs @@ -22,11 +22,6 @@ namespace MediaBrowser.Model.QuickConnect /// public string? Code { get; set; } - /// - /// Gets or sets the device friendly name. - /// - public string? FriendlyName { get; set; } - /// /// Gets or sets the private access token. /// -- cgit v1.2.3