diff --git a/Jellyfin.Api/Auth/FirstTimeOrIgnoreParentalControlSetupPolicy/FirstTimeOrIgnoreParentalControlSetupHandler.cs b/Jellyfin.Api/Auth/FirstTimeOrIgnoreParentalControlSetupPolicy/FirstTimeOrIgnoreParentalControlSetupHandler.cs
new file mode 100644
index 0000000000..2a02f8bc71
--- /dev/null
+++ b/Jellyfin.Api/Auth/FirstTimeOrIgnoreParentalControlSetupPolicy/FirstTimeOrIgnoreParentalControlSetupHandler.cs
@@ -0,0 +1,57 @@
+using System.Threading.Tasks;
+using Jellyfin.Api.Auth.IgnoreParentalControlPolicy;
+using MediaBrowser.Common.Configuration;
+using MediaBrowser.Common.Net;
+using MediaBrowser.Controller.Library;
+using Microsoft.AspNetCore.Authorization;
+using Microsoft.AspNetCore.Http;
+
+namespace Jellyfin.Api.Auth.FirstTimeOrIgnoreParentalControlSetupPolicy
+{
+ ///
+ /// Ignore parental control schedule and allow before startup wizard has been completed.
+ ///
+ public class FirstTimeOrIgnoreParentalControlSetupHandler : BaseAuthorizationHandler
+ {
+ private readonly IConfigurationManager _configurationManager;
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// Instance of the interface.
+ /// Instance of the interface.
+ /// Instance of the interface.
+ /// Instance of the interface.
+ public FirstTimeOrIgnoreParentalControlSetupHandler(
+ IUserManager userManager,
+ INetworkManager networkManager,
+ IHttpContextAccessor httpContextAccessor,
+ IConfigurationManager configurationManager)
+ : base(userManager, networkManager, httpContextAccessor)
+ {
+ _configurationManager = configurationManager;
+ }
+
+ ///
+ protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, IgnoreParentalControlRequirement requirement)
+ {
+ if (!_configurationManager.CommonConfiguration.IsStartupWizardCompleted)
+ {
+ context.Succeed(requirement);
+ return Task.CompletedTask;
+ }
+
+ var validated = ValidateClaims(context.User, ignoreSchedule: true);
+ if (validated)
+ {
+ context.Succeed(requirement);
+ }
+ else
+ {
+ context.Fail();
+ }
+
+ return Task.CompletedTask;
+ }
+ }
+}
diff --git a/Jellyfin.Api/Auth/FirstTimeOrIgnoreParentalControlSetupPolicy/FirstTimeOrIgnoreParentalControlSetupRequirement.cs b/Jellyfin.Api/Auth/FirstTimeOrIgnoreParentalControlSetupPolicy/FirstTimeOrIgnoreParentalControlSetupRequirement.cs
new file mode 100644
index 0000000000..00aaec334b
--- /dev/null
+++ b/Jellyfin.Api/Auth/FirstTimeOrIgnoreParentalControlSetupPolicy/FirstTimeOrIgnoreParentalControlSetupRequirement.cs
@@ -0,0 +1,11 @@
+using Microsoft.AspNetCore.Authorization;
+
+namespace Jellyfin.Api.Auth.FirstTimeOrIgnoreParentalControlSetupPolicy
+{
+ ///
+ /// First time setup or ignore parental controls requirement.
+ ///
+ public class FirstTimeOrIgnoreParentalControlSetupRequirement : IAuthorizationRequirement
+ {
+ }
+}
diff --git a/Jellyfin.Api/Auth/FirstTimeSetupOrDefaultPolicy/FirstTimeSetupOrDefaultHandler.cs b/Jellyfin.Api/Auth/FirstTimeSetupOrDefaultPolicy/FirstTimeSetupOrDefaultHandler.cs
new file mode 100644
index 0000000000..9815e252ee
--- /dev/null
+++ b/Jellyfin.Api/Auth/FirstTimeSetupOrDefaultPolicy/FirstTimeSetupOrDefaultHandler.cs
@@ -0,0 +1,56 @@
+using System.Threading.Tasks;
+using MediaBrowser.Common.Configuration;
+using MediaBrowser.Common.Net;
+using MediaBrowser.Controller.Library;
+using Microsoft.AspNetCore.Authorization;
+using Microsoft.AspNetCore.Http;
+
+namespace Jellyfin.Api.Auth.FirstTimeSetupOrDefaultPolicy
+{
+ ///
+ /// Authorization handler for requiring first time setup or default privileges.
+ ///
+ public class FirstTimeSetupOrDefaultHandler : BaseAuthorizationHandler
+ {
+ private readonly IConfigurationManager _configurationManager;
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// Instance of the interface.
+ /// Instance of the interface.
+ /// Instance of the interface.
+ /// Instance of the interface.
+ public FirstTimeSetupOrDefaultHandler(
+ IConfigurationManager configurationManager,
+ IUserManager userManager,
+ INetworkManager networkManager,
+ IHttpContextAccessor httpContextAccessor)
+ : base(userManager, networkManager, httpContextAccessor)
+ {
+ _configurationManager = configurationManager;
+ }
+
+ ///
+ protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, FirstTimeSetupOrDefaultRequirement firstTimeSetupOrDefaultRequirement)
+ {
+ if (!_configurationManager.CommonConfiguration.IsStartupWizardCompleted)
+ {
+ context.Succeed(firstTimeSetupOrDefaultRequirement);
+ return Task.CompletedTask;
+ }
+
+ var validated = ValidateClaims(context.User);
+ if (validated)
+ {
+ context.Succeed(firstTimeSetupOrDefaultRequirement);
+ }
+ else
+ {
+ context.Fail();
+ }
+
+ return Task.CompletedTask;
+ }
+ }
+}
diff --git a/Jellyfin.Api/Auth/FirstTimeSetupOrDefaultPolicy/FirstTimeSetupOrDefaultRequirement.cs b/Jellyfin.Api/Auth/FirstTimeSetupOrDefaultPolicy/FirstTimeSetupOrDefaultRequirement.cs
new file mode 100644
index 0000000000..f7366bd7a9
--- /dev/null
+++ b/Jellyfin.Api/Auth/FirstTimeSetupOrDefaultPolicy/FirstTimeSetupOrDefaultRequirement.cs
@@ -0,0 +1,11 @@
+using Microsoft.AspNetCore.Authorization;
+
+namespace Jellyfin.Api.Auth.FirstTimeSetupOrDefaultPolicy
+{
+ ///
+ /// The authorization requirement, requiring incomplete first time setup or default privileges, for the authorization handler.
+ ///
+ public class FirstTimeSetupOrDefaultRequirement : IAuthorizationRequirement
+ {
+ }
+}
diff --git a/Jellyfin.Api/Auth/IgnoreSchedulePolicy/IgnoreScheduleHandler.cs b/Jellyfin.Api/Auth/IgnoreParentalControlPolicy/IgnoreParentalControlHandler.cs
similarity index 77%
rename from Jellyfin.Api/Auth/IgnoreSchedulePolicy/IgnoreScheduleHandler.cs
rename to Jellyfin.Api/Auth/IgnoreParentalControlPolicy/IgnoreParentalControlHandler.cs
index 9afa0b28f1..5213bc4cb7 100644
--- a/Jellyfin.Api/Auth/IgnoreSchedulePolicy/IgnoreScheduleHandler.cs
+++ b/Jellyfin.Api/Auth/IgnoreParentalControlPolicy/IgnoreParentalControlHandler.cs
@@ -4,20 +4,20 @@ using MediaBrowser.Controller.Library;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
-namespace Jellyfin.Api.Auth.IgnoreSchedulePolicy
+namespace Jellyfin.Api.Auth.IgnoreParentalControlPolicy
{
///
/// Escape schedule controls handler.
///
- public class IgnoreScheduleHandler : BaseAuthorizationHandler
+ public class IgnoreParentalControlHandler : BaseAuthorizationHandler
{
///
- /// Initializes a new instance of the class.
+ /// Initializes a new instance of the class.
///
/// Instance of the interface.
/// Instance of the interface.
/// Instance of the interface.
- public IgnoreScheduleHandler(
+ public IgnoreParentalControlHandler(
IUserManager userManager,
INetworkManager networkManager,
IHttpContextAccessor httpContextAccessor)
@@ -26,7 +26,7 @@ namespace Jellyfin.Api.Auth.IgnoreSchedulePolicy
}
///
- protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, IgnoreScheduleRequirement requirement)
+ protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, IgnoreParentalControlRequirement requirement)
{
var validated = ValidateClaims(context.User, ignoreSchedule: true);
if (!validated)
diff --git a/Jellyfin.Api/Auth/IgnoreSchedulePolicy/IgnoreScheduleRequirement.cs b/Jellyfin.Api/Auth/IgnoreParentalControlPolicy/IgnoreParentalControlRequirement.cs
similarity index 51%
rename from Jellyfin.Api/Auth/IgnoreSchedulePolicy/IgnoreScheduleRequirement.cs
rename to Jellyfin.Api/Auth/IgnoreParentalControlPolicy/IgnoreParentalControlRequirement.cs
index d5bb61ce6c..cdad74270e 100644
--- a/Jellyfin.Api/Auth/IgnoreSchedulePolicy/IgnoreScheduleRequirement.cs
+++ b/Jellyfin.Api/Auth/IgnoreParentalControlPolicy/IgnoreParentalControlRequirement.cs
@@ -1,11 +1,11 @@
using Microsoft.AspNetCore.Authorization;
-namespace Jellyfin.Api.Auth.IgnoreSchedulePolicy
+namespace Jellyfin.Api.Auth.IgnoreParentalControlPolicy
{
///
/// Escape schedule controls requirement.
///
- public class IgnoreScheduleRequirement : IAuthorizationRequirement
+ public class IgnoreParentalControlRequirement : IAuthorizationRequirement
{
}
}
diff --git a/Jellyfin.Api/Auth/LocalAccessOrRequiresElevationPolicy/LocalAccessOrRequiresElevationHandler.cs b/Jellyfin.Api/Auth/LocalAccessOrRequiresElevationPolicy/LocalAccessOrRequiresElevationHandler.cs
new file mode 100644
index 0000000000..14722aa57e
--- /dev/null
+++ b/Jellyfin.Api/Auth/LocalAccessOrRequiresElevationPolicy/LocalAccessOrRequiresElevationHandler.cs
@@ -0,0 +1,45 @@
+using System.Threading.Tasks;
+using Jellyfin.Api.Constants;
+using MediaBrowser.Common.Net;
+using MediaBrowser.Controller.Library;
+using Microsoft.AspNetCore.Authorization;
+using Microsoft.AspNetCore.Http;
+
+namespace Jellyfin.Api.Auth.LocalAccessOrRequiresElevationPolicy
+{
+ ///
+ /// Local access or require elevated privileges handler.
+ ///
+ public class LocalAccessOrRequiresElevationHandler : BaseAuthorizationHandler
+ {
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// Instance of the interface.
+ /// Instance of the interface.
+ /// Instance of the interface.
+ public LocalAccessOrRequiresElevationHandler(
+ IUserManager userManager,
+ INetworkManager networkManager,
+ IHttpContextAccessor httpContextAccessor)
+ : base(userManager, networkManager, httpContextAccessor)
+ {
+ }
+
+ ///
+ protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, LocalAccessOrRequiresElevationRequirement requirement)
+ {
+ var validated = ValidateClaims(context.User, localAccessOnly: true);
+ if (validated || context.User.IsInRole(UserRoles.Administrator))
+ {
+ context.Succeed(requirement);
+ }
+ else
+ {
+ context.Fail();
+ }
+
+ return Task.CompletedTask;
+ }
+ }
+}
diff --git a/Jellyfin.Api/Auth/LocalAccessOrRequiresElevationPolicy/LocalAccessOrRequiresElevationRequirement.cs b/Jellyfin.Api/Auth/LocalAccessOrRequiresElevationPolicy/LocalAccessOrRequiresElevationRequirement.cs
new file mode 100644
index 0000000000..d9c64d01c4
--- /dev/null
+++ b/Jellyfin.Api/Auth/LocalAccessOrRequiresElevationPolicy/LocalAccessOrRequiresElevationRequirement.cs
@@ -0,0 +1,11 @@
+using Microsoft.AspNetCore.Authorization;
+
+namespace Jellyfin.Api.Auth.LocalAccessOrRequiresElevationPolicy
+{
+ ///
+ /// The local access or elevated privileges authorization requirement.
+ ///
+ public class LocalAccessOrRequiresElevationRequirement : IAuthorizationRequirement
+ {
+ }
+}
diff --git a/Jellyfin.Api/Constants/Policies.cs b/Jellyfin.Api/Constants/Policies.cs
index 851b56d732..7d77674700 100644
--- a/Jellyfin.Api/Constants/Policies.cs
+++ b/Jellyfin.Api/Constants/Policies.cs
@@ -13,7 +13,7 @@ namespace Jellyfin.Api.Constants
///
/// Policy name for requiring first time setup or elevated privileges.
///
- public const string FirstTimeSetupOrElevated = "FirstTimeOrElevated";
+ public const string FirstTimeSetupOrElevated = "FirstTimeSetupOrElevated";
///
/// Policy name for requiring elevated privileges.
@@ -28,11 +28,26 @@ namespace Jellyfin.Api.Constants
///
/// Policy name for escaping schedule controls.
///
- public const string IgnoreSchedule = "IgnoreSchedule";
+ public const string IgnoreParentalControl = "IgnoreParentalControl";
///
/// Policy name for requiring download permission.
///
public const string Download = "Download";
+
+ ///
+ /// Policy name for requiring first time setup or default permissions.
+ ///
+ public const string FirstTimeSetupOrDefault = "FirstTimeSetupOrDefault";
+
+ ///
+ /// Policy name for requiring local access or elevated privileges.
+ ///
+ public const string LocalAccessOrRequiresElevation = "LocalAccessOrRequiresElevation";
+
+ ///
+ /// Policy name for escaping schedule controls or requiring first time setup.
+ ///
+ public const string FirstTimeSetupOrIgnoreParentalControl = "FirstTimeSetupOrIgnoreParentalControl";
}
}
diff --git a/Jellyfin.Api/Controllers/ApiKeyController.cs b/Jellyfin.Api/Controllers/ApiKeyController.cs
index ccb7f47f08..0e28d4c474 100644
--- a/Jellyfin.Api/Controllers/ApiKeyController.cs
+++ b/Jellyfin.Api/Controllers/ApiKeyController.cs
@@ -88,7 +88,7 @@ namespace Jellyfin.Api.Controllers
[HttpDelete("Keys/{key}")]
[Authorize(Policy = Policies.RequiresElevation)]
[ProducesResponseType(StatusCodes.Status204NoContent)]
- public ActionResult RevokeKey([FromRoute] string? key)
+ public ActionResult RevokeKey([FromRoute, Required] string? key)
{
_sessionManager.RevokeToken(key);
return NoContent();
diff --git a/Jellyfin.Api/Controllers/CollectionController.cs b/Jellyfin.Api/Controllers/CollectionController.cs
index b63fc5ab19..53821a1885 100644
--- a/Jellyfin.Api/Controllers/CollectionController.cs
+++ b/Jellyfin.Api/Controllers/CollectionController.cs
@@ -1,4 +1,5 @@
using System;
+using System.ComponentModel.DataAnnotations;
using Jellyfin.Api.Constants;
using Jellyfin.Api.Extensions;
using Jellyfin.Api.Helpers;
@@ -86,7 +87,7 @@ namespace Jellyfin.Api.Controllers
/// A indicating success.
[HttpPost("{collectionId}/Items")]
[ProducesResponseType(StatusCodes.Status204NoContent)]
- public ActionResult AddToCollection([FromRoute] Guid collectionId, [FromQuery] string? itemIds)
+ public ActionResult AddToCollection([FromRoute] Guid collectionId, [FromQuery, Required] string? itemIds)
{
_collectionManager.AddToCollection(collectionId, RequestHelpers.Split(itemIds, ',', true));
return NoContent();
@@ -101,7 +102,7 @@ namespace Jellyfin.Api.Controllers
/// A indicating success.
[HttpDelete("{collectionId}/Items")]
[ProducesResponseType(StatusCodes.Status204NoContent)]
- public ActionResult RemoveFromCollection([FromRoute] Guid collectionId, [FromQuery] string? itemIds)
+ public ActionResult RemoveFromCollection([FromRoute] Guid collectionId, [FromQuery, Required] string? itemIds)
{
_collectionManager.RemoveFromCollection(collectionId, RequestHelpers.Split(itemIds, ',', true));
return NoContent();
diff --git a/Jellyfin.Api/Controllers/ConfigurationController.cs b/Jellyfin.Api/Controllers/ConfigurationController.cs
index 7d262ed595..019703dae9 100644
--- a/Jellyfin.Api/Controllers/ConfigurationController.cs
+++ b/Jellyfin.Api/Controllers/ConfigurationController.cs
@@ -1,3 +1,4 @@
+using System.ComponentModel.DataAnnotations;
using System.Text.Json;
using System.Threading.Tasks;
using Jellyfin.Api.Constants;
@@ -9,7 +10,6 @@ using MediaBrowser.Model.Configuration;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
-using Microsoft.AspNetCore.Mvc.ModelBinding;
namespace Jellyfin.Api.Controllers
{
@@ -59,7 +59,7 @@ namespace Jellyfin.Api.Controllers
[HttpPost("Configuration")]
[Authorize(Policy = Policies.RequiresElevation)]
[ProducesResponseType(StatusCodes.Status204NoContent)]
- public ActionResult UpdateConfiguration([FromBody, BindRequired] ServerConfiguration configuration)
+ public ActionResult UpdateConfiguration([FromBody, Required] ServerConfiguration configuration)
{
_configurationManager.ReplaceConfiguration(configuration);
return NoContent();
@@ -117,7 +117,7 @@ namespace Jellyfin.Api.Controllers
[HttpPost("MediaEncoder/Path")]
[Authorize(Policy = Policies.FirstTimeSetupOrElevated)]
[ProducesResponseType(StatusCodes.Status204NoContent)]
- public ActionResult UpdateMediaEncoderPath([FromForm, BindRequired] MediaEncoderPathDto mediaEncoderPath)
+ public ActionResult UpdateMediaEncoderPath([FromForm, Required] MediaEncoderPathDto mediaEncoderPath)
{
_mediaEncoder.UpdateEncoderPath(mediaEncoderPath.Path, mediaEncoderPath.PathType);
return NoContent();
diff --git a/Jellyfin.Api/Controllers/DevicesController.cs b/Jellyfin.Api/Controllers/DevicesController.cs
index 3cf7b33785..23d10e2156 100644
--- a/Jellyfin.Api/Controllers/DevicesController.cs
+++ b/Jellyfin.Api/Controllers/DevicesController.cs
@@ -1,4 +1,5 @@
using System;
+using System.ComponentModel.DataAnnotations;
using Jellyfin.Api.Constants;
using MediaBrowser.Controller.Devices;
using MediaBrowser.Controller.Security;
@@ -8,7 +9,6 @@ using MediaBrowser.Model.Querying;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
-using Microsoft.AspNetCore.Mvc.ModelBinding;
namespace Jellyfin.Api.Controllers
{
@@ -48,7 +48,7 @@ namespace Jellyfin.Api.Controllers
[HttpGet]
[Authorize(Policy = Policies.RequiresElevation)]
[ProducesResponseType(StatusCodes.Status200OK)]
- public ActionResult> GetDevices([FromQuery] bool? supportsSync, [FromQuery] Guid? userId)
+ public ActionResult> GetDevices([FromQuery] bool? supportsSync, [FromQuery, Required] Guid? userId)
{
var deviceQuery = new DeviceQuery { SupportsSync = supportsSync, UserId = userId ?? Guid.Empty };
return _deviceManager.GetDevices(deviceQuery);
@@ -65,7 +65,7 @@ namespace Jellyfin.Api.Controllers
[Authorize(Policy = Policies.RequiresElevation)]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
- public ActionResult GetDeviceInfo([FromQuery, BindRequired] string? id)
+ public ActionResult GetDeviceInfo([FromQuery, Required] string? id)
{
var deviceInfo = _deviceManager.GetDevice(id);
if (deviceInfo == null)
@@ -87,7 +87,7 @@ namespace Jellyfin.Api.Controllers
[Authorize(Policy = Policies.RequiresElevation)]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
- public ActionResult GetDeviceOptions([FromQuery, BindRequired] string? id)
+ public ActionResult GetDeviceOptions([FromQuery, Required] string? id)
{
var deviceInfo = _deviceManager.GetDeviceOptions(id);
if (deviceInfo == null)
@@ -111,8 +111,8 @@ namespace Jellyfin.Api.Controllers
[ProducesResponseType(StatusCodes.Status204NoContent)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
public ActionResult UpdateDeviceOptions(
- [FromQuery, BindRequired] string? id,
- [FromBody, BindRequired] DeviceOptions deviceOptions)
+ [FromQuery, Required] string? id,
+ [FromBody, Required] DeviceOptions deviceOptions)
{
var existingDeviceOptions = _deviceManager.GetDeviceOptions(id);
if (existingDeviceOptions == null)
@@ -134,7 +134,7 @@ namespace Jellyfin.Api.Controllers
[HttpDelete]
[ProducesResponseType(StatusCodes.Status204NoContent)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
- public ActionResult DeleteDevice([FromQuery, BindRequired] string? id)
+ public ActionResult DeleteDevice([FromQuery, Required] string? id)
{
var existingDevice = _deviceManager.GetDevice(id);
if (existingDevice == null)
diff --git a/Jellyfin.Api/Controllers/DisplayPreferencesController.cs b/Jellyfin.Api/Controllers/DisplayPreferencesController.cs
index 62f6097f36..c547d0cde3 100644
--- a/Jellyfin.Api/Controllers/DisplayPreferencesController.cs
+++ b/Jellyfin.Api/Controllers/DisplayPreferencesController.cs
@@ -11,7 +11,6 @@ using MediaBrowser.Model.Entities;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
-using Microsoft.AspNetCore.Mvc.ModelBinding;
namespace Jellyfin.Api.Controllers
{
@@ -99,9 +98,9 @@ namespace Jellyfin.Api.Controllers
[SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "displayPreferencesId", Justification = "Imported from ServiceStack")]
public ActionResult UpdateDisplayPreferences(
[FromRoute] string? displayPreferencesId,
- [FromQuery, BindRequired] Guid userId,
- [FromQuery, BindRequired] string? client,
- [FromBody, BindRequired] DisplayPreferencesDto displayPreferences)
+ [FromQuery, Required] Guid userId,
+ [FromQuery, Required] string? client,
+ [FromBody, Required] DisplayPreferencesDto displayPreferences)
{
HomeSectionType[] defaults =
{
diff --git a/Jellyfin.Api/Controllers/EnvironmentController.cs b/Jellyfin.Api/Controllers/EnvironmentController.cs
index 719bb7d86d..64670f7d84 100644
--- a/Jellyfin.Api/Controllers/EnvironmentController.cs
+++ b/Jellyfin.Api/Controllers/EnvironmentController.cs
@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
+using System.ComponentModel.DataAnnotations;
using System.IO;
using System.Linq;
using Jellyfin.Api.Constants;
@@ -8,7 +9,6 @@ using MediaBrowser.Model.IO;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
-using Microsoft.AspNetCore.Mvc.ModelBinding;
using Microsoft.Extensions.Logging;
namespace Jellyfin.Api.Controllers
@@ -47,7 +47,7 @@ namespace Jellyfin.Api.Controllers
[HttpGet("DirectoryContents")]
[ProducesResponseType(StatusCodes.Status200OK)]
public IEnumerable GetDirectoryContents(
- [FromQuery, BindRequired] string path,
+ [FromQuery, Required] string path,
[FromQuery] bool includeFiles = false,
[FromQuery] bool includeDirectories = false)
{
@@ -75,7 +75,7 @@ namespace Jellyfin.Api.Controllers
[HttpPost("ValidatePath")]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
- public ActionResult ValidatePath([FromBody, BindRequired] ValidatePathDto validatePathDto)
+ public ActionResult ValidatePath([FromBody, Required] ValidatePathDto validatePathDto)
{
if (validatePathDto.IsFile.HasValue)
{
@@ -154,7 +154,7 @@ namespace Jellyfin.Api.Controllers
/// Parent path.
[HttpGet("ParentPath")]
[ProducesResponseType(StatusCodes.Status200OK)]
- public ActionResult GetParentPath([FromQuery, BindRequired] string path)
+ public ActionResult GetParentPath([FromQuery, Required] string path)
{
string? parent = Path.GetDirectoryName(path);
if (string.IsNullOrEmpty(parent))
diff --git a/Jellyfin.Api/Controllers/ImageByNameController.cs b/Jellyfin.Api/Controllers/ImageByNameController.cs
index 5244c35b89..5285905365 100644
--- a/Jellyfin.Api/Controllers/ImageByNameController.cs
+++ b/Jellyfin.Api/Controllers/ImageByNameController.cs
@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
+using System.ComponentModel.DataAnnotations;
using System.IO;
using System.Linq;
using System.Net.Mime;
@@ -64,7 +65,7 @@ namespace Jellyfin.Api.Controllers
[Produces(MediaTypeNames.Application.Octet)]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
- public ActionResult GetGeneralImage([FromRoute] string? name, [FromRoute] string? type)
+ public ActionResult GetGeneralImage([FromRoute, Required] string? name, [FromRoute, Required] string? type)
{
var filename = string.Equals(type, "primary", StringComparison.OrdinalIgnoreCase)
? "folder"
@@ -110,8 +111,8 @@ namespace Jellyfin.Api.Controllers
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
public ActionResult GetRatingImage(
- [FromRoute] string? theme,
- [FromRoute] string? name)
+ [FromRoute, Required] string? theme,
+ [FromRoute, Required] string? name)
{
return GetImageFile(_applicationPaths.RatingsPath, theme, name);
}
@@ -143,8 +144,8 @@ namespace Jellyfin.Api.Controllers
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
public ActionResult GetMediaInfoImage(
- [FromRoute] string? theme,
- [FromRoute] string? name)
+ [FromRoute, Required] string? theme,
+ [FromRoute, Required] string? name)
{
return GetImageFile(_applicationPaths.MediaInfoImagesPath, theme, name);
}
diff --git a/Jellyfin.Api/Controllers/ImageController.cs b/Jellyfin.Api/Controllers/ImageController.cs
index 360164ad4f..45447ae0cc 100644
--- a/Jellyfin.Api/Controllers/ImageController.cs
+++ b/Jellyfin.Api/Controllers/ImageController.cs
@@ -84,6 +84,7 @@ namespace Jellyfin.Api.Controllers
/// A .
[HttpPost("Users/{userId}/Images/{imageType}")]
[HttpPost("Users/{userId}/Images/{imageType}/{index?}", Name = "PostUserImage_2")]
+ [Authorize(Policy = Policies.DefaultAuthorization)]
[ProducesResponseType(StatusCodes.Status204NoContent)]
[ProducesResponseType(StatusCodes.Status403Forbidden)]
[SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "imageType", Justification = "Imported from ServiceStack")]
@@ -130,6 +131,7 @@ namespace Jellyfin.Api.Controllers
/// A .
[HttpDelete("Users/{userId}/Images/{itemType}")]
[HttpDelete("Users/{userId}/Images/{itemType}/{index?}", Name = "DeleteUserImage_2")]
+ [Authorize(Policy = Policies.DefaultAuthorization)]
[SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "imageType", Justification = "Imported from ServiceStack")]
[SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "index", Justification = "Imported from ServiceStack")]
[ProducesResponseType(StatusCodes.Status204NoContent)]
@@ -259,6 +261,7 @@ namespace Jellyfin.Api.Controllers
/// Item not found.
/// The list of image infos on success, or if item not found.
[HttpGet("Items/{itemId}/Images")]
+ [Authorize(Policy = Policies.DefaultAuthorization)]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
public ActionResult> GetItemImageInfos([FromRoute] Guid itemId)
diff --git a/Jellyfin.Api/Controllers/InstantMixController.cs b/Jellyfin.Api/Controllers/InstantMixController.cs
index 8ca232ceff..73bd30c4d1 100644
--- a/Jellyfin.Api/Controllers/InstantMixController.cs
+++ b/Jellyfin.Api/Controllers/InstantMixController.cs
@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
+using System.ComponentModel.DataAnnotations;
using System.Linq;
using Jellyfin.Api.Constants;
using Jellyfin.Api.Extensions;
@@ -174,7 +175,7 @@ namespace Jellyfin.Api.Controllers
[HttpGet("MusicGenres/{name}/InstantMix")]
[ProducesResponseType(StatusCodes.Status200OK)]
public ActionResult> GetInstantMixFromMusicGenre(
- [FromRoute] string? name,
+ [FromRoute, Required] string? name,
[FromQuery] Guid? userId,
[FromQuery] int? limit,
[FromQuery] string? fields,
diff --git a/Jellyfin.Api/Controllers/ItemLookupController.cs b/Jellyfin.Api/Controllers/ItemLookupController.cs
index 0d9dffbfe6..c9ad15babe 100644
--- a/Jellyfin.Api/Controllers/ItemLookupController.cs
+++ b/Jellyfin.Api/Controllers/ItemLookupController.cs
@@ -22,7 +22,6 @@ using MediaBrowser.Model.Providers;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
-using Microsoft.AspNetCore.Mvc.ModelBinding;
using Microsoft.Extensions.Logging;
namespace Jellyfin.Api.Controllers
@@ -94,7 +93,7 @@ namespace Jellyfin.Api.Controllers
/// The task result contains an containing the list of remote search results.
///
[HttpPost("Items/RemoteSearch/Movie")]
- public async Task>> GetMovieRemoteSearchResults([FromBody, BindRequired] RemoteSearchQuery query)
+ public async Task>> GetMovieRemoteSearchResults([FromBody, Required] RemoteSearchQuery query)
{
var results = await _providerManager.GetRemoteSearchResults(query, CancellationToken.None)
.ConfigureAwait(false);
@@ -111,7 +110,7 @@ namespace Jellyfin.Api.Controllers
/// The task result contains an containing the list of remote search results.
///
[HttpPost("Items/RemoteSearch/Trailer")]
- public async Task>> GetTrailerRemoteSearchResults([FromBody, BindRequired] RemoteSearchQuery query)
+ public async Task>> GetTrailerRemoteSearchResults([FromBody, Required] RemoteSearchQuery query)
{
var results = await _providerManager.GetRemoteSearchResults(query, CancellationToken.None)
.ConfigureAwait(false);
@@ -128,7 +127,7 @@ namespace Jellyfin.Api.Controllers
/// The task result contains an containing the list of remote search results.
///
[HttpPost("Items/RemoteSearch/MusicVideo")]
- public async Task>> GetMusicVideoRemoteSearchResults([FromBody, BindRequired] RemoteSearchQuery query)
+ public async Task>> GetMusicVideoRemoteSearchResults([FromBody, Required] RemoteSearchQuery query)
{
var results = await _providerManager.GetRemoteSearchResults(query, CancellationToken.None)
.ConfigureAwait(false);
@@ -145,7 +144,7 @@ namespace Jellyfin.Api.Controllers
/// The task result contains an containing the list of remote search results.
///
[HttpPost("Items/RemoteSearch/Series")]
- public async Task>> GetSeriesRemoteSearchResults([FromBody, BindRequired] RemoteSearchQuery query)
+ public async Task>> GetSeriesRemoteSearchResults([FromBody, Required] RemoteSearchQuery query)
{
var results = await _providerManager.GetRemoteSearchResults(query, CancellationToken.None)
.ConfigureAwait(false);
@@ -162,7 +161,7 @@ namespace Jellyfin.Api.Controllers
/// The task result contains an containing the list of remote search results.
///
[HttpPost("Items/RemoteSearch/BoxSet")]
- public async Task>> GetBoxSetRemoteSearchResults([FromBody, BindRequired] RemoteSearchQuery query)
+ public async Task>> GetBoxSetRemoteSearchResults([FromBody, Required] RemoteSearchQuery query)
{
var results = await _providerManager.GetRemoteSearchResults(query, CancellationToken.None)
.ConfigureAwait(false);
@@ -179,7 +178,7 @@ namespace Jellyfin.Api.Controllers
/// The task result contains an containing the list of remote search results.
///
[HttpPost("Items/RemoteSearch/MusicArtist")]
- public async Task>> GetMusicArtistRemoteSearchResults([FromBody, BindRequired] RemoteSearchQuery query)
+ public async Task>> GetMusicArtistRemoteSearchResults([FromBody, Required] RemoteSearchQuery query)
{
var results = await _providerManager.GetRemoteSearchResults(query, CancellationToken.None)
.ConfigureAwait(false);
@@ -196,7 +195,7 @@ namespace Jellyfin.Api.Controllers
/// The task result contains an containing the list of remote search results.
///
[HttpPost("Items/RemoteSearch/MusicAlbum")]
- public async Task>> GetMusicAlbumRemoteSearchResults([FromBody, BindRequired] RemoteSearchQuery query)
+ public async Task>> GetMusicAlbumRemoteSearchResults([FromBody, Required] RemoteSearchQuery query)
{
var results = await _providerManager.GetRemoteSearchResults(query, CancellationToken.None)
.ConfigureAwait(false);
@@ -214,7 +213,7 @@ namespace Jellyfin.Api.Controllers
///
[HttpPost("Items/RemoteSearch/Person")]
[Authorize(Policy = Policies.RequiresElevation)]
- public async Task>> GetPersonRemoteSearchResults([FromBody, BindRequired] RemoteSearchQuery query)
+ public async Task>> GetPersonRemoteSearchResults([FromBody, Required] RemoteSearchQuery query)
{
var results = await _providerManager.GetRemoteSearchResults(query, CancellationToken.None)
.ConfigureAwait(false);
@@ -231,7 +230,7 @@ namespace Jellyfin.Api.Controllers
/// The task result contains an containing the list of remote search results.
///
[HttpPost("Items/RemoteSearch/Book")]
- public async Task>> GetBookRemoteSearchResults([FromBody, BindRequired] RemoteSearchQuery query)
+ public async Task>> GetBookRemoteSearchResults([FromBody, Required] RemoteSearchQuery query)
{
var results = await _providerManager.GetRemoteSearchResults(query, CancellationToken.None)
.ConfigureAwait(false);
@@ -296,7 +295,7 @@ namespace Jellyfin.Api.Controllers
[Authorize(Policy = Policies.RequiresElevation)]
public async Task ApplySearchCriteria(
[FromRoute] Guid itemId,
- [FromBody, BindRequired] RemoteSearchResult searchResult,
+ [FromBody, Required] RemoteSearchResult searchResult,
[FromQuery] bool replaceAllImages = true)
{
var item = _libraryManager.GetItemById(itemId);
diff --git a/Jellyfin.Api/Controllers/ItemUpdateController.cs b/Jellyfin.Api/Controllers/ItemUpdateController.cs
index a5d9d36a33..4b40c6ada9 100644
--- a/Jellyfin.Api/Controllers/ItemUpdateController.cs
+++ b/Jellyfin.Api/Controllers/ItemUpdateController.cs
@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
+using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.Threading;
using Jellyfin.Api.Constants;
@@ -17,7 +18,6 @@ using MediaBrowser.Model.IO;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
-using Microsoft.AspNetCore.Mvc.ModelBinding;
namespace Jellyfin.Api.Controllers
{
@@ -67,7 +67,7 @@ namespace Jellyfin.Api.Controllers
[HttpPost("Items/{itemId}")]
[ProducesResponseType(StatusCodes.Status204NoContent)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
- public ActionResult UpdateItem([FromRoute] Guid itemId, [FromBody, BindRequired] BaseItemDto request)
+ public ActionResult UpdateItem([FromRoute] Guid itemId, [FromBody, Required] BaseItemDto request)
{
var item = _libraryManager.GetItemById(itemId);
if (item == null)
@@ -194,7 +194,7 @@ namespace Jellyfin.Api.Controllers
[HttpPost("Items/{itemId}/ContentType")]
[ProducesResponseType(StatusCodes.Status204NoContent)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
- public ActionResult UpdateItemContentType([FromRoute] Guid itemId, [FromQuery, BindRequired] string? contentType)
+ public ActionResult UpdateItemContentType([FromRoute] Guid itemId, [FromQuery, Required] string? contentType)
{
var item = _libraryManager.GetItemById(itemId);
if (item == null)
diff --git a/Jellyfin.Api/Controllers/LibraryController.cs b/Jellyfin.Api/Controllers/LibraryController.cs
index 4731a5c8b4..4548e202a0 100644
--- a/Jellyfin.Api/Controllers/LibraryController.cs
+++ b/Jellyfin.Api/Controllers/LibraryController.cs
@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
+using System.ComponentModel.DataAnnotations;
using System.Globalization;
using System.IO;
using System.Linq;
@@ -32,7 +33,6 @@ using MediaBrowser.Model.Querying;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
-using Microsoft.AspNetCore.Mvc.ModelBinding;
using Microsoft.Extensions.Logging;
using Book = MediaBrowser.Controller.Entities.Book;
using Movie = Jellyfin.Data.Entities.Movie;
@@ -597,7 +597,7 @@ namespace Jellyfin.Api.Controllers
[HttpPost("Library/Media/Updated")]
[Authorize(Policy = Policies.DefaultAuthorization)]
[ProducesResponseType(StatusCodes.Status204NoContent)]
- public ActionResult PostUpdatedMedia([FromBody, BindRequired] MediaUpdateInfoDto[] updates)
+ public ActionResult PostUpdatedMedia([FromBody, Required] MediaUpdateInfoDto[] updates)
{
foreach (var item in updates)
{
@@ -685,6 +685,7 @@ namespace Jellyfin.Api.Controllers
[HttpGet("Shows/{itemId}/Similar", Name = "GetSimilarShows2")]
[HttpGet("Movies/{itemId}/Similar", Name = "GetSimilarMovies2")]
[HttpGet("Trailers/{itemId}/Similar", Name = "GetSimilarTrailers2")]
+ [Authorize(Policy = Policies.DefaultAuthorization)]
[ProducesResponseType(StatusCodes.Status200OK)]
public ActionResult> GetSimilarItems(
[FromRoute] Guid itemId,
@@ -736,11 +737,11 @@ namespace Jellyfin.Api.Controllers
/// Library options info returned.
/// Library options info.
[HttpGet("Libraries/AvailableOptions")]
- [Authorize(Policy = Policies.FirstTimeSetupOrElevated)]
+ [Authorize(Policy = Policies.FirstTimeSetupOrDefault)]
[ProducesResponseType(StatusCodes.Status200OK)]
public ActionResult GetLibraryOptionsInfo(
[FromQuery] string? libraryContentType,
- [FromQuery] bool isNewLibrary = false)
+ [FromQuery] bool isNewLibrary)
{
var result = new LibraryOptionsResultDto();
diff --git a/Jellyfin.Api/Controllers/LibraryStructureController.cs b/Jellyfin.Api/Controllers/LibraryStructureController.cs
index ca150f3f24..cdab4f356f 100644
--- a/Jellyfin.Api/Controllers/LibraryStructureController.cs
+++ b/Jellyfin.Api/Controllers/LibraryStructureController.cs
@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
+using System.ComponentModel.DataAnnotations;
using System.Globalization;
using System.IO;
using System.Linq;
@@ -17,7 +18,6 @@ using MediaBrowser.Model.Entities;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
-using Microsoft.AspNetCore.Mvc.ModelBinding;
namespace Jellyfin.Api.Controllers
{
@@ -204,7 +204,7 @@ namespace Jellyfin.Api.Controllers
[HttpPost("Paths")]
[ProducesResponseType(StatusCodes.Status204NoContent)]
public ActionResult AddMediaPath(
- [FromBody, BindRequired] MediaPathDto mediaPathDto,
+ [FromBody, Required] MediaPathDto mediaPathDto,
[FromQuery] bool refreshLibrary = false)
{
_libraryMonitor.Stop();
diff --git a/Jellyfin.Api/Controllers/LocalizationController.cs b/Jellyfin.Api/Controllers/LocalizationController.cs
index 1466dd3ec0..ef2e7e8b15 100644
--- a/Jellyfin.Api/Controllers/LocalizationController.cs
+++ b/Jellyfin.Api/Controllers/LocalizationController.cs
@@ -11,7 +11,7 @@ namespace Jellyfin.Api.Controllers
///
/// Localization controller.
///
- [Authorize(Policy = Policies.FirstTimeSetupOrElevated)]
+ [Authorize(Policy = Policies.FirstTimeSetupOrDefault)]
public class LocalizationController : BaseJellyfinApiController
{
private readonly ILocalizationManager _localization;
diff --git a/Jellyfin.Api/Controllers/MediaInfoController.cs b/Jellyfin.Api/Controllers/MediaInfoController.cs
index 242cbf1918..517113074c 100644
--- a/Jellyfin.Api/Controllers/MediaInfoController.cs
+++ b/Jellyfin.Api/Controllers/MediaInfoController.cs
@@ -1,5 +1,6 @@
using System;
using System.Buffers;
+using System.ComponentModel.DataAnnotations;
using System.Globalization;
using System.Linq;
using System.Net.Mime;
@@ -91,7 +92,7 @@ namespace Jellyfin.Api.Controllers
/// A containing a with the playback information.
[HttpGet("Items/{itemId}/PlaybackInfo")]
[ProducesResponseType(StatusCodes.Status200OK)]
- public async Task> GetPlaybackInfo([FromRoute] Guid itemId, [FromQuery] Guid? userId)
+ public async Task> GetPlaybackInfo([FromRoute] Guid itemId, [FromQuery, Required] Guid? userId)
{
return await GetPlaybackInfoInternal(itemId, userId).ConfigureAwait(false);
}
@@ -281,7 +282,7 @@ namespace Jellyfin.Api.Controllers
/// A indicating success.
[HttpPost("LiveStreams/Close")]
[ProducesResponseType(StatusCodes.Status204NoContent)]
- public ActionResult CloseLiveStream([FromQuery] string? liveStreamId)
+ public ActionResult CloseLiveStream([FromQuery, Required] string? liveStreamId)
{
_mediaSourceManager.CloseLiveStream(liveStreamId).GetAwaiter().GetResult();
return NoContent();
diff --git a/Jellyfin.Api/Controllers/NotificationsController.cs b/Jellyfin.Api/Controllers/NotificationsController.cs
index 1bb39b5f76..cf3e780b44 100644
--- a/Jellyfin.Api/Controllers/NotificationsController.cs
+++ b/Jellyfin.Api/Controllers/NotificationsController.cs
@@ -1,13 +1,16 @@
using System;
using System.Collections.Generic;
+using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.Threading;
+using Jellyfin.Api.Constants;
using Jellyfin.Api.Models.NotificationDtos;
using Jellyfin.Data.Enums;
using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.Notifications;
using MediaBrowser.Model.Dto;
using MediaBrowser.Model.Notifications;
+using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
@@ -16,6 +19,7 @@ namespace Jellyfin.Api.Controllers
///
/// The notification controller.
///
+ [Authorize(Policy = Policies.DefaultAuthorization)]
public class NotificationsController : BaseJellyfinApiController
{
private readonly INotificationManager _notificationManager;
@@ -83,19 +87,19 @@ namespace Jellyfin.Api.Controllers
///
/// Sends a notification to all admins.
///
- /// The name of the notification.
- /// The description of the notification.
/// The URL of the notification.
/// The level of the notification.
+ /// The name of the notification.
+ /// The description of the notification.
/// Notification sent.
/// A .
[HttpPost("Admin")]
[ProducesResponseType(StatusCodes.Status204NoContent)]
public ActionResult CreateAdminNotification(
- [FromQuery] string? name,
- [FromQuery] string? description,
[FromQuery] string? url,
- [FromQuery] NotificationLevel? level)
+ [FromQuery] NotificationLevel? level,
+ [FromQuery] string name = "",
+ [FromQuery] string description = "")
{
var notification = new NotificationRequest
{
diff --git a/Jellyfin.Api/Controllers/PackageController.cs b/Jellyfin.Api/Controllers/PackageController.cs
index 06c4213fb0..3d6a879093 100644
--- a/Jellyfin.Api/Controllers/PackageController.cs
+++ b/Jellyfin.Api/Controllers/PackageController.cs
@@ -127,7 +127,6 @@ namespace Jellyfin.Api.Controllers
/// Package repositories returned.
/// An containing the list of package repositories.
[HttpGet("Repositories")]
- [Authorize(Policy = Policies.DefaultAuthorization)]
[ProducesResponseType(StatusCodes.Status200OK)]
public ActionResult> GetRepositories()
{
diff --git a/Jellyfin.Api/Controllers/PersonsController.cs b/Jellyfin.Api/Controllers/PersonsController.cs
index 23cc23ce70..b6ccec666a 100644
--- a/Jellyfin.Api/Controllers/PersonsController.cs
+++ b/Jellyfin.Api/Controllers/PersonsController.cs
@@ -1,6 +1,7 @@
using System;
using System.Globalization;
using System.Linq;
+using Jellyfin.Api.Constants;
using Jellyfin.Api.Extensions;
using Jellyfin.Api.Helpers;
using Jellyfin.Data.Entities;
@@ -9,6 +10,7 @@ using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Library;
using MediaBrowser.Model.Dto;
using MediaBrowser.Model.Querying;
+using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
@@ -17,6 +19,7 @@ namespace Jellyfin.Api.Controllers
///
/// Persons controller.
///
+ [Authorize(Policy = Policies.DefaultAuthorization)]
public class PersonsController : BaseJellyfinApiController
{
private readonly ILibraryManager _libraryManager;
diff --git a/Jellyfin.Api/Controllers/PlaylistsController.cs b/Jellyfin.Api/Controllers/PlaylistsController.cs
index cf46604948..12c87d7c36 100644
--- a/Jellyfin.Api/Controllers/PlaylistsController.cs
+++ b/Jellyfin.Api/Controllers/PlaylistsController.cs
@@ -1,4 +1,5 @@
using System;
+using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.Threading.Tasks;
using Jellyfin.Api.Constants;
@@ -14,7 +15,6 @@ using MediaBrowser.Model.Querying;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
-using Microsoft.AspNetCore.Mvc.ModelBinding;
namespace Jellyfin.Api.Controllers
{
@@ -59,7 +59,7 @@ namespace Jellyfin.Api.Controllers
[HttpPost]
[ProducesResponseType(StatusCodes.Status200OK)]
public async Task> CreatePlaylist(
- [FromBody, BindRequired] CreatePlaylistDto createPlaylistRequest)
+ [FromBody, Required] CreatePlaylistDto createPlaylistRequest)
{
Guid[] idGuidArray = RequestHelpers.GetGuids(createPlaylistRequest.Ids);
var result = await _playlistManager.CreatePlaylist(new PlaylistCreationRequest
diff --git a/Jellyfin.Api/Controllers/PluginsController.cs b/Jellyfin.Api/Controllers/PluginsController.cs
index fe10f0f1bf..b2f34680b0 100644
--- a/Jellyfin.Api/Controllers/PluginsController.cs
+++ b/Jellyfin.Api/Controllers/PluginsController.cs
@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
+using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.Text.Json;
using System.Threading.Tasks;
@@ -13,7 +14,6 @@ using MediaBrowser.Model.Plugins;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
-using Microsoft.AspNetCore.Mvc.ModelBinding;
namespace Jellyfin.Api.Controllers
{
@@ -154,7 +154,7 @@ namespace Jellyfin.Api.Controllers
[HttpPost("SecurityInfo")]
[Authorize(Policy = Policies.RequiresElevation)]
[ProducesResponseType(StatusCodes.Status204NoContent)]
- public ActionResult UpdatePluginSecurityInfo([FromBody, BindRequired] PluginSecurityInfo pluginSecurityInfo)
+ public ActionResult UpdatePluginSecurityInfo([FromBody, Required] PluginSecurityInfo pluginSecurityInfo)
{
return NoContent();
}
diff --git a/Jellyfin.Api/Controllers/RemoteImageController.cs b/Jellyfin.Api/Controllers/RemoteImageController.cs
index 50a161ef6e..baa3d80ac8 100644
--- a/Jellyfin.Api/Controllers/RemoteImageController.cs
+++ b/Jellyfin.Api/Controllers/RemoteImageController.cs
@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
+using System.ComponentModel.DataAnnotations;
using System.IO;
using System.Linq;
using System.Net.Mime;
@@ -18,7 +19,6 @@ using MediaBrowser.Model.Providers;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
-using Microsoft.AspNetCore.Mvc.ModelBinding;
namespace Jellyfin.Api.Controllers
{
@@ -154,7 +154,7 @@ namespace Jellyfin.Api.Controllers
[Produces(MediaTypeNames.Application.Octet)]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
- public async Task GetRemoteImage([FromQuery, BindRequired] string imageUrl)
+ public async Task GetRemoteImage([FromQuery, Required] string imageUrl)
{
var urlHash = imageUrl.GetMD5();
var pointerCachePath = GetFullCachePath(urlHash.ToString());
@@ -209,7 +209,7 @@ namespace Jellyfin.Api.Controllers
[ProducesResponseType(StatusCodes.Status404NotFound)]
public async Task DownloadRemoteImage(
[FromRoute] Guid itemId,
- [FromQuery, BindRequired] ImageType type,
+ [FromQuery, Required] ImageType type,
[FromQuery] string? imageUrl)
{
var item = _libraryManager.GetItemById(itemId);
diff --git a/Jellyfin.Api/Controllers/ScheduledTasksController.cs b/Jellyfin.Api/Controllers/ScheduledTasksController.cs
index 3df325e3ba..e672070c06 100644
--- a/Jellyfin.Api/Controllers/ScheduledTasksController.cs
+++ b/Jellyfin.Api/Controllers/ScheduledTasksController.cs
@@ -1,12 +1,12 @@
using System;
using System.Collections.Generic;
+using System.ComponentModel.DataAnnotations;
using System.Linq;
using Jellyfin.Api.Constants;
using MediaBrowser.Model.Tasks;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
-using Microsoft.AspNetCore.Mvc.ModelBinding;
namespace Jellyfin.Api.Controllers
{
@@ -71,7 +71,7 @@ namespace Jellyfin.Api.Controllers
[HttpGet("{taskId}")]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
- public ActionResult GetTask([FromRoute] string? taskId)
+ public ActionResult GetTask([FromRoute, Required] string? taskId)
{
var task = _taskManager.ScheduledTasks.FirstOrDefault(i =>
string.Equals(i.Id, taskId, StringComparison.OrdinalIgnoreCase));
@@ -118,7 +118,7 @@ namespace Jellyfin.Api.Controllers
[HttpDelete("Running/{taskId}")]
[ProducesResponseType(StatusCodes.Status204NoContent)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
- public ActionResult StopTask([FromRoute] string? taskId)
+ public ActionResult StopTask([FromRoute, Required] string? taskId)
{
var task = _taskManager.ScheduledTasks.FirstOrDefault(o =>
o.Id.Equals(taskId, StringComparison.OrdinalIgnoreCase));
@@ -144,8 +144,8 @@ namespace Jellyfin.Api.Controllers
[ProducesResponseType(StatusCodes.Status204NoContent)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
public ActionResult UpdateTask(
- [FromRoute] string? taskId,
- [FromBody, BindRequired] TaskTriggerInfo[] triggerInfos)
+ [FromRoute, Required] string? taskId,
+ [FromBody, Required] TaskTriggerInfo[] triggerInfos)
{
var task = _taskManager.ScheduledTasks.FirstOrDefault(o =>
o.Id.Equals(taskId, StringComparison.OrdinalIgnoreCase));
diff --git a/Jellyfin.Api/Controllers/SessionController.cs b/Jellyfin.Api/Controllers/SessionController.cs
index 3e6f577f13..48b57bdb7f 100644
--- a/Jellyfin.Api/Controllers/SessionController.cs
+++ b/Jellyfin.Api/Controllers/SessionController.cs
@@ -122,12 +122,13 @@ namespace Jellyfin.Api.Controllers
/// Instruction sent to session.
/// A .
[HttpPost("Sessions/{sessionId}/Viewing")]
+ [Authorize(Policy = Policies.DefaultAuthorization)]
[ProducesResponseType(StatusCodes.Status204NoContent)]
public ActionResult DisplayContent(
- [FromRoute] string? sessionId,
- [FromQuery] string? itemType,
- [FromQuery] string? itemId,
- [FromQuery] string? itemName)
+ [FromRoute, Required] string? sessionId,
+ [FromQuery, Required] string? itemType,
+ [FromQuery, Required] string? itemId,
+ [FromQuery, Required] string? itemName)
{
var command = new BrowseRequest
{
@@ -156,9 +157,10 @@ namespace Jellyfin.Api.Controllers
/// Instruction sent to session.
/// A .
[HttpPost("Sessions/{sessionId}/Playing")]
+ [Authorize(Policy = Policies.DefaultAuthorization)]
[ProducesResponseType(StatusCodes.Status204NoContent)]
public ActionResult Play(
- [FromRoute] string? sessionId,
+ [FromRoute, Required] string? sessionId,
[FromQuery] Guid[] itemIds,
[FromQuery] long? startPositionTicks,
[FromQuery] PlayCommand playCommand,
@@ -190,9 +192,10 @@ namespace Jellyfin.Api.Controllers
/// Playstate command sent to session.
/// A .
[HttpPost("Sessions/{sessionId}/Playing/{command}")]
+ [Authorize(Policy = Policies.DefaultAuthorization)]
[ProducesResponseType(StatusCodes.Status204NoContent)]
public ActionResult SendPlaystateCommand(
- [FromRoute] string? sessionId,
+ [FromRoute, Required] string? sessionId,
[FromBody] PlaystateRequest playstateRequest)
{
_sessionManager.SendPlaystateCommand(
@@ -212,10 +215,11 @@ namespace Jellyfin.Api.Controllers
/// System command sent to session.
/// A .
[HttpPost("Sessions/{sessionId}/System/{command}")]
+ [Authorize(Policy = Policies.DefaultAuthorization)]
[ProducesResponseType(StatusCodes.Status204NoContent)]
public ActionResult SendSystemCommand(
- [FromRoute] string? sessionId,
- [FromRoute] string? command)
+ [FromRoute, Required] string? sessionId,
+ [FromRoute, Required] string? command)
{
var name = command;
if (Enum.TryParse(name, true, out GeneralCommandType commandType))
@@ -243,10 +247,11 @@ namespace Jellyfin.Api.Controllers
/// General command sent to session.
/// A .
[HttpPost("Sessions/{sessionId}/Command/{command}")]
+ [Authorize(Policy = Policies.DefaultAuthorization)]
[ProducesResponseType(StatusCodes.Status204NoContent)]
public ActionResult SendGeneralCommand(
- [FromRoute] string? sessionId,
- [FromRoute] string? command)
+ [FromRoute, Required] string? sessionId,
+ [FromRoute, Required] string? command)
{
var currentSession = RequestHelpers.GetSession(_sessionManager, _authContext, Request);
@@ -269,9 +274,10 @@ namespace Jellyfin.Api.Controllers
/// Full general command sent to session.
/// A .
[HttpPost("Sessions/{sessionId}/Command")]
+ [Authorize(Policy = Policies.DefaultAuthorization)]
[ProducesResponseType(StatusCodes.Status204NoContent)]
public ActionResult SendFullGeneralCommand(
- [FromRoute] string? sessionId,
+ [FromRoute, Required] string? sessionId,
[FromBody, Required] GeneralCommand command)
{
var currentSession = RequestHelpers.GetSession(_sessionManager, _authContext, Request);
@@ -302,11 +308,12 @@ namespace Jellyfin.Api.Controllers
/// Message sent.
/// A .
[HttpPost("Sessions/{sessionId}/Message")]
+ [Authorize(Policy = Policies.DefaultAuthorization)]
[ProducesResponseType(StatusCodes.Status204NoContent)]
public ActionResult SendMessageCommand(
- [FromRoute] string? sessionId,
- [FromQuery] string? text,
- [FromQuery] string? header,
+ [FromRoute, Required] string? sessionId,
+ [FromQuery, Required] string? text,
+ [FromQuery, Required] string? header,
[FromQuery] long? timeoutMs)
{
var command = new MessageCommand
@@ -329,9 +336,10 @@ namespace Jellyfin.Api.Controllers
/// User added to session.
/// A .
[HttpPost("Sessions/{sessionId}/User/{userId}")]
+ [Authorize(Policy = Policies.DefaultAuthorization)]
[ProducesResponseType(StatusCodes.Status204NoContent)]
public ActionResult AddUserToSession(
- [FromRoute] string? sessionId,
+ [FromRoute, Required] string? sessionId,
[FromRoute] Guid userId)
{
_sessionManager.AddAdditionalUser(sessionId, userId);
@@ -346,6 +354,7 @@ namespace Jellyfin.Api.Controllers
/// User removed from session.
/// A .
[HttpDelete("Sessions/{sessionId}/User/{userId}")]
+ [Authorize(Policy = Policies.DefaultAuthorization)]
[ProducesResponseType(StatusCodes.Status204NoContent)]
public ActionResult RemoveUserFromSession(
[FromRoute] string? sessionId,
@@ -367,9 +376,10 @@ namespace Jellyfin.Api.Controllers
/// Capabilities posted.
/// A .
[HttpPost("Sessions/Capabilities")]
+ [Authorize(Policy = Policies.DefaultAuthorization)]
[ProducesResponseType(StatusCodes.Status204NoContent)]
public ActionResult PostCapabilities(
- [FromQuery] string? id,
+ [FromQuery, Required] string? id,
[FromQuery] string? playableMediaTypes,
[FromQuery] string? supportedCommands,
[FromQuery] bool supportsMediaControl = false,
@@ -400,9 +410,10 @@ namespace Jellyfin.Api.Controllers
/// Capabilities updated.
/// A .
[HttpPost("Sessions/Capabilities/Full")]
+ [Authorize(Policy = Policies.DefaultAuthorization)]
[ProducesResponseType(StatusCodes.Status204NoContent)]
public ActionResult PostFullCapabilities(
- [FromQuery] string? id,
+ [FromQuery, Required] string? id,
[FromBody, Required] ClientCapabilities capabilities)
{
if (string.IsNullOrWhiteSpace(id))
@@ -423,6 +434,7 @@ namespace Jellyfin.Api.Controllers
/// Session reported to server.
/// A .
[HttpPost("Sessions/Viewing")]
+ [Authorize(Policy = Policies.DefaultAuthorization)]
[ProducesResponseType(StatusCodes.Status204NoContent)]
public ActionResult ReportViewing(
[FromQuery] string? sessionId,
@@ -440,6 +452,7 @@ namespace Jellyfin.Api.Controllers
/// Session end reported to server.
/// A .
[HttpPost("Sessions/Logout")]
+ [Authorize(Policy = Policies.DefaultAuthorization)]
[ProducesResponseType(StatusCodes.Status204NoContent)]
public ActionResult ReportSessionEnded()
{
@@ -455,6 +468,7 @@ namespace Jellyfin.Api.Controllers
/// Auth providers retrieved.
/// An with the auth providers.
[HttpGet("Auth/Providers")]
+ [Authorize(Policy = Policies.RequiresElevation)]
[ProducesResponseType(StatusCodes.Status200OK)]
public ActionResult> GetAuthProviders()
{
@@ -468,6 +482,7 @@ namespace Jellyfin.Api.Controllers
/// An with the password reset providers.
[HttpGet("Auto/PasswordResetProviders")]
[ProducesResponseType(StatusCodes.Status200OK)]
+ [Authorize(Policy = Policies.RequiresElevation)]
public ActionResult> GetPasswordResetProviders()
{
return _userManager.GetPasswordResetProviders();
diff --git a/Jellyfin.Api/Controllers/SubtitleController.cs b/Jellyfin.Api/Controllers/SubtitleController.cs
index d5633fba52..988acccc3a 100644
--- a/Jellyfin.Api/Controllers/SubtitleController.cs
+++ b/Jellyfin.Api/Controllers/SubtitleController.cs
@@ -113,7 +113,7 @@ namespace Jellyfin.Api.Controllers
[ProducesResponseType(StatusCodes.Status200OK)]
public async Task>> SearchRemoteSubtitles(
[FromRoute] Guid itemId,
- [FromRoute] string? language,
+ [FromRoute, Required] string? language,
[FromQuery] bool? isPerfectMatch)
{
var video = (Video)_libraryManager.GetItemById(itemId);
@@ -133,7 +133,7 @@ namespace Jellyfin.Api.Controllers
[ProducesResponseType(StatusCodes.Status204NoContent)]
public async Task DownloadRemoteSubtitles(
[FromRoute] Guid itemId,
- [FromRoute] string? subtitleId)
+ [FromRoute, Required] string? subtitleId)
{
var video = (Video)_libraryManager.GetItemById(itemId);
@@ -162,7 +162,7 @@ namespace Jellyfin.Api.Controllers
[Authorize(Policy = Policies.DefaultAuthorization)]
[ProducesResponseType(StatusCodes.Status200OK)]
[Produces(MediaTypeNames.Application.Octet)]
- public async Task GetRemoteSubtitles([FromRoute] string? id)
+ public async Task GetRemoteSubtitles([FromRoute, Required] string? id)
{
var result = await _subtitleManager.GetRemoteSubtitles(id, CancellationToken.None).ConfigureAwait(false);
diff --git a/Jellyfin.Api/Controllers/SyncPlayController.cs b/Jellyfin.Api/Controllers/SyncPlayController.cs
index 2b1b95b1b5..e16a10ba4d 100644
--- a/Jellyfin.Api/Controllers/SyncPlayController.cs
+++ b/Jellyfin.Api/Controllers/SyncPlayController.cs
@@ -94,7 +94,7 @@ namespace Jellyfin.Api.Controllers
///
/// Optional. Filter by item id.
/// Groups returned.
- /// An containing the available SyncPlay groups.
+ /// An containing the available SyncPlay groups.
[HttpGet("List")]
[ProducesResponseType(StatusCodes.Status200OK)]
public ActionResult> SyncPlayGetGroups([FromQuery] Guid? filterItemId)
diff --git a/Jellyfin.Api/Controllers/SystemController.cs b/Jellyfin.Api/Controllers/SystemController.cs
index 6f9a75e2f5..bbfd163de5 100644
--- a/Jellyfin.Api/Controllers/SystemController.cs
+++ b/Jellyfin.Api/Controllers/SystemController.cs
@@ -23,7 +23,6 @@ namespace Jellyfin.Api.Controllers
///
/// The system controller.
///
- [Route("System")]
public class SystemController : BaseJellyfinApiController
{
private readonly IServerApplicationHost _appHost;
@@ -60,8 +59,7 @@ namespace Jellyfin.Api.Controllers
/// Information retrieved.
/// A with info about the system.
[HttpGet("Info")]
- [Authorize(Policy = Policies.IgnoreSchedule)]
- [Authorize(Policy = Policies.FirstTimeSetupOrElevated)]
+ [Authorize(Policy = Policies.FirstTimeSetupOrIgnoreParentalControl)]
[ProducesResponseType(StatusCodes.Status200OK)]
public async Task> GetSystemInfo()
{
@@ -99,8 +97,7 @@ namespace Jellyfin.Api.Controllers
/// Server restarted.
/// No content. Server restarted.
[HttpPost("Restart")]
- [Authorize(Policy = Policies.LocalAccessOnly)]
- [Authorize(Policy = Policies.RequiresElevation)]
+ [Authorize(Policy = Policies.LocalAccessOrRequiresElevation)]
[ProducesResponseType(StatusCodes.Status204NoContent)]
public ActionResult RestartApplication()
{
diff --git a/Jellyfin.Api/Controllers/TimeSyncController.cs b/Jellyfin.Api/Controllers/TimeSyncController.cs
index bbabcd6e60..2dc744e7ca 100644
--- a/Jellyfin.Api/Controllers/TimeSyncController.cs
+++ b/Jellyfin.Api/Controllers/TimeSyncController.cs
@@ -9,7 +9,7 @@ namespace Jellyfin.Api.Controllers
///
/// The time sync controller.
///
- [Route("GetUtcTime")]
+ [Route("")]
public class TimeSyncController : BaseJellyfinApiController
{
///
@@ -17,7 +17,7 @@ namespace Jellyfin.Api.Controllers
///
/// Time returned.
/// An to sync the client and server time.
- [HttpGet]
+ [HttpGet("GetUtcTime")]
[ProducesResponseType(statusCode: StatusCodes.Status200OK)]
public ActionResult GetUtcTime()
{
diff --git a/Jellyfin.Api/Controllers/TvShowsController.cs b/Jellyfin.Api/Controllers/TvShowsController.cs
index d4560dfa25..f463ab8894 100644
--- a/Jellyfin.Api/Controllers/TvShowsController.cs
+++ b/Jellyfin.Api/Controllers/TvShowsController.cs
@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
+using System.ComponentModel.DataAnnotations;
using System.Globalization;
using System.Linq;
using Jellyfin.Api.Constants;
@@ -68,7 +69,7 @@ namespace Jellyfin.Api.Controllers
[HttpGet("NextUp")]
[ProducesResponseType(StatusCodes.Status200OK)]
public ActionResult> GetNextUp(
- [FromQuery] Guid? userId,
+ [FromQuery, Required] Guid? userId,
[FromQuery] int? startIndex,
[FromQuery] int? limit,
[FromQuery] string? fields,
@@ -126,7 +127,7 @@ namespace Jellyfin.Api.Controllers
[HttpGet("Upcoming")]
[ProducesResponseType(StatusCodes.Status200OK)]
public ActionResult> GetUpcomingEpisodes(
- [FromQuery] Guid? userId,
+ [FromQuery, Required] Guid? userId,
[FromQuery] int? startIndex,
[FromQuery] int? limit,
[FromQuery] string? fields,
@@ -193,8 +194,8 @@ namespace Jellyfin.Api.Controllers
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
public ActionResult> GetEpisodes(
- [FromRoute] string? seriesId,
- [FromQuery] Guid? userId,
+ [FromRoute, Required] string? seriesId,
+ [FromQuery, Required] Guid? userId,
[FromQuery] string? fields,
[FromQuery] int? season,
[FromQuery] string? seasonId,
@@ -316,8 +317,8 @@ namespace Jellyfin.Api.Controllers
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
public ActionResult> GetSeasons(
- [FromRoute] string? seriesId,
- [FromQuery] Guid? userId,
+ [FromRoute, Required] string? seriesId,
+ [FromQuery, Required] Guid? userId,
[FromQuery] string? fields,
[FromQuery] bool? isSpecialSeason,
[FromQuery] bool? isMissing,
diff --git a/Jellyfin.Api/Controllers/UserController.cs b/Jellyfin.Api/Controllers/UserController.cs
index 2ce5c7e569..d897f07b7f 100644
--- a/Jellyfin.Api/Controllers/UserController.cs
+++ b/Jellyfin.Api/Controllers/UserController.cs
@@ -20,7 +20,6 @@ using MediaBrowser.Model.Users;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
-using Microsoft.AspNetCore.Mvc.ModelBinding;
namespace Jellyfin.Api.Controllers
{
@@ -106,7 +105,7 @@ namespace Jellyfin.Api.Controllers
/// User not found.
/// An with information about the user or a if the user was not found.
[HttpGet("{userId}")]
- [Authorize(Policy = Policies.IgnoreSchedule)]
+ [Authorize(Policy = Policies.IgnoreParentalControl)]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
public ActionResult GetUserById([FromRoute] Guid userId)
@@ -157,8 +156,8 @@ namespace Jellyfin.Api.Controllers
[ProducesResponseType(StatusCodes.Status404NotFound)]
public async Task> AuthenticateUser(
[FromRoute, Required] Guid userId,
- [FromQuery, BindRequired] string? pw,
- [FromQuery, BindRequired] string? password)
+ [FromQuery, Required] string? pw,
+ [FromQuery] string? password)
{
var user = _userManager.GetUserById(userId);
@@ -190,7 +189,7 @@ namespace Jellyfin.Api.Controllers
/// A containing an with information about the new session.
[HttpPost("AuthenticateByName")]
[ProducesResponseType(StatusCodes.Status200OK)]
- public async Task> AuthenticateUserByName([FromBody, BindRequired] AuthenticateUserByName request)
+ public async Task> AuthenticateUserByName([FromBody, Required] AuthenticateUserByName request)
{
var auth = _authContext.GetAuthorizationInfo(Request);
@@ -371,7 +370,7 @@ namespace Jellyfin.Api.Controllers
/// User policy update forbidden.
/// A indicating success or a or a on failure..
[HttpPost("{userId}/Policy")]
- [Authorize(Policy = Policies.DefaultAuthorization)]
+ [Authorize(Policy = Policies.RequiresElevation)]
[ProducesResponseType(StatusCodes.Status204NoContent)]
[ProducesResponseType(StatusCodes.Status400BadRequest)]
[ProducesResponseType(StatusCodes.Status403Forbidden)]
diff --git a/Jellyfin.Api/Controllers/VideoAttachmentsController.cs b/Jellyfin.Api/Controllers/VideoAttachmentsController.cs
index eef0a93cdf..09a1c93e6a 100644
--- a/Jellyfin.Api/Controllers/VideoAttachmentsController.cs
+++ b/Jellyfin.Api/Controllers/VideoAttachmentsController.cs
@@ -1,12 +1,11 @@
using System;
+using System.ComponentModel.DataAnnotations;
using System.Net.Mime;
using System.Threading;
using System.Threading.Tasks;
-using Jellyfin.Api.Constants;
using MediaBrowser.Common.Extensions;
using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.MediaEncoding;
-using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
@@ -16,7 +15,6 @@ namespace Jellyfin.Api.Controllers
/// Attachments controller.
///
[Route("Videos")]
- [Authorize(Policy = Policies.DefaultAuthorization)]
public class VideoAttachmentsController : BaseJellyfinApiController
{
private readonly ILibraryManager _libraryManager;
@@ -49,9 +47,9 @@ namespace Jellyfin.Api.Controllers
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
public async Task> GetAttachment(
- [FromRoute] Guid videoId,
- [FromRoute] string? mediaSourceId,
- [FromRoute] int index)
+ [FromRoute, Required] Guid videoId,
+ [FromRoute, Required] string mediaSourceId,
+ [FromRoute, Required] int index)
{
try
{
diff --git a/Jellyfin.Api/Controllers/VideosController.cs b/Jellyfin.Api/Controllers/VideosController.cs
index ebe88a9c05..fe065c76f6 100644
--- a/Jellyfin.Api/Controllers/VideosController.cs
+++ b/Jellyfin.Api/Controllers/VideosController.cs
@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
+using System.ComponentModel.DataAnnotations;
using System.Globalization;
using System.Linq;
using System.Net.Http;
@@ -35,7 +36,6 @@ namespace Jellyfin.Api.Controllers
///
/// The videos controller.
///
- [Route("Videos")]
public class VideosController : BaseJellyfinApiController
{
private readonly ILibraryManager _libraryManager;
@@ -196,7 +196,7 @@ namespace Jellyfin.Api.Controllers
[Authorize(Policy = Policies.RequiresElevation)]
[ProducesResponseType(StatusCodes.Status204NoContent)]
[ProducesResponseType(StatusCodes.Status400BadRequest)]
- public ActionResult MergeVersions([FromQuery] string? itemIds)
+ public ActionResult MergeVersions([FromQuery, Required] string? itemIds)
{
var items = RequestHelpers.Split(itemIds, ',', true)
.Select(i => _libraryManager.GetItemById(i))
diff --git a/Jellyfin.Api/Controllers/YearsController.cs b/Jellyfin.Api/Controllers/YearsController.cs
index d09b016a9a..eb91ac23e9 100644
--- a/Jellyfin.Api/Controllers/YearsController.cs
+++ b/Jellyfin.Api/Controllers/YearsController.cs
@@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using System.Linq;
+using Jellyfin.Api.Constants;
using Jellyfin.Api.Extensions;
using Jellyfin.Api.Helpers;
using Jellyfin.Data.Entities;
@@ -9,6 +10,7 @@ using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Library;
using MediaBrowser.Model.Dto;
using MediaBrowser.Model.Querying;
+using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
@@ -17,6 +19,7 @@ namespace Jellyfin.Api.Controllers
///
/// Years controller.
///
+ [Authorize(Policy = Policies.DefaultAuthorization)]
public class YearsController : BaseJellyfinApiController
{
private readonly ILibraryManager _libraryManager;
diff --git a/Jellyfin.Server/Extensions/ApiServiceCollectionExtensions.cs b/Jellyfin.Server/Extensions/ApiServiceCollectionExtensions.cs
index 6e91042dfd..83d8fac5b5 100644
--- a/Jellyfin.Server/Extensions/ApiServiceCollectionExtensions.cs
+++ b/Jellyfin.Server/Extensions/ApiServiceCollectionExtensions.cs
@@ -7,8 +7,11 @@ using Jellyfin.Api;
using Jellyfin.Api.Auth;
using Jellyfin.Api.Auth.DefaultAuthorizationPolicy;
using Jellyfin.Api.Auth.DownloadPolicy;
+using Jellyfin.Api.Auth.FirstTimeOrIgnoreParentalControlSetupPolicy;
+using Jellyfin.Api.Auth.FirstTimeSetupOrDefaultPolicy;
using Jellyfin.Api.Auth.FirstTimeSetupOrElevatedPolicy;
-using Jellyfin.Api.Auth.IgnoreSchedulePolicy;
+using Jellyfin.Api.Auth.IgnoreParentalControlPolicy;
+using Jellyfin.Api.Auth.LocalAccessOrRequiresElevationPolicy;
using Jellyfin.Api.Auth.LocalAccessPolicy;
using Jellyfin.Api.Auth.RequiresElevationPolicy;
using Jellyfin.Api.Constants;
@@ -41,9 +44,12 @@ namespace Jellyfin.Server.Extensions
{
serviceCollection.AddSingleton();
serviceCollection.AddSingleton();
+ serviceCollection.AddSingleton();
serviceCollection.AddSingleton();
- serviceCollection.AddSingleton();
+ serviceCollection.AddSingleton();
+ serviceCollection.AddSingleton();
serviceCollection.AddSingleton();
+ serviceCollection.AddSingleton();
serviceCollection.AddSingleton();
return serviceCollection.AddAuthorizationCore(options =>
{
@@ -61,6 +67,13 @@ namespace Jellyfin.Server.Extensions
policy.AddAuthenticationSchemes(AuthenticationSchemes.CustomAuthentication);
policy.AddRequirements(new DownloadRequirement());
});
+ options.AddPolicy(
+ Policies.FirstTimeSetupOrDefault,
+ policy =>
+ {
+ policy.AddAuthenticationSchemes(AuthenticationSchemes.CustomAuthentication);
+ policy.AddRequirements(new FirstTimeSetupOrDefaultRequirement());
+ });
options.AddPolicy(
Policies.FirstTimeSetupOrElevated,
policy =>
@@ -69,11 +82,18 @@ namespace Jellyfin.Server.Extensions
policy.AddRequirements(new FirstTimeSetupOrElevatedRequirement());
});
options.AddPolicy(
- Policies.IgnoreSchedule,
+ Policies.IgnoreParentalControl,
policy =>
{
policy.AddAuthenticationSchemes(AuthenticationSchemes.CustomAuthentication);
- policy.AddRequirements(new IgnoreScheduleRequirement());
+ policy.AddRequirements(new IgnoreParentalControlRequirement());
+ });
+ options.AddPolicy(
+ Policies.FirstTimeSetupOrIgnoreParentalControl,
+ policy =>
+ {
+ policy.AddAuthenticationSchemes(AuthenticationSchemes.CustomAuthentication);
+ policy.AddRequirements(new FirstTimeOrIgnoreParentalControlSetupRequirement());
});
options.AddPolicy(
Policies.LocalAccessOnly,
@@ -82,6 +102,13 @@ namespace Jellyfin.Server.Extensions
policy.AddAuthenticationSchemes(AuthenticationSchemes.CustomAuthentication);
policy.AddRequirements(new LocalAccessRequirement());
});
+ options.AddPolicy(
+ Policies.LocalAccessOrRequiresElevation,
+ policy =>
+ {
+ policy.AddAuthenticationSchemes(AuthenticationSchemes.CustomAuthentication);
+ policy.AddRequirements(new LocalAccessOrRequiresElevationRequirement());
+ });
options.AddPolicy(
Policies.RequiresElevation,
policy =>
diff --git a/tests/Jellyfin.Api.Tests/Auth/IgnoreSchedulePolicy/IgnoreScheduleHandlerTests.cs b/tests/Jellyfin.Api.Tests/Auth/IgnoreSchedulePolicy/IgnoreScheduleHandlerTests.cs
index b65d45aa08..7150c90bb8 100644
--- a/tests/Jellyfin.Api.Tests/Auth/IgnoreSchedulePolicy/IgnoreScheduleHandlerTests.cs
+++ b/tests/Jellyfin.Api.Tests/Auth/IgnoreSchedulePolicy/IgnoreScheduleHandlerTests.cs
@@ -3,7 +3,7 @@ using System.Collections.Generic;
using System.Threading.Tasks;
using AutoFixture;
using AutoFixture.AutoMoq;
-using Jellyfin.Api.Auth.IgnoreSchedulePolicy;
+using Jellyfin.Api.Auth.IgnoreParentalControlPolicy;
using Jellyfin.Api.Constants;
using Jellyfin.Data.Entities;
using Jellyfin.Data.Enums;
@@ -20,7 +20,7 @@ namespace Jellyfin.Api.Tests.Auth.IgnoreSchedulePolicy
{
private readonly Mock _configurationManagerMock;
private readonly List _requirements;
- private readonly IgnoreScheduleHandler _sut;
+ private readonly IgnoreParentalControlHandler _sut;
private readonly Mock _userManagerMock;
private readonly Mock _httpContextAccessor;
@@ -33,11 +33,11 @@ namespace Jellyfin.Api.Tests.Auth.IgnoreSchedulePolicy
{
var fixture = new Fixture().Customize(new AutoMoqCustomization());
_configurationManagerMock = fixture.Freeze>();
- _requirements = new List { new IgnoreScheduleRequirement() };
+ _requirements = new List { new IgnoreParentalControlRequirement() };
_userManagerMock = fixture.Freeze>();
_httpContextAccessor = fixture.Freeze>();
- _sut = fixture.Create();
+ _sut = fixture.Create();
}
[Theory]