@ -7,6 +7,7 @@ using System.Threading.Tasks;
using MediaBrowser.Common.Extensions ;
using MediaBrowser.Controller.Authentication ;
using MediaBrowser.Controller.Configuration ;
using MediaBrowser.Controller.Net ;
using MediaBrowser.Controller.QuickConnect ;
using MediaBrowser.Controller.Session ;
using MediaBrowser.Model.QuickConnect ;
@ -29,8 +30,9 @@ namespace Emby.Server.Implementations.QuickConnect
/// </summary>
private const int Timeout = 10 ;
private readonly RNGCryptoServiceProvider _rng = new ( ) ;
private readonly ConcurrentDictionary < string , QuickConnectResult > _currentRequests = new ( ) ;
private readonly RNGCryptoServiceProvider _rng = new ( ) ;
private readonly ConcurrentDictionary < string , QuickConnectResult > _currentRequests = new ( ) ;
private readonly ConcurrentDictionary < string , ( DateTime Timestamp , AuthenticationResult AuthenticationResult ) > _authorizedSecrets = new ( ) ;
private readonly IServerConfigurationManager _config ;
private readonly ILogger < QuickConnectManager > _logger ;
@ -68,14 +70,41 @@ namespace Emby.Server.Implementations.QuickConnect
}
/// <inheritdoc/>
public QuickConnectResult TryConnect ( )
public QuickConnectResult TryConnect ( AuthorizationInfo authorizationInfo )
{
if ( string . IsNullOrEmpty ( authorizationInfo . DeviceId ) )
{
throw new ArgumentException ( nameof ( authorizationInfo . DeviceId ) + " is required" ) ;
}
if ( string . IsNullOrEmpty ( authorizationInfo . Device ) )
{
throw new ArgumentException ( nameof ( authorizationInfo . Device ) + " is required" ) ;
}
if ( string . IsNullOrEmpty ( authorizationInfo . Client ) )
{
throw new ArgumentException ( nameof ( authorizationInfo . Client ) + " is required" ) ;
}
if ( string . IsNullOrEmpty ( authorizationInfo . Version ) )
{
throw new ArgumentException ( nameof ( authorizationInfo . Version ) + "is required" ) ;
}
AssertActive ( ) ;
ExpireRequests ( ) ;
var secret = GenerateSecureRandom ( ) ;
var code = GenerateCode ( ) ;
var result = new QuickConnectResult ( secret , code , DateTime . UtcNow ) ;
var result = new QuickConnectResult (
secret ,
code ,
DateTime . UtcNow ,
authorizationInfo . DeviceId ,
authorizationInfo . Device ,
authorizationInfo . Client ,
authorizationInfo . Version ) ;
_currentRequests [ code ] = result ;
return result ;
@ -135,19 +164,41 @@ namespace Emby.Server.Implementations.QuickConnect
throw new InvalidOperationException ( "Request is already authorized" ) ;
}
var token = Guid . NewGuid ( ) ;
result . Authentication = token ;
// 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.
result . DateAdded = DateTime . Now. Add ( TimeSpan . FromMinutes ( 1 ) ) ;
result . DateAdded = DateTime . UtcNow . Add ( TimeSpan . FromMinutes ( 1 ) ) ;
await _sessionManager . AuthenticateQuickConnect ( userId ) . ConfigureAwait ( false ) ;
var authenticationResult = await _sessionManager . AuthenticateDirect ( new AuthenticationRequest
{
UserId = userId ,
DeviceId = result . DeviceId ,
DeviceName = result . DeviceName ,
App = result . AppName ,
AppVersion = result . AppVersion
} ) . ConfigureAwait ( false ) ;
_authorizedSecrets [ result . Secret ] = ( DateTime . UtcNow , authenticationResult ) ;
result . Authenticated = true ;
_currentRequests [ code ] = result ;
_logger . LogDebug ( "Authorizing device with code {Code} to login as user {userId}" , code , userId ) ;
_logger . LogDebug ( "Authorizing device with code {Code} to login as user { U serId}", code , userId ) ;
return true ;
}
/// <inheritdoc/>
public AuthenticationResult GetAuthorizedRequest ( string secret )
{
AssertActive ( ) ;
ExpireRequests ( ) ;
if ( ! _authorizedSecrets . TryGetValue ( secret , out var result ) )
{
throw new ResourceNotFoundException ( "Unable to find request" ) ;
}
return result . AuthenticationResult ;
}
/// <summary>
/// Dispose.
/// </summary>
@ -189,7 +240,7 @@ namespace Emby.Server.Implementations.QuickConnect
// Expire stale connection requests
foreach ( var ( _ , currentRequest ) in _currentRequests )
{
if ( expireAll | | currentRequest . DateAdded > minTime )
if ( expireAll | | currentRequest . DateAdded < minTime )
{
var code = currentRequest . Code ;
_logger . LogDebug ( "Removing expired request {Code}" , code ) ;
@ -200,6 +251,18 @@ namespace Emby.Server.Implementations.QuickConnect
}
}
}
foreach ( var ( secret , ( timestamp , _ ) ) in _authorizedSecrets )
{
if ( expireAll | | timestamp < minTime )
{
_logger . LogDebug ( "Removing expired secret {Secret}" , secret ) ;
if ( ! _authorizedSecrets . TryRemove ( secret , out _ ) )
{
_logger . LogWarning ( "Secret {Secret} already expired" , secret ) ;
}
}
}
}
}
}