From 035d29fb357006c29ffb40e0a53c1e999237cdd1 Mon Sep 17 00:00:00 2001
From: Matt Montgomery <33811686+ConfusedPolarBear@users.noreply.github.com>
Date: Thu, 13 Aug 2020 15:35:04 -0500
Subject: Migrate to new API standard
---
Jellyfin.Api/Controllers/UserController.cs | 41 ++++++++++++++++++++++++++++++
1 file changed, 41 insertions(+)
(limited to 'Jellyfin.Api/Controllers/UserController.cs')
diff --git a/Jellyfin.Api/Controllers/UserController.cs b/Jellyfin.Api/Controllers/UserController.cs
index 272312522..131fffb7a 100644
--- a/Jellyfin.Api/Controllers/UserController.cs
+++ b/Jellyfin.Api/Controllers/UserController.cs
@@ -216,6 +216,47 @@ namespace Jellyfin.Api.Controllers
}
}
+ ///
+ /// Authenticates a user with quick connect.
+ ///
+ /// The request.
+ /// User authenticated.
+ /// Missing token.
+ /// A containing an with information about the new session.
+ [HttpPost("AuthenticateWithQuickConnect")]
+ [ProducesResponseType(StatusCodes.Status200OK)]
+ public async Task> AuthenticateWithQuickConnect([FromBody, Required] QuickConnectDto request)
+ {
+ if (request.Token == null)
+ {
+ return BadRequest("Access token is required.");
+ }
+
+ var auth = _authContext.GetAuthorizationInfo(Request);
+
+ try
+ {
+ var authRequest = new AuthenticationRequest
+ {
+ App = auth.Client,
+ AppVersion = auth.Version,
+ DeviceId = auth.DeviceId,
+ DeviceName = auth.Device,
+ };
+
+ var result = await _sessionManager.AuthenticateQuickConnect(
+ authRequest,
+ request.Token).ConfigureAwait(false);
+
+ return result;
+ }
+ catch (SecurityException e)
+ {
+ // rethrow adding IP address to message
+ throw new SecurityException($"[{HttpContext.Connection.RemoteIpAddress}] {e.Message}", e);
+ }
+ }
+
///
/// Updates a user's password.
///
--
cgit v1.2.3
From eaa57115347f6f70d478f2ca39601d2e70efbdaf Mon Sep 17 00:00:00 2001
From: ConfusedPolarBear <33811686+ConfusedPolarBear@users.noreply.github.com>
Date: Sun, 16 Aug 2020 17:21:08 -0500
Subject: Apply suggestions from code review
Co-authored-by: Cody Robibero
---
Jellyfin.Api/Controllers/QuickConnectController.cs | 15 +++++----------
Jellyfin.Api/Controllers/UserController.cs | 5 -----
Jellyfin.Api/Models/UserDtos/QuickConnectDto.cs | 1 +
3 files changed, 6 insertions(+), 15 deletions(-)
(limited to 'Jellyfin.Api/Controllers/UserController.cs')
diff --git a/Jellyfin.Api/Controllers/QuickConnectController.cs b/Jellyfin.Api/Controllers/QuickConnectController.cs
index d45ea058d..fd5453595 100644
--- a/Jellyfin.Api/Controllers/QuickConnectController.cs
+++ b/Jellyfin.Api/Controllers/QuickConnectController.cs
@@ -46,7 +46,7 @@ namespace Jellyfin.Api.Controllers
public ActionResult GetStatus()
{
_quickConnect.ExpireRequests();
- return Ok(_quickConnect.State);
+ return _quickConnect.State;
}
///
@@ -60,7 +60,7 @@ namespace Jellyfin.Api.Controllers
[ProducesResponseType(StatusCodes.Status200OK)]
public ActionResult Initiate([FromQuery] string? friendlyName)
{
- return Ok(_quickConnect.TryConnect(friendlyName));
+ return _quickConnect.TryConnect(friendlyName);
}
///
@@ -78,7 +78,7 @@ namespace Jellyfin.Api.Controllers
try
{
var result = _quickConnect.CheckRequestStatus(secret);
- return Ok(result);
+ return result;
}
catch (ResourceNotFoundException)
{
@@ -135,12 +135,7 @@ namespace Jellyfin.Api.Controllers
[ProducesResponseType(StatusCodes.Status400BadRequest)]
public ActionResult Authorize([FromQuery, Required] string? code)
{
- if (code == null)
- {
- return BadRequest("Missing code");
- }
-
- return Ok(_quickConnect.AuthorizeRequest(Request, code));
+ return _quickConnect.AuthorizeRequest(Request, code);
}
///
@@ -153,7 +148,7 @@ namespace Jellyfin.Api.Controllers
[ProducesResponseType(StatusCodes.Status200OK)]
public ActionResult Deauthorize()
{
- var userId = _authContext.GetAuthorizationInfo(Request).UserId;
+ var userId = ClaimHelpers.GetUserId(request.HttpContext.User);
return _quickConnect.DeleteAllDevices(userId);
}
}
diff --git a/Jellyfin.Api/Controllers/UserController.cs b/Jellyfin.Api/Controllers/UserController.cs
index 131fffb7a..355816bd3 100644
--- a/Jellyfin.Api/Controllers/UserController.cs
+++ b/Jellyfin.Api/Controllers/UserController.cs
@@ -227,11 +227,6 @@ namespace Jellyfin.Api.Controllers
[ProducesResponseType(StatusCodes.Status200OK)]
public async Task> AuthenticateWithQuickConnect([FromBody, Required] QuickConnectDto request)
{
- if (request.Token == null)
- {
- return BadRequest("Access token is required.");
- }
-
var auth = _authContext.GetAuthorizationInfo(Request);
try
diff --git a/Jellyfin.Api/Models/UserDtos/QuickConnectDto.cs b/Jellyfin.Api/Models/UserDtos/QuickConnectDto.cs
index 8f53d5f37..ac0949732 100644
--- a/Jellyfin.Api/Models/UserDtos/QuickConnectDto.cs
+++ b/Jellyfin.Api/Models/UserDtos/QuickConnectDto.cs
@@ -8,6 +8,7 @@
///
/// Gets or sets the quick connect token.
///
+ [Required]
public string? Token { get; set; }
}
}
--
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 'Jellyfin.Api/Controllers/UserController.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
From 2f33bee2a94f7175050f83d568f8bd65a01a86f8 Mon Sep 17 00:00:00 2001
From: crobibero
Date: Tue, 1 Sep 2020 17:26:49 -0600
Subject: Set openapi schema type to file where possible
---
.../Attributes/ProducesAudioFileAttribute.cs | 18 ++++++++
Jellyfin.Api/Attributes/ProducesFileAttribute.cs | 28 ++++++++++++
.../Attributes/ProducesImageFileAttribute.cs | 18 ++++++++
.../Attributes/ProducesPlaylistFileAttribute.cs | 18 ++++++++
.../Attributes/ProducesVideoFileAttribute.cs | 18 ++++++++
Jellyfin.Api/Controllers/AudioController.cs | 2 +
.../Controllers/ConfigurationController.cs | 3 ++
Jellyfin.Api/Controllers/DashboardController.cs | 3 ++
Jellyfin.Api/Controllers/DlnaServerController.cs | 25 +++++++----
Jellyfin.Api/Controllers/DynamicHlsController.cs | 7 +++
Jellyfin.Api/Controllers/HlsSegmentController.cs | 4 ++
Jellyfin.Api/Controllers/ImageController.cs | 11 ++++-
Jellyfin.Api/Controllers/ItemLookupController.cs | 10 +++--
Jellyfin.Api/Controllers/LibraryController.cs | 3 ++
Jellyfin.Api/Controllers/LiveTvController.cs | 4 ++
Jellyfin.Api/Controllers/MediaInfoController.cs | 2 +
Jellyfin.Api/Controllers/SubtitleController.cs | 4 ++
Jellyfin.Api/Controllers/SystemController.cs | 9 ++--
.../Controllers/UniversalAudioController.cs | 2 +
Jellyfin.Api/Controllers/UserController.cs | 6 +--
Jellyfin.Api/Controllers/VideoHlsController.cs | 2 +
Jellyfin.Api/Controllers/VideosController.cs | 4 +-
.../Extensions/ApiServiceCollectionExtensions.cs | 3 ++
Jellyfin.Server/Filters/FileResponseFilter.cs | 52 ++++++++++++++++++++++
24 files changed, 232 insertions(+), 24 deletions(-)
create mode 100644 Jellyfin.Api/Attributes/ProducesAudioFileAttribute.cs
create mode 100644 Jellyfin.Api/Attributes/ProducesFileAttribute.cs
create mode 100644 Jellyfin.Api/Attributes/ProducesImageFileAttribute.cs
create mode 100644 Jellyfin.Api/Attributes/ProducesPlaylistFileAttribute.cs
create mode 100644 Jellyfin.Api/Attributes/ProducesVideoFileAttribute.cs
create mode 100644 Jellyfin.Server/Filters/FileResponseFilter.cs
(limited to 'Jellyfin.Api/Controllers/UserController.cs')
diff --git a/Jellyfin.Api/Attributes/ProducesAudioFileAttribute.cs b/Jellyfin.Api/Attributes/ProducesAudioFileAttribute.cs
new file mode 100644
index 000000000..3adb700eb
--- /dev/null
+++ b/Jellyfin.Api/Attributes/ProducesAudioFileAttribute.cs
@@ -0,0 +1,18 @@
+namespace Jellyfin.Api.Attributes
+{
+ ///
+ /// Produces file attribute of "image/*".
+ ///
+ public class ProducesAudioFileAttribute : ProducesFileAttribute
+ {
+ private const string ContentType = "audio/*";
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ public ProducesAudioFileAttribute()
+ : base(ContentType)
+ {
+ }
+ }
+}
diff --git a/Jellyfin.Api/Attributes/ProducesFileAttribute.cs b/Jellyfin.Api/Attributes/ProducesFileAttribute.cs
new file mode 100644
index 000000000..62a576ede
--- /dev/null
+++ b/Jellyfin.Api/Attributes/ProducesFileAttribute.cs
@@ -0,0 +1,28 @@
+using System;
+
+namespace Jellyfin.Api.Attributes
+{
+ ///
+ /// Internal produces image attribute.
+ ///
+ [AttributeUsage(AttributeTargets.Method)]
+ public class ProducesFileAttribute : Attribute
+ {
+ private readonly string[] _contentTypes;
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// Content types this endpoint produces.
+ public ProducesFileAttribute(params string[] contentTypes)
+ {
+ _contentTypes = contentTypes;
+ }
+
+ ///
+ /// Gets the configured content types.
+ ///
+ /// the configured content types.
+ public string[] GetContentTypes() => _contentTypes;
+ }
+}
diff --git a/Jellyfin.Api/Attributes/ProducesImageFileAttribute.cs b/Jellyfin.Api/Attributes/ProducesImageFileAttribute.cs
new file mode 100644
index 000000000..e15813676
--- /dev/null
+++ b/Jellyfin.Api/Attributes/ProducesImageFileAttribute.cs
@@ -0,0 +1,18 @@
+namespace Jellyfin.Api.Attributes
+{
+ ///
+ /// Produces file attribute of "image/*".
+ ///
+ public class ProducesImageFileAttribute : ProducesFileAttribute
+ {
+ private const string ContentType = "image/*";
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ public ProducesImageFileAttribute()
+ : base(ContentType)
+ {
+ }
+ }
+}
diff --git a/Jellyfin.Api/Attributes/ProducesPlaylistFileAttribute.cs b/Jellyfin.Api/Attributes/ProducesPlaylistFileAttribute.cs
new file mode 100644
index 000000000..5d928ab91
--- /dev/null
+++ b/Jellyfin.Api/Attributes/ProducesPlaylistFileAttribute.cs
@@ -0,0 +1,18 @@
+namespace Jellyfin.Api.Attributes
+{
+ ///
+ /// Produces file attribute of "image/*".
+ ///
+ public class ProducesPlaylistFileAttribute : ProducesFileAttribute
+ {
+ private const string ContentType = "application/x-mpegURL";
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ public ProducesPlaylistFileAttribute()
+ : base(ContentType)
+ {
+ }
+ }
+}
diff --git a/Jellyfin.Api/Attributes/ProducesVideoFileAttribute.cs b/Jellyfin.Api/Attributes/ProducesVideoFileAttribute.cs
new file mode 100644
index 000000000..d8b2856dc
--- /dev/null
+++ b/Jellyfin.Api/Attributes/ProducesVideoFileAttribute.cs
@@ -0,0 +1,18 @@
+namespace Jellyfin.Api.Attributes
+{
+ ///
+ /// Produces file attribute of "video/*".
+ ///
+ public class ProducesVideoFileAttribute : ProducesFileAttribute
+ {
+ private const string ContentType = "video/*";
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ public ProducesVideoFileAttribute()
+ : base(ContentType)
+ {
+ }
+ }
+}
diff --git a/Jellyfin.Api/Controllers/AudioController.cs b/Jellyfin.Api/Controllers/AudioController.cs
index 802cd026e..48fd198b5 100644
--- a/Jellyfin.Api/Controllers/AudioController.cs
+++ b/Jellyfin.Api/Controllers/AudioController.cs
@@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
+using Jellyfin.Api.Attributes;
using Jellyfin.Api.Helpers;
using Jellyfin.Api.Models.StreamingDtos;
using MediaBrowser.Controller.MediaEncoding;
@@ -88,6 +89,7 @@ namespace Jellyfin.Api.Controllers
[HttpHead("{itemId}/stream.{container}", Name = "HeadAudioStreamByContainer")]
[HttpHead("{itemId}/stream", Name = "HeadAudioStream")]
[ProducesResponseType(StatusCodes.Status200OK)]
+ [ProducesAudioFile]
public async Task GetAudioStream(
[FromRoute] Guid itemId,
[FromRoute] string? container,
diff --git a/Jellyfin.Api/Controllers/ConfigurationController.cs b/Jellyfin.Api/Controllers/ConfigurationController.cs
index 20fb0ec87..606e27970 100644
--- a/Jellyfin.Api/Controllers/ConfigurationController.cs
+++ b/Jellyfin.Api/Controllers/ConfigurationController.cs
@@ -1,6 +1,8 @@
using System.ComponentModel.DataAnnotations;
+using System.Net.Mime;
using System.Text.Json;
using System.Threading.Tasks;
+using Jellyfin.Api.Attributes;
using Jellyfin.Api.Constants;
using Jellyfin.Api.Models.ConfigurationDtos;
using MediaBrowser.Common.Json;
@@ -73,6 +75,7 @@ namespace Jellyfin.Api.Controllers
/// Configuration.
[HttpGet("Configuration/{key}")]
[ProducesResponseType(StatusCodes.Status200OK)]
+ [ProducesFile(MediaTypeNames.Application.Json)]
public ActionResult
/// The user id.
/// The request.
- /// Password successfully reset.
+ /// Password successfully reset.
/// User is not allowed to update the password.
/// User not found.
/// A indicating success or a or a on failure.
diff --git a/Jellyfin.Api/Controllers/VideoHlsController.cs b/Jellyfin.Api/Controllers/VideoHlsController.cs
index 76188f46d..92e82727e 100644
--- a/Jellyfin.Api/Controllers/VideoHlsController.cs
+++ b/Jellyfin.Api/Controllers/VideoHlsController.cs
@@ -4,6 +4,7 @@ using System.Globalization;
using System.IO;
using System.Threading;
using System.Threading.Tasks;
+using Jellyfin.Api.Attributes;
using Jellyfin.Api.Constants;
using Jellyfin.Api.Helpers;
using Jellyfin.Api.Models.PlaybackDtos;
@@ -161,6 +162,7 @@ namespace Jellyfin.Api.Controllers
/// A containing the hls file.
[HttpGet("Videos/{itemId}/live.m3u8")]
[ProducesResponseType(StatusCodes.Status200OK)]
+ [ProducesPlaylistFile]
public async Task GetLiveHlsStream(
[FromRoute] Guid itemId,
[FromQuery] string? container,
diff --git a/Jellyfin.Api/Controllers/VideosController.cs b/Jellyfin.Api/Controllers/VideosController.cs
index f42810c94..18023664b 100644
--- a/Jellyfin.Api/Controllers/VideosController.cs
+++ b/Jellyfin.Api/Controllers/VideosController.cs
@@ -6,6 +6,7 @@ using System.Linq;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
+using Jellyfin.Api.Attributes;
using Jellyfin.Api.Constants;
using Jellyfin.Api.Extensions;
using Jellyfin.Api.Helpers;
@@ -159,7 +160,7 @@ namespace Jellyfin.Api.Controllers
/// A indicating success, or a if the video doesn't exist.
[HttpDelete("{itemId}/AlternateSources")]
[Authorize(Policy = Policies.RequiresElevation)]
- [ProducesResponseType(StatusCodes.Status200OK)]
+ [ProducesResponseType(StatusCodes.Status204NoContent)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
public async Task DeleteAlternateSources([FromRoute] Guid itemId)
{
@@ -326,6 +327,7 @@ namespace Jellyfin.Api.Controllers
[HttpHead("{itemId}/{stream=stream}.{container?}", Name = "HeadVideoStream_2")]
[HttpHead("{itemId}/stream", Name = "HeadVideoStream")]
[ProducesResponseType(StatusCodes.Status200OK)]
+ [ProducesVideoFile]
public async Task GetVideoStream(
[FromRoute] Guid itemId,
[FromRoute] string? container,
diff --git a/Jellyfin.Server/Extensions/ApiServiceCollectionExtensions.cs b/Jellyfin.Server/Extensions/ApiServiceCollectionExtensions.cs
index 0160a05f9..04b611123 100644
--- a/Jellyfin.Server/Extensions/ApiServiceCollectionExtensions.cs
+++ b/Jellyfin.Server/Extensions/ApiServiceCollectionExtensions.cs
@@ -16,6 +16,7 @@ using Jellyfin.Api.Auth.LocalAccessPolicy;
using Jellyfin.Api.Auth.RequiresElevationPolicy;
using Jellyfin.Api.Constants;
using Jellyfin.Api.Controllers;
+using Jellyfin.Server.Filters;
using Jellyfin.Server.Formatters;
using Jellyfin.Server.Models;
using MediaBrowser.Common;
@@ -249,6 +250,8 @@ namespace Jellyfin.Server.Extensions
// TODO - remove when all types are supported in System.Text.Json
c.AddSwaggerTypeMappings();
+
+ c.OperationFilter();
});
}
diff --git a/Jellyfin.Server/Filters/FileResponseFilter.cs b/Jellyfin.Server/Filters/FileResponseFilter.cs
new file mode 100644
index 000000000..8ea35c281
--- /dev/null
+++ b/Jellyfin.Server/Filters/FileResponseFilter.cs
@@ -0,0 +1,52 @@
+using System;
+using System.Linq;
+using Jellyfin.Api.Attributes;
+using Microsoft.OpenApi.Models;
+using Swashbuckle.AspNetCore.SwaggerGen;
+
+namespace Jellyfin.Server.Filters
+{
+ ///
+ public class FileResponseFilter : IOperationFilter
+ {
+ private const string SuccessCode = "200";
+ private static readonly OpenApiMediaType _openApiMediaType = new OpenApiMediaType
+ {
+ Schema = new OpenApiSchema
+ {
+ Type = "file"
+ }
+ };
+
+ ///
+ public void Apply(OpenApiOperation operation, OperationFilterContext context)
+ {
+ foreach (var attribute in context.ApiDescription.ActionDescriptor.EndpointMetadata)
+ {
+ if (attribute is ProducesFileAttribute producesFileAttribute)
+ {
+ // Get operation response values.
+ var (_, value) = operation.Responses
+ .FirstOrDefault(o => o.Key.Equals(SuccessCode, StringComparison.Ordinal));
+
+ // Operation doesn't have a response.
+ if (value == null)
+ {
+ continue;
+ }
+
+ // Clear existing responses.
+ value.Content.Clear();
+
+ // Add all content-types as file.
+ foreach (var contentType in producesFileAttribute.GetContentTypes())
+ {
+ value.Content.Add(contentType, _openApiMediaType);
+ }
+
+ break;
+ }
+ }
+ }
+ }
+}
--
cgit v1.2.3
From 59d47ec3f52d8592f6a2aac405ee214cfda10081 Mon Sep 17 00:00:00 2001
From: crobibero
Date: Sat, 5 Sep 2020 17:07:25 -0600
Subject: Make all FromRoute required
---
Jellyfin.Api/Controllers/AlbumsController.cs | 5 +-
Jellyfin.Api/Controllers/ArtistsController.cs | 3 +-
Jellyfin.Api/Controllers/AudioController.cs | 5 +-
Jellyfin.Api/Controllers/ChannelsController.cs | 4 +-
Jellyfin.Api/Controllers/CollectionController.cs | 4 +-
.../Controllers/ConfigurationController.cs | 4 +-
.../Controllers/DisplayPreferencesController.cs | 4 +-
Jellyfin.Api/Controllers/DlnaController.cs | 6 +-
Jellyfin.Api/Controllers/DlnaServerController.cs | 18 +--
Jellyfin.Api/Controllers/DynamicHlsController.cs | 32 ++---
Jellyfin.Api/Controllers/GenresController.cs | 2 +-
Jellyfin.Api/Controllers/HlsSegmentController.cs | 12 +-
Jellyfin.Api/Controllers/ImageController.cs | 156 ++++++++++-----------
Jellyfin.Api/Controllers/InstantMixController.cs | 12 +-
Jellyfin.Api/Controllers/ItemLookupController.cs | 4 +-
Jellyfin.Api/Controllers/ItemRefreshController.cs | 2 +-
Jellyfin.Api/Controllers/ItemUpdateController.cs | 6 +-
Jellyfin.Api/Controllers/ItemsController.cs | 4 +-
Jellyfin.Api/Controllers/LibraryController.cs | 16 +--
Jellyfin.Api/Controllers/LiveTvController.cs | 26 ++--
Jellyfin.Api/Controllers/MediaInfoController.cs | 4 +-
Jellyfin.Api/Controllers/MusicGenresController.cs | 2 +-
Jellyfin.Api/Controllers/PackageController.cs | 6 +-
Jellyfin.Api/Controllers/PersonsController.cs | 3 +-
Jellyfin.Api/Controllers/PlaylistsController.cs | 28 ++--
Jellyfin.Api/Controllers/PlaystateController.cs | 18 +--
Jellyfin.Api/Controllers/PluginsController.cs | 10 +-
Jellyfin.Api/Controllers/RemoteImageController.cs | 6 +-
.../Controllers/ScheduledTasksController.cs | 2 +-
Jellyfin.Api/Controllers/SessionController.cs | 6 +-
Jellyfin.Api/Controllers/StudiosController.cs | 2 +-
Jellyfin.Api/Controllers/SubtitleController.cs | 16 +--
Jellyfin.Api/Controllers/SuggestionsController.cs | 2 +-
.../Controllers/UniversalAudioController.cs | 4 +-
Jellyfin.Api/Controllers/UserController.cs | 14 +-
Jellyfin.Api/Controllers/UserLibraryController.cs | 20 +--
Jellyfin.Api/Controllers/UserViewsController.cs | 4 +-
Jellyfin.Api/Controllers/VideoHlsController.cs | 2 +-
Jellyfin.Api/Controllers/VideosController.cs | 8 +-
Jellyfin.Api/Controllers/YearsController.cs | 2 +-
40 files changed, 244 insertions(+), 240 deletions(-)
(limited to 'Jellyfin.Api/Controllers/UserController.cs')
diff --git a/Jellyfin.Api/Controllers/AlbumsController.cs b/Jellyfin.Api/Controllers/AlbumsController.cs
index 190d4bd07..9b68d056f 100644
--- a/Jellyfin.Api/Controllers/AlbumsController.cs
+++ b/Jellyfin.Api/Controllers/AlbumsController.cs
@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
+using System.ComponentModel.DataAnnotations;
using System.Linq;
using Jellyfin.Api.Extensions;
using Jellyfin.Api.Helpers;
@@ -52,7 +53,7 @@ namespace Jellyfin.Api.Controllers
[HttpGet("Albums/{albumId}/Similar")]
[ProducesResponseType(StatusCodes.Status200OK)]
public ActionResult> GetSimilarAlbums(
- [FromRoute] string albumId,
+ [FromRoute][Required] string albumId,
[FromQuery] Guid? userId,
[FromQuery] string? excludeArtistIds,
[FromQuery] int? limit)
@@ -84,7 +85,7 @@ namespace Jellyfin.Api.Controllers
[HttpGet("Artists/{artistId}/Similar")]
[ProducesResponseType(StatusCodes.Status200OK)]
public ActionResult> GetSimilarArtists(
- [FromRoute] string artistId,
+ [FromRoute][Required] string artistId,
[FromQuery] Guid? userId,
[FromQuery] string? excludeArtistIds,
[FromQuery] int? limit)
diff --git a/Jellyfin.Api/Controllers/ArtistsController.cs b/Jellyfin.Api/Controllers/ArtistsController.cs
index 3f72830cd..b3dad14f3 100644
--- a/Jellyfin.Api/Controllers/ArtistsController.cs
+++ b/Jellyfin.Api/Controllers/ArtistsController.cs
@@ -1,4 +1,5 @@
using System;
+using System.ComponentModel.DataAnnotations;
using System.Linq;
using Jellyfin.Api.Constants;
using Jellyfin.Api.Extensions;
@@ -469,7 +470,7 @@ namespace Jellyfin.Api.Controllers
/// An containing the artist.
[HttpGet("{name}")]
[ProducesResponseType(StatusCodes.Status200OK)]
- public ActionResult GetArtistByName([FromRoute] string name, [FromQuery] Guid? userId)
+ public ActionResult GetArtistByName([FromRoute][Required] string name, [FromQuery] Guid? userId)
{
var dtoOptions = new DtoOptions().AddClientFields(Request);
diff --git a/Jellyfin.Api/Controllers/AudioController.cs b/Jellyfin.Api/Controllers/AudioController.cs
index 802cd026e..a81efe966 100644
--- a/Jellyfin.Api/Controllers/AudioController.cs
+++ b/Jellyfin.Api/Controllers/AudioController.cs
@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
+using System.ComponentModel.DataAnnotations;
using System.Threading.Tasks;
using Jellyfin.Api.Helpers;
using Jellyfin.Api.Models.StreamingDtos;
@@ -89,8 +90,8 @@ namespace Jellyfin.Api.Controllers
[HttpHead("{itemId}/stream", Name = "HeadAudioStream")]
[ProducesResponseType(StatusCodes.Status200OK)]
public async Task GetAudioStream(
- [FromRoute] Guid itemId,
- [FromRoute] string? container,
+ [FromRoute][Required] Guid itemId,
+ [FromRoute][Required] string? container,
[FromQuery] bool? @static,
[FromQuery] string? @params,
[FromQuery] string? tag,
diff --git a/Jellyfin.Api/Controllers/ChannelsController.cs b/Jellyfin.Api/Controllers/ChannelsController.cs
index bdd7dfd96..93d9a9d00 100644
--- a/Jellyfin.Api/Controllers/ChannelsController.cs
+++ b/Jellyfin.Api/Controllers/ChannelsController.cs
@@ -90,7 +90,7 @@ namespace Jellyfin.Api.Controllers
/// Channel features returned.
/// An containing the channel features.
[HttpGet("{channelId}/Features")]
- public ActionResult GetChannelFeatures([FromRoute] string channelId)
+ public ActionResult GetChannelFeatures([FromRoute][Required] string channelId)
{
return _channelManager.GetChannelFeatures(channelId);
}
@@ -114,7 +114,7 @@ namespace Jellyfin.Api.Controllers
///
[HttpGet("{channelId}/Items")]
public async Task>> GetChannelItems(
- [FromRoute] Guid channelId,
+ [FromRoute][Required] Guid channelId,
[FromQuery] Guid? folderId,
[FromQuery] Guid? userId,
[FromQuery] int? startIndex,
diff --git a/Jellyfin.Api/Controllers/CollectionController.cs b/Jellyfin.Api/Controllers/CollectionController.cs
index c5910d6e8..0b1f655da 100644
--- a/Jellyfin.Api/Controllers/CollectionController.cs
+++ b/Jellyfin.Api/Controllers/CollectionController.cs
@@ -88,7 +88,7 @@ namespace Jellyfin.Api.Controllers
/// A indicating success.
[HttpPost("{collectionId}/Items")]
[ProducesResponseType(StatusCodes.Status204NoContent)]
- public async Task AddToCollection([FromRoute] Guid collectionId, [FromQuery, Required] string? itemIds)
+ public async Task AddToCollection([FromRoute][Required] Guid collectionId, [FromQuery, Required] string? itemIds)
{
await _collectionManager.AddToCollectionAsync(collectionId, RequestHelpers.GetGuids(itemIds)).ConfigureAwait(true);
return NoContent();
@@ -103,7 +103,7 @@ namespace Jellyfin.Api.Controllers
/// A indicating success.
[HttpDelete("{collectionId}/Items")]
[ProducesResponseType(StatusCodes.Status204NoContent)]
- public async Task RemoveFromCollection([FromRoute] Guid collectionId, [FromQuery, Required] string? itemIds)
+ public async Task RemoveFromCollection([FromRoute][Required] Guid collectionId, [FromQuery, Required] string? itemIds)
{
await _collectionManager.RemoveFromCollectionAsync(collectionId, RequestHelpers.GetGuids(itemIds)).ConfigureAwait(false);
return NoContent();
diff --git a/Jellyfin.Api/Controllers/ConfigurationController.cs b/Jellyfin.Api/Controllers/ConfigurationController.cs
index 20fb0ec87..f13b6d38d 100644
--- a/Jellyfin.Api/Controllers/ConfigurationController.cs
+++ b/Jellyfin.Api/Controllers/ConfigurationController.cs
@@ -73,7 +73,7 @@ namespace Jellyfin.Api.Controllers
/// Configuration.
[HttpGet("Configuration/{key}")]
[ProducesResponseType(StatusCodes.Status200OK)]
- public ActionResult