Add persistent setting configuration and temporary activation

This commit is contained in:
ConfusedPolarBear 2020-04-19 01:33:09 -05:00
parent 36f3e933a2
commit 387a07c6dd
5 changed files with 150 additions and 15 deletions

View File

@ -0,0 +1,30 @@
#pragma warning disable CS1591
using System.Collections.Generic;
using MediaBrowser.Common.Configuration;
namespace Emby.Server.Implementations.QuickConnect
{
public static class ConfigurationExtension
{
public static QuickConnectConfiguration GetQuickConnectConfiguration(this IConfigurationManager manager)
{
return manager.GetConfiguration<QuickConnectConfiguration>("quickconnect");
}
}
public class QuickConnectConfigurationFactory : IConfigurationFactory
{
public IEnumerable<ConfigurationStore> GetConfigurations()
{
return new ConfigurationStore[]
{
new ConfigurationStore
{
Key = "quickconnect",
ConfigurationType = typeof(QuickConnectConfiguration)
}
};
}
}
}

View File

@ -0,0 +1,13 @@
using MediaBrowser.Model.QuickConnect;
namespace Emby.Server.Implementations.QuickConnect
{
public class QuickConnectConfiguration
{
public QuickConnectConfiguration()
{
}
public QuickConnectState State { get; set; }
}
}

View File

