diff --git a/Emby.Server.Implementations/QuickConnect/QuickConnectManager.cs b/Emby.Server.Implementations/QuickConnect/QuickConnectManager.cs
index 949c3b5058..52e934229a 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 8a8223ee7f..fbe8e065c0 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 1625bcffe7..b1ee2ff53b 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 355816bd32..d67f82219a 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 fd7e973f67..959a2d7712 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 a10d60d57e..0fa40b6a72 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.
///