@ -3,7 +3,9 @@ using System.Collections.Generic;
using System.Globalization; using System.Globalization;
using System.Linq; using System.Linq;
using System.Security.Cryptography; using System.Security.Cryptography;
using MediaBrowser.Common.Configuration;
using MediaBrowser.Controller; using MediaBrowser.Controller;
using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.Net; using MediaBrowser.Controller.Net;
using MediaBrowser.Controller.QuickConnect; using MediaBrowser.Controller.QuickConnect;
@ -12,6 +14,7 @@ using MediaBrowser.Model.Globalization;
using MediaBrowser.Model.QuickConnect; using MediaBrowser.Model.QuickConnect;
using MediaBrowser.Model.Serialization; using MediaBrowser.Model.Serialization;
using MediaBrowser.Model.Services; using MediaBrowser.Model.Services;
using MediaBrowser.Model.Tasks;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
namespace Emby.Server.Implementations.QuickConnect namespace Emby.Server.Implementations.QuickConnect
@ -24,6 +27,7 @@ namespace Emby.Server.Implementations.QuickConnect
private readonly RNGCryptoServiceProvider _rng = new RNGCryptoServiceProvider(); private readonly RNGCryptoServiceProvider _rng = new RNGCryptoServiceProvider();
private Dictionary<string, QuickConnectResult> _currentRequests = new Dictionary<string, QuickConnectResult>(); private Dictionary<string, QuickConnectResult> _currentRequests = new Dictionary<string, QuickConnectResult>();
private IServerConfigurationManager _config;
private ILogger _logger; private ILogger _logger;
private IUserManager _userManager; private IUserManager _userManager;
private ILocalizationManager _localizationManager; private ILocalizationManager _localizationManager;
@ -31,11 +35,13 @@ namespace Emby.Server.Implementations.QuickConnect
private IAuthenticationRepository _authenticationRepository; private IAuthenticationRepository _authenticationRepository;
private IAuthorizationContext _authContext; private IAuthorizationContext _authContext;
private IServerApplicationHost _appHost; private IServerApplicationHost _appHost;
private ITaskManager _taskManager;
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="QuickConnectManager"/> class. /// Initializes a new instance of the <see cref="QuickConnectManager"/> class.
/// Should only be called at server startup when a singleton is created. /// Should only be called at server startup when a singleton is created.
/// </summary> /// </summary>
/// <param name="config">Configuration.</param>
/// <param name="loggerFactory">Logger.</param> /// <param name="loggerFactory">Logger.</param>
/// <param name="userManager">User manager.</param> /// <param name="userManager">User manager.</param>
/// <param name="localization">Localization.</param> /// <param name="localization">Localization.</param>
@ -43,15 +49,19 @@ namespace Emby.Server.Implementations.QuickConnect
/// <param name="appHost">Application host.</param> /// <param name="appHost">Application host.</param>
/// <param name="authContext">Authentication context.</param> /// <param name="authContext">Authentication context.</param>
/// <param name="authenticationRepository">Authentication repository.</param> /// <param name="authenticationRepository">Authentication repository.</param>
/// <param name="taskManager">Task scheduler.</param>
public QuickConnectManager( public QuickConnectManager(
IServerConfigurationManager config,
ILoggerFactory loggerFactory, ILoggerFactory loggerFactory,
IUserManager userManager, IUserManager userManager,
ILocalizationManager localization, ILocalizationManager localization,
IJsonSerializer jsonSerializer, IJsonSerializer jsonSerializer,
IServerApplicationHost appHost, IServerApplicationHost appHost,
IAuthorizationContext authContext, IAuthorizationContext authContext,
IAuthenticationRepository authenticationRepository) IAuthenticationRepository authenticationRepository,
ITaskManager taskManager)
{ {
_config = config;
_logger = loggerFactory.CreateLogger(nameof(QuickConnectManager)); _logger = loggerFactory.CreateLogger(nameof(QuickConnectManager));
_userManager = userManager; _userManager = userManager;
_localizationManager = localization; _localizationManager = localization;
@ -59,6 +69,16 @@ namespace Emby.Server.Implementations.QuickConnect
_appHost = appHost; _appHost = appHost;
_authContext = authContext; _authContext = authContext;
_authenticationRepository = authenticationRepository; _authenticationRepository = authenticationRepository;
_taskManager = taskManager;
ReloadConfiguration();
}
private void ReloadConfiguration()
{
var config = _config.GetQuickConnectConfiguration();
State = config.State;
} }
/// <inheritdoc/> /// <inheritdoc/>
@ -73,6 +93,10 @@ namespace Emby.Server.Implementations.QuickConnect
/// <inheritdoc/> /// <inheritdoc/>
public int RequestExpiry { get; set; } = 30; public int RequestExpiry { get; set; } = 30;
private bool TemporaryActivation { get; set; } = false;
private DateTime DateActivated { get; set; }
/// <inheritdoc/> /// <inheritdoc/>
public void AssertActive() public void AssertActive()
{ {
@ -82,17 +106,37 @@ namespace Emby.Server.Implementations.QuickConnect
} }
} }
/// <inheritdoc/>
public QuickConnectResult Activate()
{
// This should not call SetEnabled since that would persist the "temporary" activation to the configuration file
State = QuickConnectState.Active;
DateActivated = DateTime.Now;
TemporaryActivation = true;
return new QuickConnectResult();
}
/// <inheritdoc/> /// <inheritdoc/>
public void SetEnabled(QuickConnectState newState) public void SetEnabled(QuickConnectState newState)
{ {
_logger.LogDebug("Changed quick connect state from {0} to {1}", State, newState); _logger.LogDebug("Changed quick connect state from {0} to {1}", State, newState);
State = newState; State = newState;
_config.SaveConfiguration("quickconnect", new QuickConnectConfiguration()
{
State = State
});
_logger.LogDebug("Configuration saved");
} }
/// <inheritdoc/> /// <inheritdoc/>
public QuickConnectResult TryConnect(string friendlyName) public QuickConnectResult TryConnect(string friendlyName)
{ {
ExpireRequests(true);
if (State != QuickConnectState.Active) if (State != QuickConnectState.Active)
{ {
_logger.LogDebug("Refusing quick connect initiation request, current state is {0}", State); _logger.LogDebug("Refusing quick connect initiation request, current state is {0}", State);
@ -122,13 +166,11 @@ namespace Emby.Server.Implementations.QuickConnect
/// <inheritdoc/> /// <inheritdoc/>
public QuickConnectResult CheckRequestStatus(string secret) public QuickConnectResult CheckRequestStatus(string secret)
{ {
AssertActive();
ExpireRequests(); ExpireRequests();
AssertActive();
string lookup = _currentRequests.Where(x => x.Value.Secret == secret).Select(x => x.Value.Lookup).DefaultIfEmpty(string.Empty).First(); string lookup = _currentRequests.Where(x => x.Value.Secret == secret).Select(x => x.Value.Lookup).DefaultIfEmpty(string.Empty).First();
_logger.LogDebug("Transformed private identifier {0} into public lookup {1}", secret, lookup);
if (!_currentRequests.ContainsKey(lookup)) if (!_currentRequests.ContainsKey(lookup))
{ {
throw new KeyNotFoundException("Unable to find request with provided identifier"); throw new KeyNotFoundException("Unable to find request with provided identifier");
@ -146,8 +188,8 @@ namespace Emby.Server.Implementations.QuickConnect
/// <inheritdoc/> /// <inheritdoc/>
public List<QuickConnectResult> GetCurrentRequestsInternal() public List<QuickConnectResult> GetCurrentRequestsInternal()
{ {
AssertActive();
ExpireRequests(); ExpireRequests();
AssertActive();
return _currentRequests.Values.ToList(); return _currentRequests.Values.ToList();
} }
@ -174,12 +216,11 @@ namespace Emby.Server.Implementations.QuickConnect
/// <inheritdoc/> /// <inheritdoc/>
public bool AuthorizeRequest(IRequest request, string lookup) public bool AuthorizeRequest(IRequest request, string lookup)
{ {
ExpireRequests();
AssertActive(); AssertActive();
var auth = _authContext.GetAuthorizationInfo(request); var auth = _authContext.GetAuthorizationInfo(request);
ExpireRequests();
if (!_currentRequests.ContainsKey(lookup)) if (!_currentRequests.ContainsKey(lookup))
{ {
throw new KeyNotFoundException("Unable to find request"); throw new KeyNotFoundException("Unable to find request");
@ -208,6 +249,8 @@ namespace Emby.Server.Implementations.QuickConnect
UserId = auth.UserId UserId = auth.UserId
}); });
_logger.LogInformation("Allowing device {0} to login as user {1} with quick connect code {2}", result.FriendlyName, auth.User.Name, result.Code);
return true; return true;
} }
@ -239,8 +282,21 @@ namespace Emby.Server.Implementations.QuickConnect
return string.Join(string.Empty, bytes.Select(x => x.ToString("x2", CultureInfo.InvariantCulture))); return string.Join(string.Empty, bytes.Select(x => x.ToString("x2", CultureInfo.InvariantCulture)));
} }
private void ExpireRequests() private void ExpireRequests(bool onlyCheckTime = false)
{ {
// check if quick connect should be deactivated
if (TemporaryActivation && DateTime.Now > DateActivated.AddMinutes(10) && State == QuickConnectState.Active)
{
_logger.LogDebug("Quick connect time expired, deactivating");
SetEnabled(QuickConnectState.Available);
}
if (onlyCheckTime)
{
return;
}
// expire stale connection requests
var delete = new List<string>(); var delete = new List<string>();
var values = _currentRequests.Values.ToList(); var values = _currentRequests.Values.ToList();

View File

@ -98,6 +98,11 @@ namespace MediaBrowser.Api.QuickConnect
public object Get(QuickConnectList request) public object Get(QuickConnectList request)
{ {
if(_quickConnect.State != QuickConnectState.Active)
{
return Array.Empty<QuickConnectResultDto>();
}
return _quickConnect.GetCurrentRequests(); return _quickConnect.GetCurrentRequests();
} }
@ -124,15 +129,40 @@ namespace MediaBrowser.Api.QuickConnect
public object Post(Activate request) public object Post(Activate request)
{ {
if (_quickConnect.State == QuickConnectState.Available) string name = _authContext.GetAuthorizationInfo(Request).User.Name;
{
_quickConnect.SetEnabled(QuickConnectState.Active);
string name = _authContext.GetAuthorizationInfo(Request).User.Name; if(_quickConnect.State == QuickConnectState.Unavailable)
Logger.LogInformation("{name} enabled quick connect", name); {
return new QuickConnectResult()
{
Error = "Quick connect is not enabled on this server"
};
} }
return _quickConnect.State; else if(_quickConnect.State == QuickConnectState.Available)
{
var result = _quickConnect.Activate();
if (string.IsNullOrEmpty(result.Error))
{
Logger.LogInformation("{name} temporarily activated quick connect", name);
}
return result;
}
else if(_quickConnect.State == QuickConnectState.Active)
{
return new QuickConnectResult()
{
Error = ""
};
}
return new QuickConnectResult()
{
Error = "Unknown current state"
};
} }
public object Post(Available request) public object Post(Available request)

View File

@ -35,10 +35,16 @@ namespace MediaBrowser.Controller.QuickConnect
/// </summary> /// </summary>
void AssertActive(); void AssertActive();
/// <summary>
/// Temporarily activates quick connect for a short amount of time.
/// </summary>
/// <returns>A quick connect result object indicating success.</returns>
QuickConnectResult Activate();
/// <summary> /// <summary>
/// Changes the status of quick connect. /// Changes the status of quick connect.
/// </summary> /// </summary>
/// <param name="newState">New state to change to</param> /// <param name="newState">New state to change to.</param>
void SetEnabled(QuickConnectState newState); void SetEnabled(QuickConnectState newState);
/// <summary> /// <summary>