Merge pull request #3833 from crobibero/api-cleanup-v3

api-migration review fixes
This commit is contained in:
Patrick Barron 2020-08-07 18:08:12 +00:00 committed by GitHub
commit a15be774ac
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
43 changed files with 391 additions and 133 deletions

View file

@ -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
{
/// <summary>
/// Ignore parental control schedule and allow before startup wizard has been completed.
/// </summary>
public class FirstTimeOrIgnoreParentalControlSetupHandler : BaseAuthorizationHandler<IgnoreParentalControlRequirement>
{
private readonly IConfigurationManager _configurationManager;
/// <summary>
/// Initializes a new instance of the <see cref="FirstTimeOrIgnoreParentalControlSetupHandler"/> class.
/// </summary>
/// <param name="userManager">Instance of the <see cref="IUserManager"/> interface.</param>
/// <param name="networkManager">Instance of the <see cref="INetworkManager"/> interface.</param>
/// <param name="httpContextAccessor">Instance of the <see cref="IHttpContextAccessor"/> interface.</param>
/// <param name="configurationManager">Instance of the <see cref="IConfigurationManager"/> interface.</param>
public FirstTimeOrIgnoreParentalControlSetupHandler(
IUserManager userManager,
INetworkManager networkManager,
IHttpContextAccessor httpContextAccessor,
IConfigurationManager configurationManager)
: base(userManager, networkManager, httpContextAccessor)
{
_configurationManager = configurationManager;
}
/// <inheritdoc />
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;
}
}
}

View file

@ -0,0 +1,11 @@
using Microsoft.AspNetCore.Authorization;
namespace Jellyfin.Api.Auth.FirstTimeOrIgnoreParentalControlSetupPolicy
{
/// <summary>
/// First time setup or ignore parental controls requirement.
/// </summary>
public class FirstTimeOrIgnoreParentalControlSetupRequirement : IAuthorizationRequirement
{
}
}

View file

@ -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
{
/// <summary>
/// Authorization handler for requiring first time setup or default privileges.
/// </summary>
public class FirstTimeSetupOrDefaultHandler : BaseAuthorizationHandler<FirstTimeSetupOrDefaultRequirement>
{
private readonly IConfigurationManager _configurationManager;
/// <summary>
/// Initializes a new instance of the <see cref="FirstTimeSetupOrDefaultHandler" /> class.
/// </summary>
/// <param name="configurationManager">Instance of the <see cref="IConfigurationManager"/> interface.</param>
/// <param name="userManager">Instance of the <see cref="IUserManager"/> interface.</param>
/// <param name="networkManager">Instance of the <see cref="INetworkManager"/> interface.</param>
/// <param name="httpContextAccessor">Instance of the <see cref="IHttpContextAccessor"/> interface.</param>
public FirstTimeSetupOrDefaultHandler(
IConfigurationManager configurationManager,
IUserManager userManager,
INetworkManager networkManager,
IHttpContextAccessor httpContextAccessor)
: base(userManager, networkManager, httpContextAccessor)
{
_configurationManager = configurationManager;
}
/// <inheritdoc />
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;
}
}
}

View file

@ -0,0 +1,11 @@
using Microsoft.AspNetCore.Authorization;
namespace Jellyfin.Api.Auth.FirstTimeSetupOrDefaultPolicy
{
/// <summary>
/// The authorization requirement, requiring incomplete first time setup or default privileges, for the authorization handler.
/// </summary>
public class FirstTimeSetupOrDefaultRequirement : IAuthorizationRequirement
{
}
}

View file

@ -4,20 +4,20 @@ using MediaBrowser.Controller.Library;
using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http;
namespace Jellyfin.Api.Auth.IgnoreSchedulePolicy namespace Jellyfin.Api.Auth.IgnoreParentalControlPolicy
{ {
/// <summary> /// <summary>
/// Escape schedule controls handler. /// Escape schedule controls handler.
/// </summary> /// </summary>
public class IgnoreScheduleHandler : BaseAuthorizationHandler<IgnoreScheduleRequirement> public class IgnoreParentalControlHandler : BaseAuthorizationHandler<IgnoreParentalControlRequirement>
{ {
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="IgnoreScheduleHandler"/> class. /// Initializes a new instance of the <see cref="IgnoreParentalControlHandler"/> class.
/// </summary> /// </summary>
/// <param name="userManager">Instance of the <see cref="IUserManager"/> interface.</param> /// <param name="userManager">Instance of the <see cref="IUserManager"/> interface.</param>
/// <param name="networkManager">Instance of the <see cref="INetworkManager"/> interface.</param> /// <param name="networkManager">Instance of the <see cref="INetworkManager"/> interface.</param>
/// <param name="httpContextAccessor">Instance of the <see cref="IHttpContextAccessor"/> interface.</param> /// <param name="httpContextAccessor">Instance of the <see cref="IHttpContextAccessor"/> interface.</param>
public IgnoreScheduleHandler( public IgnoreParentalControlHandler(
IUserManager userManager, IUserManager userManager,
INetworkManager networkManager, INetworkManager networkManager,
IHttpContextAccessor httpContextAccessor) IHttpContextAccessor httpContextAccessor)
@ -26,7 +26,7 @@ namespace Jellyfin.Api.Auth.IgnoreSchedulePolicy
} }
/// <inheritdoc /> /// <inheritdoc />
protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, IgnoreScheduleRequirement requirement) protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, IgnoreParentalControlRequirement requirement)
{ {
var validated = ValidateClaims(context.User, ignoreSchedule: true); var validated = ValidateClaims(context.User, ignoreSchedule: true);
if (!validated) if (!validated)

View file

@ -1,11 +1,11 @@
using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Authorization;
namespace Jellyfin.Api.Auth.IgnoreSchedulePolicy namespace Jellyfin.Api.Auth.IgnoreParentalControlPolicy
{ {
/// <summary> /// <summary>
/// Escape schedule controls requirement. /// Escape schedule controls requirement.
/// </summary> /// </summary>
public class IgnoreScheduleRequirement : IAuthorizationRequirement public class IgnoreParentalControlRequirement : IAuthorizationRequirement
{ {
} }
} }

View file

@ -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
{
/// <summary>
/// Local access or require elevated privileges handler.
/// </summary>
public class LocalAccessOrRequiresElevationHandler : BaseAuthorizationHandler<LocalAccessOrRequiresElevationRequirement>
{
/// <summary>
/// Initializes a new instance of the <see cref="LocalAccessOrRequiresElevationHandler"/> class.
/// </summary>
/// <param name="userManager">Instance of the <see cref="IUserManager"/> interface.</param>
/// <param name="networkManager">Instance of the <see cref="INetworkManager"/> interface.</param>
/// <param name="httpContextAccessor">Instance of the <see cref="IHttpContextAccessor"/> interface.</param>
public LocalAccessOrRequiresElevationHandler(
IUserManager userManager,
INetworkManager networkManager,
IHttpContextAccessor httpContextAccessor)
: base(userManager, networkManager, httpContextAccessor)
{
}
/// <inheritdoc />
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;
}
}
}

View file

@ -0,0 +1,11 @@
using Microsoft.AspNetCore.Authorization;
namespace Jellyfin.Api.Auth.LocalAccessOrRequiresElevationPolicy
{
/// <summary>
/// The local access or elevated privileges authorization requirement.
/// </summary>
public class LocalAccessOrRequiresElevationRequirement : IAuthorizationRequirement
{
}
}

View file

@ -13,7 +13,7 @@ namespace Jellyfin.Api.Constants
/// <summary> /// <summary>
/// Policy name for requiring first time setup or elevated privileges. /// Policy name for requiring first time setup or elevated privileges.
/// </summary> /// </summary>
public const string FirstTimeSetupOrElevated = "FirstTimeOrElevated"; public const string FirstTimeSetupOrElevated = "FirstTimeSetupOrElevated";
/// <summary> /// <summary>
/// Policy name for requiring elevated privileges. /// Policy name for requiring elevated privileges.
@ -28,11 +28,26 @@ namespace Jellyfin.Api.Constants
/// <summary> /// <summary>
/// Policy name for escaping schedule controls. /// Policy name for escaping schedule controls.
/// </summary> /// </summary>
public const string IgnoreSchedule = "IgnoreSchedule"; public const string IgnoreParentalControl = "IgnoreParentalControl";
/// <summary> /// <summary>
/// Policy name for requiring download permission. /// Policy name for requiring download permission.
/// </summary> /// </summary>
public const string Download = "Download"; public const string Download = "Download";
/// <summary>
/// Policy name for requiring first time setup or default permissions.
/// </summary>
public const string FirstTimeSetupOrDefault = "FirstTimeSetupOrDefault";
/// <summary>
/// Policy name for requiring local access or elevated privileges.
/// </summary>
public const string LocalAccessOrRequiresElevation = "LocalAccessOrRequiresElevation";
/// <summary>
/// Policy name for escaping schedule controls or requiring first time setup.
/// </summary>
public const string FirstTimeSetupOrIgnoreParentalControl = "FirstTimeSetupOrIgnoreParentalControl";
} }
} }

View file

@ -88,7 +88,7 @@ namespace Jellyfin.Api.Controllers
[HttpDelete("Keys/{key}")] [HttpDelete("Keys/{key}")]
[Authorize(Policy = Policies.RequiresElevation)] [Authorize(Policy = Policies.RequiresElevation)]
[ProducesResponseType(StatusCodes.Status204NoContent)] [ProducesResponseType(StatusCodes.Status204NoContent)]
public ActionResult RevokeKey([FromRoute] string? key) public ActionResult RevokeKey([FromRoute, Required] string? key)
{ {
_sessionManager.RevokeToken(key); _sessionManager.RevokeToken(key);
return NoContent(); return NoContent();

View file

@ -1,4 +1,5 @@
using System; using System;
using System.ComponentModel.DataAnnotations;
using Jellyfin.Api.Constants; using Jellyfin.Api.Constants;
using Jellyfin.Api.Extensions; using Jellyfin.Api.Extensions;
using Jellyfin.Api.Helpers; using Jellyfin.Api.Helpers;
@ -86,7 +87,7 @@ namespace Jellyfin.Api.Controllers
/// <returns>A <see cref="NoContentResult"/> indicating success.</returns> /// <returns>A <see cref="NoContentResult"/> indicating success.</returns>
[HttpPost("{collectionId}/Items")] [HttpPost("{collectionId}/Items")]
[ProducesResponseType(StatusCodes.Status204NoContent)] [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)); _collectionManager.AddToCollection(collectionId, RequestHelpers.Split(itemIds, ',', true));
return NoContent(); return NoContent();
@ -101,7 +102,7 @@ namespace Jellyfin.Api.Controllers
/// <returns>A <see cref="NoContentResult"/> indicating success.</returns> /// <returns>A <see cref="NoContentResult"/> indicating success.</returns>
[HttpDelete("{collectionId}/Items")] [HttpDelete("{collectionId}/Items")]
[ProducesResponseType(StatusCodes.Status204NoContent)] [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)); _collectionManager.RemoveFromCollection(collectionId, RequestHelpers.Split(itemIds, ',', true));
return NoContent(); return NoContent();

View file

@ -1,3 +1,4 @@
using System.ComponentModel.DataAnnotations;
using System.Text.Json; using System.Text.Json;
using System.Threading.Tasks; using System.Threading.Tasks;
using Jellyfin.Api.Constants; using Jellyfin.Api.Constants;
@ -9,7 +10,6 @@ using MediaBrowser.Model.Configuration;
using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.ModelBinding;
namespace Jellyfin.Api.Controllers namespace Jellyfin.Api.Controllers
{ {
@ -59,7 +59,7 @@ namespace Jellyfin.Api.Controllers
[HttpPost("Configuration")] [HttpPost("Configuration")]
[Authorize(Policy = Policies.RequiresElevation)] [Authorize(Policy = Policies.RequiresElevation)]
[ProducesResponseType(StatusCodes.Status204NoContent)] [ProducesResponseType(StatusCodes.Status204NoContent)]
public ActionResult UpdateConfiguration([FromBody, BindRequired] ServerConfiguration configuration) public ActionResult UpdateConfiguration([FromBody, Required] ServerConfiguration configuration)
{ {
_configurationManager.ReplaceConfiguration(configuration); _configurationManager.ReplaceConfiguration(configuration);
return NoContent(); return NoContent();
@ -117,7 +117,7 @@ namespace Jellyfin.Api.Controllers
[HttpPost("MediaEncoder/Path")] [HttpPost("MediaEncoder/Path")]
[Authorize(Policy = Policies.FirstTimeSetupOrElevated)] [Authorize(Policy = Policies.FirstTimeSetupOrElevated)]
[ProducesResponseType(StatusCodes.Status204NoContent)] [ProducesResponseType(StatusCodes.Status204NoContent)]
public ActionResult UpdateMediaEncoderPath([FromForm, BindRequired] MediaEncoderPathDto mediaEncoderPath) public ActionResult UpdateMediaEncoderPath([FromForm, Required] MediaEncoderPathDto mediaEncoderPath)
{ {
_mediaEncoder.UpdateEncoderPath(mediaEncoderPath.Path, mediaEncoderPath.PathType); _mediaEncoder.UpdateEncoderPath(mediaEncoderPath.Path, mediaEncoderPath.PathType);
return NoContent(); return NoContent();

View file

@ -1,4 +1,5 @@
using System; using System;
using System.ComponentModel.DataAnnotations;
using Jellyfin.Api.Constants; using Jellyfin.Api.Constants;
using MediaBrowser.Controller.Devices; using MediaBrowser.Controller.Devices;
using MediaBrowser.Controller.Security; using MediaBrowser.Controller.Security;
@ -8,7 +9,6 @@ using MediaBrowser.Model.Querying;
using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.ModelBinding;
namespace Jellyfin.Api.Controllers namespace Jellyfin.Api.Controllers
{ {
@ -48,7 +48,7 @@ namespace Jellyfin.Api.Controllers
[HttpGet] [HttpGet]
[Authorize(Policy = Policies.RequiresElevation)] [Authorize(Policy = Policies.RequiresElevation)]
[ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status200OK)]
public ActionResult<QueryResult<DeviceInfo>> GetDevices([FromQuery] bool? supportsSync, [FromQuery] Guid? userId) public ActionResult<QueryResult<DeviceInfo>> GetDevices([FromQuery] bool? supportsSync, [FromQuery, Required] Guid? userId)
{ {
var deviceQuery = new DeviceQuery { SupportsSync = supportsSync, UserId = userId ?? Guid.Empty }; var deviceQuery = new DeviceQuery { SupportsSync = supportsSync, UserId = userId ?? Guid.Empty };
return _deviceManager.GetDevices(deviceQuery); return _deviceManager.GetDevices(deviceQuery);
@ -65,7 +65,7 @@ namespace Jellyfin.Api.Controllers
[Authorize(Policy = Policies.RequiresElevation)] [Authorize(Policy = Policies.RequiresElevation)]
[ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)] [ProducesResponseType(StatusCodes.Status404NotFound)]
public ActionResult<DeviceInfo> GetDeviceInfo([FromQuery, BindRequired] string? id) public ActionResult<DeviceInfo> GetDeviceInfo([FromQuery, Required] string? id)
{ {
var deviceInfo = _deviceManager.GetDevice(id); var deviceInfo = _deviceManager.GetDevice(id);
if (deviceInfo == null) if (deviceInfo == null)
@ -87,7 +87,7 @@ namespace Jellyfin.Api.Controllers
[Authorize(Policy = Policies.RequiresElevation)] [Authorize(Policy = Policies.RequiresElevation)]
[ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)] [ProducesResponseType(StatusCodes.Status404NotFound)]
public ActionResult<DeviceOptions> GetDeviceOptions([FromQuery, BindRequired] string? id) public ActionResult<DeviceOptions> GetDeviceOptions([FromQuery, Required] string? id)
{ {
var deviceInfo = _deviceManager.GetDeviceOptions(id); var deviceInfo = _deviceManager.GetDeviceOptions(id);
if (deviceInfo == null) if (deviceInfo == null)
@ -111,8 +111,8 @@ namespace Jellyfin.Api.Controllers
[ProducesResponseType(StatusCodes.Status204NoContent)] [ProducesResponseType(StatusCodes.Status204NoContent)]
[ProducesResponseType(StatusCodes.Status404NotFound)] [ProducesResponseType(StatusCodes.Status404NotFound)]
public ActionResult UpdateDeviceOptions( public ActionResult UpdateDeviceOptions(
[FromQuery, BindRequired] string? id, [FromQuery, Required] string? id,
[FromBody, BindRequired] DeviceOptions deviceOptions) [FromBody, Required] DeviceOptions deviceOptions)
{ {
var existingDeviceOptions = _deviceManager.GetDeviceOptions(id); var existingDeviceOptions = _deviceManager.GetDeviceOptions(id);
if (existingDeviceOptions == null) if (existingDeviceOptions == null)
@ -134,7 +134,7 @@ namespace Jellyfin.Api.Controllers
[HttpDelete] [HttpDelete]
[ProducesResponseType(StatusCodes.Status204NoContent)] [ProducesResponseType(StatusCodes.Status204NoContent)]
[ProducesResponseType(StatusCodes.Status404NotFound)] [ProducesResponseType(StatusCodes.Status404NotFound)]
public ActionResult DeleteDevice([FromQuery, BindRequired] string? id) public ActionResult DeleteDevice([FromQuery, Required] string? id)
{ {
var existingDevice = _deviceManager.GetDevice(id); var existingDevice = _deviceManager.GetDevice(id);
if (existingDevice == null) if (existingDevice == null)

View file

@ -11,7 +11,6 @@ using MediaBrowser.Model.Entities;
using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.ModelBinding;
namespace Jellyfin.Api.Controllers namespace Jellyfin.Api.Controllers
{ {
@ -99,9 +98,9 @@ namespace Jellyfin.Api.Controllers
[SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "displayPreferencesId", Justification = "Imported from ServiceStack")] [SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "displayPreferencesId", Justification = "Imported from ServiceStack")]
public ActionResult UpdateDisplayPreferences( public ActionResult UpdateDisplayPreferences(
[FromRoute] string? displayPreferencesId, [FromRoute] string? displayPreferencesId,
[FromQuery, BindRequired] Guid userId, [FromQuery, Required] Guid userId,
[FromQuery, BindRequired] string? client, [FromQuery, Required] string? client,
[FromBody, BindRequired] DisplayPreferencesDto displayPreferences) [FromBody, Required] DisplayPreferencesDto displayPreferences)
{ {
HomeSectionType[] defaults = HomeSectionType[] defaults =
{ {

View file

@ -1,5 +1,6 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using Jellyfin.Api.Constants; using Jellyfin.Api.Constants;
@ -8,7 +9,6 @@ using MediaBrowser.Model.IO;
using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.ModelBinding;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
namespace Jellyfin.Api.Controllers namespace Jellyfin.Api.Controllers
@ -47,7 +47,7 @@ namespace Jellyfin.Api.Controllers
[HttpGet("DirectoryContents")] [HttpGet("DirectoryContents")]
[ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status200OK)]
public IEnumerable<FileSystemEntryInfo> GetDirectoryContents( public IEnumerable<FileSystemEntryInfo> GetDirectoryContents(
[FromQuery, BindRequired] string path, [FromQuery, Required] string path,
[FromQuery] bool includeFiles = false, [FromQuery] bool includeFiles = false,
[FromQuery] bool includeDirectories = false) [FromQuery] bool includeDirectories = false)
{ {
@ -75,7 +75,7 @@ namespace Jellyfin.Api.Controllers
[HttpPost("ValidatePath")] [HttpPost("ValidatePath")]
[ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)] [ProducesResponseType(StatusCodes.Status404NotFound)]
public ActionResult ValidatePath([FromBody, BindRequired] ValidatePathDto validatePathDto) public ActionResult ValidatePath([FromBody, Required] ValidatePathDto validatePathDto)
{ {
if (validatePathDto.IsFile.HasValue) if (validatePathDto.IsFile.HasValue)
{ {
@ -154,7 +154,7 @@ namespace Jellyfin.Api.Controllers
/// <returns>Parent path.</returns> /// <returns>Parent path.</returns>
[HttpGet("ParentPath")] [HttpGet("ParentPath")]
[ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status200OK)]
public ActionResult<string?> GetParentPath([FromQuery, BindRequired] string path) public ActionResult<string?> GetParentPath([FromQuery, Required] string path)
{ {
string? parent = Path.GetDirectoryName(path); string? parent = Path.GetDirectoryName(path);
if (string.IsNullOrEmpty(parent)) if (string.IsNullOrEmpty(parent))

View file

@ -1,5 +1,6 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using System.Net.Mime; using System.Net.Mime;
@ -64,7 +65,7 @@ namespace Jellyfin.Api.Controllers
[Produces(MediaTypeNames.Application.Octet)] [Produces(MediaTypeNames.Application.Octet)]
[ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)] [ProducesResponseType(StatusCodes.Status404NotFound)]
public ActionResult<FileStreamResult> GetGeneralImage([FromRoute] string? name, [FromRoute] string? type) public ActionResult<FileStreamResult> GetGeneralImage([FromRoute, Required] string? name, [FromRoute, Required] string? type)
{ {
var filename = string.Equals(type, "primary", StringComparison.OrdinalIgnoreCase) var filename = string.Equals(type, "primary", StringComparison.OrdinalIgnoreCase)
? "folder" ? "folder"
@ -110,8 +111,8 @@ namespace Jellyfin.Api.Controllers
[ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)] [ProducesResponseType(StatusCodes.Status404NotFound)]
public ActionResult<FileStreamResult> GetRatingImage( public ActionResult<FileStreamResult> GetRatingImage(
[FromRoute] string? theme, [FromRoute, Required] string? theme,
[FromRoute] string? name) [FromRoute, Required] string? name)
{ {
return GetImageFile(_applicationPaths.RatingsPath, theme, name); return GetImageFile(_applicationPaths.RatingsPath, theme, name);
} }
@ -143,8 +144,8 @@ namespace Jellyfin.Api.Controllers
[ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)] [ProducesResponseType(StatusCodes.Status404NotFound)]
public ActionResult<FileStreamResult> GetMediaInfoImage( public ActionResult<FileStreamResult> GetMediaInfoImage(
[FromRoute] string? theme, [FromRoute, Required] string? theme,
[FromRoute] string? name) [FromRoute, Required] string? name)
{ {
return GetImageFile(_applicationPaths.MediaInfoImagesPath, theme, name); return GetImageFile(_applicationPaths.MediaInfoImagesPath, theme, name);
} }

View file

@ -84,6 +84,7 @@ namespace Jellyfin.Api.Controllers
/// <returns>A <see cref="NoContentResult"/>.</returns> /// <returns>A <see cref="NoContentResult"/>.</returns>
[HttpPost("Users/{userId}/Images/{imageType}")] [HttpPost("Users/{userId}/Images/{imageType}")]
[HttpPost("Users/{userId}/Images/{imageType}/{index?}", Name = "PostUserImage_2")] [HttpPost("Users/{userId}/Images/{imageType}/{index?}", Name = "PostUserImage_2")]
[Authorize(Policy = Policies.DefaultAuthorization)]
[ProducesResponseType(StatusCodes.Status204NoContent)] [ProducesResponseType(StatusCodes.Status204NoContent)]
[ProducesResponseType(StatusCodes.Status403Forbidden)] [ProducesResponseType(StatusCodes.Status403Forbidden)]
[SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "imageType", Justification = "Imported from ServiceStack")] [SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "imageType", Justification = "Imported from ServiceStack")]
@ -130,6 +131,7 @@ namespace Jellyfin.Api.Controllers
/// <returns>A <see cref="NoContentResult"/>.</returns> /// <returns>A <see cref="NoContentResult"/>.</returns>
[HttpDelete("Users/{userId}/Images/{itemType}")] [HttpDelete("Users/{userId}/Images/{itemType}")]
[HttpDelete("Users/{userId}/Images/{itemType}/{index?}", Name = "DeleteUserImage_2")] [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 = "imageType", Justification = "Imported from ServiceStack")]
[SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "index", Justification = "Imported from ServiceStack")] [SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "index", Justification = "Imported from ServiceStack")]
[ProducesResponseType(StatusCodes.Status204NoContent)] [ProducesResponseType(StatusCodes.Status204NoContent)]
@ -259,6 +261,7 @@ namespace Jellyfin.Api.Controllers
/// <response code="404">Item not found.</response> /// <response code="404">Item not found.</response>
/// <returns>The list of image infos on success, or <see cref="NotFoundResult"/> if item not found.</returns> /// <returns>The list of image infos on success, or <see cref="NotFoundResult"/> if item not found.</returns>
[HttpGet("Items/{itemId}/Images")] [HttpGet("Items/{itemId}/Images")]
[Authorize(Policy = Policies.DefaultAuthorization)]
[ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)] [ProducesResponseType(StatusCodes.Status404NotFound)]
public ActionResult<IEnumerable<ImageInfo>> GetItemImageInfos([FromRoute] Guid itemId) public ActionResult<IEnumerable<ImageInfo>> GetItemImageInfos([FromRoute] Guid itemId)

View file

@ -1,5 +1,6 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Linq; using System.Linq;
using Jellyfin.Api.Constants; using Jellyfin.Api.Constants;
using Jellyfin.Api.Extensions; using Jellyfin.Api.Extensions;
@ -174,7 +175,7 @@ namespace Jellyfin.Api.Controllers
[HttpGet("MusicGenres/{name}/InstantMix")] [HttpGet("MusicGenres/{name}/InstantMix")]
[ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status200OK)]
public ActionResult<QueryResult<BaseItemDto>> GetInstantMixFromMusicGenre( public ActionResult<QueryResult<BaseItemDto>> GetInstantMixFromMusicGenre(
[FromRoute] string? name, [FromRoute, Required] string? name,
[FromQuery] Guid? userId, [FromQuery] Guid? userId,
[FromQuery] int? limit, [FromQuery] int? limit,
[FromQuery] string? fields, [FromQuery] string? fields,

View file

@ -22,7 +22,6 @@ using MediaBrowser.Model.Providers;
using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.ModelBinding;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
namespace Jellyfin.Api.Controllers namespace Jellyfin.Api.Controllers
@ -94,7 +93,7 @@ namespace Jellyfin.Api.Controllers
/// The task result contains an <see cref="OkResult"/> containing the list of remote search results. /// The task result contains an <see cref="OkResult"/> containing the list of remote search results.
/// </returns> /// </returns>
[HttpPost("Items/RemoteSearch/Movie")] [HttpPost("Items/RemoteSearch/Movie")]
public async Task<ActionResult<IEnumerable<RemoteSearchResult>>> GetMovieRemoteSearchResults([FromBody, BindRequired] RemoteSearchQuery<MovieInfo> query) public async Task<ActionResult<IEnumerable<RemoteSearchResult>>> GetMovieRemoteSearchResults([FromBody, Required] RemoteSearchQuery<MovieInfo> query)
{ {
var results = await _providerManager.GetRemoteSearchResults<Movie, MovieInfo>(query, CancellationToken.None) var results = await _providerManager.GetRemoteSearchResults<Movie, MovieInfo>(query, CancellationToken.None)
.ConfigureAwait(false); .ConfigureAwait(false);
@ -111,7 +110,7 @@ namespace Jellyfin.Api.Controllers
/// The task result contains an <see cref="OkResult"/> containing the list of remote search results. /// The task result contains an <see cref="OkResult"/> containing the list of remote search results.
/// </returns> /// </returns>
[HttpPost("Items/RemoteSearch/Trailer")] [HttpPost("Items/RemoteSearch/Trailer")]
public async Task<ActionResult<IEnumerable<RemoteSearchResult>>> GetTrailerRemoteSearchResults([FromBody, BindRequired] RemoteSearchQuery<TrailerInfo> query) public async Task<ActionResult<IEnumerable<RemoteSearchResult>>> GetTrailerRemoteSearchResults([FromBody, Required] RemoteSearchQuery<TrailerInfo> query)
{ {
var results = await _providerManager.GetRemoteSearchResults<Trailer, TrailerInfo>(query, CancellationToken.None) var results = await _providerManager.GetRemoteSearchResults<Trailer, TrailerInfo>(query, CancellationToken.None)
.ConfigureAwait(false); .ConfigureAwait(false);
@ -128,7 +127,7 @@ namespace Jellyfin.Api.Controllers
/// The task result contains an <see cref="OkResult"/> containing the list of remote search results. /// The task result contains an <see cref="OkResult"/> containing the list of remote search results.
/// </returns> /// </returns>
[HttpPost("Items/RemoteSearch/MusicVideo")] [HttpPost("Items/RemoteSearch/MusicVideo")]
public async Task<ActionResult<IEnumerable<RemoteSearchResult>>> GetMusicVideoRemoteSearchResults([FromBody, BindRequired] RemoteSearchQuery<MusicVideoInfo> query) public async Task<ActionResult<IEnumerable<RemoteSearchResult>>> GetMusicVideoRemoteSearchResults([FromBody, Required] RemoteSearchQuery<MusicVideoInfo> query)
{ {
var results = await _providerManager.GetRemoteSearchResults<MusicVideo, MusicVideoInfo>(query, CancellationToken.None) var results = await _providerManager.GetRemoteSearchResults<MusicVideo, MusicVideoInfo>(query, CancellationToken.None)
.ConfigureAwait(false); .ConfigureAwait(false);
@ -145,7 +144,7 @@ namespace Jellyfin.Api.Controllers
/// The task result contains an <see cref="OkResult"/> containing the list of remote search results. /// The task result contains an <see cref="OkResult"/> containing the list of remote search results.
/// </returns> /// </returns>
[HttpPost("Items/RemoteSearch/Series")] [HttpPost("Items/RemoteSearch/Series")]
public async Task<ActionResult<IEnumerable<RemoteSearchResult>>> GetSeriesRemoteSearchResults([FromBody, BindRequired] RemoteSearchQuery<SeriesInfo> query) public async Task<ActionResult<IEnumerable<RemoteSearchResult>>> GetSeriesRemoteSearchResults([FromBody, Required] RemoteSearchQuery<SeriesInfo> query)
{ {
var results = await _providerManager.GetRemoteSearchResults<Series, SeriesInfo>(query, CancellationToken.None) var results = await _providerManager.GetRemoteSearchResults<Series, SeriesInfo>(query, CancellationToken.None)
.ConfigureAwait(false); .ConfigureAwait(false);
@ -162,7 +161,7 @@ namespace Jellyfin.Api.Controllers
/// The task result contains an <see cref="OkResult"/> containing the list of remote search results. /// The task result contains an <see cref="OkResult"/> containing the list of remote search results.
/// </returns> /// </returns>
[HttpPost("Items/RemoteSearch/BoxSet")] [HttpPost("Items/RemoteSearch/BoxSet")]
public async Task<ActionResult<IEnumerable<RemoteSearchResult>>> GetBoxSetRemoteSearchResults([FromBody, BindRequired] RemoteSearchQuery<BoxSetInfo> query) public async Task<ActionResult<IEnumerable<RemoteSearchResult>>> GetBoxSetRemoteSearchResults([FromBody, Required] RemoteSearchQuery<BoxSetInfo> query)
{ {
var results = await _providerManager.GetRemoteSearchResults<BoxSet, BoxSetInfo>(query, CancellationToken.None) var results = await _providerManager.GetRemoteSearchResults<BoxSet, BoxSetInfo>(query, CancellationToken.None)
.ConfigureAwait(false); .ConfigureAwait(false);
@ -179,7 +178,7 @@ namespace Jellyfin.Api.Controllers
/// The task result contains an <see cref="OkResult"/> containing the list of remote search results. /// The task result contains an <see cref="OkResult"/> containing the list of remote search results.
/// </returns> /// </returns>
[HttpPost("Items/RemoteSearch/MusicArtist")] [HttpPost("Items/RemoteSearch/MusicArtist")]
public async Task<ActionResult<IEnumerable<RemoteSearchResult>>> GetMusicArtistRemoteSearchResults([FromBody, BindRequired] RemoteSearchQuery<ArtistInfo> query) public async Task<ActionResult<IEnumerable<RemoteSearchResult>>> GetMusicArtistRemoteSearchResults([FromBody, Required] RemoteSearchQuery<ArtistInfo> query)
{ {
var results = await _providerManager.GetRemoteSearchResults<MusicArtist, ArtistInfo>(query, CancellationToken.None) var results = await _providerManager.GetRemoteSearchResults<MusicArtist, ArtistInfo>(query, CancellationToken.None)
.ConfigureAwait(false); .ConfigureAwait(false);
@ -196,7 +195,7 @@ namespace Jellyfin.Api.Controllers
/// The task result contains an <see cref="OkResult"/> containing the list of remote search results. /// The task result contains an <see cref="OkResult"/> containing the list of remote search results.
/// </returns> /// </returns>
[HttpPost("Items/RemoteSearch/MusicAlbum")] [HttpPost("Items/RemoteSearch/MusicAlbum")]
public async Task<ActionResult<IEnumerable<RemoteSearchResult>>> GetMusicAlbumRemoteSearchResults([FromBody, BindRequired] RemoteSearchQuery<AlbumInfo> query) public async Task<ActionResult<IEnumerable<RemoteSearchResult>>> GetMusicAlbumRemoteSearchResults([FromBody, Required] RemoteSearchQuery<AlbumInfo> query)
{ {
var results = await _providerManager.GetRemoteSearchResults<MusicAlbum, AlbumInfo>(query, CancellationToken.None) var results = await _providerManager.GetRemoteSearchResults<MusicAlbum, AlbumInfo>(query, CancellationToken.None)
.ConfigureAwait(false); .ConfigureAwait(false);
@ -214,7 +213,7 @@ namespace Jellyfin.Api.Controllers
/// </returns> /// </returns>
[HttpPost("Items/RemoteSearch/Person")] [HttpPost("Items/RemoteSearch/Person")]
[Authorize(Policy = Policies.RequiresElevation)] [Authorize(Policy = Policies.RequiresElevation)]
public async Task<ActionResult<IEnumerable<RemoteSearchResult>>> GetPersonRemoteSearchResults([FromBody, BindRequired] RemoteSearchQuery<PersonLookupInfo> query) public async Task<ActionResult<IEnumerable<RemoteSearchResult>>> GetPersonRemoteSearchResults([FromBody, Required] RemoteSearchQuery<PersonLookupInfo> query)
{ {
var results = await _providerManager.GetRemoteSearchResults<Person, PersonLookupInfo>(query, CancellationToken.None) var results = await _providerManager.GetRemoteSearchResults<Person, PersonLookupInfo>(query, CancellationToken.None)
.ConfigureAwait(false); .ConfigureAwait(false);
@ -231,7 +230,7 @@ namespace Jellyfin.Api.Controllers
/// The task result contains an <see cref="OkResult"/> containing the list of remote search results. /// The task result contains an <see cref="OkResult"/> containing the list of remote search results.
/// </returns> /// </returns>
[HttpPost("Items/RemoteSearch/Book")] [HttpPost("Items/RemoteSearch/Book")]
public async Task<ActionResult<IEnumerable<RemoteSearchResult>>> GetBookRemoteSearchResults([FromBody, BindRequired] RemoteSearchQuery<BookInfo> query) public async Task<ActionResult<IEnumerable<RemoteSearchResult>>> GetBookRemoteSearchResults([FromBody, Required] RemoteSearchQuery<BookInfo> query)
{ {
var results = await _providerManager.GetRemoteSearchResults<Book, BookInfo>(query, CancellationToken.None) var results = await _providerManager.GetRemoteSearchResults<Book, BookInfo>(query, CancellationToken.None)
.ConfigureAwait(false); .ConfigureAwait(false);
@ -296,7 +295,7 @@ namespace Jellyfin.Api.Controllers
[Authorize(Policy = Policies.RequiresElevation)] [Authorize(Policy = Policies.RequiresElevation)]
public async Task<ActionResult> ApplySearchCriteria( public async Task<ActionResult> ApplySearchCriteria(
[FromRoute] Guid itemId, [FromRoute] Guid itemId,
[FromBody, BindRequired] RemoteSearchResult searchResult, [FromBody, Required] RemoteSearchResult searchResult,
[FromQuery] bool replaceAllImages = true) [FromQuery] bool replaceAllImages = true)
{ {
var item = _libraryManager.GetItemById(itemId); var item = _libraryManager.GetItemById(itemId);

View file

@ -1,5 +1,6 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Linq; using System.Linq;
using System.Threading; using System.Threading;
using Jellyfin.Api.Constants; using Jellyfin.Api.Constants;
@ -17,7 +18,6 @@ using MediaBrowser.Model.IO;
using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.ModelBinding;
namespace Jellyfin.Api.Controllers namespace Jellyfin.Api.Controllers
{ {
@ -67,7 +67,7 @@ namespace Jellyfin.Api.Controllers
[HttpPost("Items/{itemId}")] [HttpPost("Items/{itemId}")]
[ProducesResponseType(StatusCodes.Status204NoContent)] [ProducesResponseType(StatusCodes.Status204NoContent)]
[ProducesResponseType(StatusCodes.Status404NotFound)] [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); var item = _libraryManager.GetItemById(itemId);
if (item == null) if (item == null)
@ -194,7 +194,7 @@ namespace Jellyfin.Api.Controllers
[HttpPost("Items/{itemId}/ContentType")] [HttpPost("Items/{itemId}/ContentType")]
[ProducesResponseType(StatusCodes.Status204NoContent)] [ProducesResponseType(StatusCodes.Status204NoContent)]
[ProducesResponseType(StatusCodes.Status404NotFound)] [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); var item = _libraryManager.GetItemById(itemId);
if (item == null) if (item == null)

View file

@ -1,5 +1,6 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Globalization; using System.Globalization;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
@ -32,7 +33,6 @@ using MediaBrowser.Model.Querying;
using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.ModelBinding;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using Book = MediaBrowser.Controller.Entities.Book; using Book = MediaBrowser.Controller.Entities.Book;
using Movie = Jellyfin.Data.Entities.Movie; using Movie = Jellyfin.Data.Entities.Movie;
@ -597,7 +597,7 @@ namespace Jellyfin.Api.Controllers
[HttpPost("Library/Media/Updated")] [HttpPost("Library/Media/Updated")]
[Authorize(Policy = Policies.DefaultAuthorization)] [Authorize(Policy = Policies.DefaultAuthorization)]
[ProducesResponseType(StatusCodes.Status204NoContent)] [ProducesResponseType(StatusCodes.Status204NoContent)]
public ActionResult PostUpdatedMedia([FromBody, BindRequired] MediaUpdateInfoDto[] updates) public ActionResult PostUpdatedMedia([FromBody, Required] MediaUpdateInfoDto[] updates)
{ {
foreach (var item in updates) foreach (var item in updates)
{ {
@ -685,6 +685,7 @@ namespace Jellyfin.Api.Controllers
[HttpGet("Shows/{itemId}/Similar", Name = "GetSimilarShows2")] [HttpGet("Shows/{itemId}/Similar", Name = "GetSimilarShows2")]
[HttpGet("Movies/{itemId}/Similar", Name = "GetSimilarMovies2")] [HttpGet("Movies/{itemId}/Similar", Name = "GetSimilarMovies2")]
[HttpGet("Trailers/{itemId}/Similar", Name = "GetSimilarTrailers2")] [HttpGet("Trailers/{itemId}/Similar", Name = "GetSimilarTrailers2")]
[Authorize(Policy = Policies.DefaultAuthorization)]
[ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status200OK)]
public ActionResult<QueryResult<BaseItemDto>> GetSimilarItems( public ActionResult<QueryResult<BaseItemDto>> GetSimilarItems(
[FromRoute] Guid itemId, [FromRoute] Guid itemId,
@ -736,11 +737,11 @@ namespace Jellyfin.Api.Controllers
/// <response code="200">Library options info returned.</response> /// <response code="200">Library options info returned.</response>
/// <returns>Library options info.</returns> /// <returns>Library options info.</returns>
[HttpGet("Libraries/AvailableOptions")] [HttpGet("Libraries/AvailableOptions")]
[Authorize(Policy = Policies.FirstTimeSetupOrElevated)] [Authorize(Policy = Policies.FirstTimeSetupOrDefault)]
[ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status200OK)]
public ActionResult<LibraryOptionsResultDto> GetLibraryOptionsInfo( public ActionResult<LibraryOptionsResultDto> GetLibraryOptionsInfo(
[FromQuery] string? libraryContentType, [FromQuery] string? libraryContentType,
[FromQuery] bool isNewLibrary = false) [FromQuery] bool isNewLibrary)
{ {
var result = new LibraryOptionsResultDto(); var result = new LibraryOptionsResultDto();

View file

@ -1,5 +1,6 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Globalization; using System.Globalization;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
@ -17,7 +18,6 @@ using MediaBrowser.Model.Entities;
using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.ModelBinding;
namespace Jellyfin.Api.Controllers namespace Jellyfin.Api.Controllers
{ {
@ -204,7 +204,7 @@ namespace Jellyfin.Api.Controllers
[HttpPost("Paths")] [HttpPost("Paths")]
[ProducesResponseType(StatusCodes.Status204NoContent)] [ProducesResponseType(StatusCodes.Status204NoContent)]
public ActionResult AddMediaPath( public ActionResult AddMediaPath(
[FromBody, BindRequired] MediaPathDto mediaPathDto, [FromBody, Required] MediaPathDto mediaPathDto,
[FromQuery] bool refreshLibrary = false) [FromQuery] bool refreshLibrary = false)
{ {
_libraryMonitor.Stop(); _libraryMonitor.Stop();

View file

@ -11,7 +11,7 @@ namespace Jellyfin.Api.Controllers
/// <summary> /// <summary>
/// Localization controller. /// Localization controller.
/// </summary> /// </summary>
[Authorize(Policy = Policies.FirstTimeSetupOrElevated)] [Authorize(Policy = Policies.FirstTimeSetupOrDefault)]
public class LocalizationController : BaseJellyfinApiController public class LocalizationController : BaseJellyfinApiController
{ {
private readonly ILocalizationManager _localization; private readonly ILocalizationManager _localization;

View file

@ -1,5 +1,6 @@
using System; using System;
using System.Buffers; using System.Buffers;
using System.ComponentModel.DataAnnotations;
using System.Globalization; using System.Globalization;
using System.Linq; using System.Linq;
using System.Net.Mime; using System.Net.Mime;
@ -91,7 +92,7 @@ namespace Jellyfin.Api.Controllers
/// <returns>A <see cref="Task"/> containing a <see cref="PlaybackInfoResponse"/> with the playback information.</returns> /// <returns>A <see cref="Task"/> containing a <see cref="PlaybackInfoResponse"/> with the playback information.</returns>
[HttpGet("Items/{itemId}/PlaybackInfo")] [HttpGet("Items/{itemId}/PlaybackInfo")]
[ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status200OK)]
public async Task<ActionResult<PlaybackInfoResponse>> GetPlaybackInfo([FromRoute] Guid itemId, [FromQuery] Guid? userId) public async Task<ActionResult<PlaybackInfoResponse>> GetPlaybackInfo([FromRoute] Guid itemId, [FromQuery, Required] Guid? userId)
{ {
return await GetPlaybackInfoInternal(itemId, userId).ConfigureAwait(false); return await GetPlaybackInfoInternal(itemId, userId).ConfigureAwait(false);
} }
@ -281,7 +282,7 @@ namespace Jellyfin.Api.Controllers
/// <returns>A <see cref="NoContentResult"/> indicating success.</returns> /// <returns>A <see cref="NoContentResult"/> indicating success.</returns>
[HttpPost("LiveStreams/Close")] [HttpPost("LiveStreams/Close")]
[ProducesResponseType(StatusCodes.Status204NoContent)] [ProducesResponseType(StatusCodes.Status204NoContent)]
public ActionResult CloseLiveStream([FromQuery] string? liveStreamId) public ActionResult CloseLiveStream([FromQuery, Required] string? liveStreamId)
{ {
_mediaSourceManager.CloseLiveStream(liveStreamId).GetAwaiter().GetResult(); _mediaSourceManager.CloseLiveStream(liveStreamId).GetAwaiter().GetResult();
return NoContent(); return NoContent();

View file

@ -1,13 +1,16 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Linq; using System.Linq;
using System.Threading; using System.Threading;
using Jellyfin.Api.Constants;
using Jellyfin.Api.Models.NotificationDtos; using Jellyfin.Api.Models.NotificationDtos;
using Jellyfin.Data.Enums; using Jellyfin.Data.Enums;
using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.Notifications; using MediaBrowser.Controller.Notifications;
using MediaBrowser.Model.Dto; using MediaBrowser.Model.Dto;
using MediaBrowser.Model.Notifications; using MediaBrowser.Model.Notifications;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
@ -16,6 +19,7 @@ namespace Jellyfin.Api.Controllers
/// <summary> /// <summary>
/// The notification controller. /// The notification controller.
/// </summary> /// </summary>
[Authorize(Policy = Policies.DefaultAuthorization)]
public class NotificationsController : BaseJellyfinApiController public class NotificationsController : BaseJellyfinApiController
{ {
private readonly INotificationManager _notificationManager; private readonly INotificationManager _notificationManager;
@ -83,19 +87,19 @@ namespace Jellyfin.Api.Controllers
/// <summary> /// <summary>
/// Sends a notification to all admins. /// Sends a notification to all admins.
/// </summary> /// </summary>
/// <param name="name">The name of the notification.</param>
/// <param name="description">The description of the notification.</param>
/// <param name="url">The URL of the notification.</param> /// <param name="url">The URL of the notification.</param>
/// <param name="level">The level of the notification.</param> /// <param name="level">The level of the notification.</param>
/// <param name="name">The name of the notification.</param>
/// <param name="description">The description of the notification.</param>
/// <response code="204">Notification sent.</response> /// <response code="204">Notification sent.</response>
/// <returns>A <cref see="NoContentResult"/>.</returns> /// <returns>A <cref see="NoContentResult"/>.</returns>
[HttpPost("Admin")] [HttpPost("Admin")]
[ProducesResponseType(StatusCodes.Status204NoContent)] [ProducesResponseType(StatusCodes.Status204NoContent)]
public ActionResult CreateAdminNotification( public ActionResult CreateAdminNotification(
[FromQuery] string? name,
[FromQuery] string? description,
[FromQuery] string? url, [FromQuery] string? url,
[FromQuery] NotificationLevel? level) [FromQuery] NotificationLevel? level,
[FromQuery] string name = "",
[FromQuery] string description = "")
{ {
var notification = new NotificationRequest var notification = new NotificationRequest
{ {

View file

@ -127,7 +127,6 @@ namespace Jellyfin.Api.Controllers
/// <response code="200">Package repositories returned.</response> /// <response code="200">Package repositories returned.</response>
/// <returns>An <see cref="OkResult"/> containing the list of package repositories.</returns> /// <returns>An <see cref="OkResult"/> containing the list of package repositories.</returns>
[HttpGet("Repositories")] [HttpGet("Repositories")]
[Authorize(Policy = Policies.DefaultAuthorization)]
[ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status200OK)]
public ActionResult<IEnumerable<RepositoryInfo>> GetRepositories() public ActionResult<IEnumerable<RepositoryInfo>> GetRepositories()
{ {

View file

@ -1,6 +1,7 @@
using System; using System;
using System.Globalization; using System.Globalization;
using System.Linq; using System.Linq;
using Jellyfin.Api.Constants;
using Jellyfin.Api.Extensions; using Jellyfin.Api.Extensions;
using Jellyfin.Api.Helpers; using Jellyfin.Api.Helpers;
using Jellyfin.Data.Entities; using Jellyfin.Data.Entities;
@ -9,6 +10,7 @@ using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Library;
using MediaBrowser.Model.Dto; using MediaBrowser.Model.Dto;
using MediaBrowser.Model.Querying; using MediaBrowser.Model.Querying;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
@ -17,6 +19,7 @@ namespace Jellyfin.Api.Controllers
/// <summary> /// <summary>
/// Persons controller. /// Persons controller.
/// </summary> /// </summary>
[Authorize(Policy = Policies.DefaultAuthorization)]
public class PersonsController : BaseJellyfinApiController public class PersonsController : BaseJellyfinApiController
{ {
private readonly ILibraryManager _libraryManager; private readonly ILibraryManager _libraryManager;

View file

@ -1,4 +1,5 @@
using System; using System;
using System.ComponentModel.DataAnnotations;
using System.Linq; using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
using Jellyfin.Api.Constants; using Jellyfin.Api.Constants;
@ -14,7 +15,6 @@ using MediaBrowser.Model.Querying;
using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.ModelBinding;
namespace Jellyfin.Api.Controllers namespace Jellyfin.Api.Controllers
{ {
@ -59,7 +59,7 @@ namespace Jellyfin.Api.Controllers
[HttpPost] [HttpPost]
[ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status200OK)]
public async Task<ActionResult<PlaylistCreationResult>> CreatePlaylist( public async Task<ActionResult<PlaylistCreationResult>> CreatePlaylist(
[FromBody, BindRequired] CreatePlaylistDto createPlaylistRequest) [FromBody, Required] CreatePlaylistDto createPlaylistRequest)
{ {
Guid[] idGuidArray = RequestHelpers.GetGuids(createPlaylistRequest.Ids); Guid[] idGuidArray = RequestHelpers.GetGuids(createPlaylistRequest.Ids);
var result = await _playlistManager.CreatePlaylist(new PlaylistCreationRequest var result = await _playlistManager.CreatePlaylist(new PlaylistCreationRequest

View file

@ -1,5 +1,6 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Linq; using System.Linq;
using System.Text.Json; using System.Text.Json;
using System.Threading.Tasks; using System.Threading.Tasks;
@ -13,7 +14,6 @@ using MediaBrowser.Model.Plugins;
using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.ModelBinding;
namespace Jellyfin.Api.Controllers namespace Jellyfin.Api.Controllers
{ {
@ -154,7 +154,7 @@ namespace Jellyfin.Api.Controllers
[HttpPost("SecurityInfo")] [HttpPost("SecurityInfo")]
[Authorize(Policy = Policies.RequiresElevation)] [Authorize(Policy = Policies.RequiresElevation)]
[ProducesResponseType(StatusCodes.Status204NoContent)] [ProducesResponseType(StatusCodes.Status204NoContent)]
public ActionResult UpdatePluginSecurityInfo([FromBody, BindRequired] PluginSecurityInfo pluginSecurityInfo) public ActionResult UpdatePluginSecurityInfo([FromBody, Required] PluginSecurityInfo pluginSecurityInfo)
{ {
return NoContent(); return NoContent();
} }

View file

@ -1,5 +1,6 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using System.Net.Mime; using System.Net.Mime;
@ -18,7 +19,6 @@ using MediaBrowser.Model.Providers;
using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.ModelBinding;
namespace Jellyfin.Api.Controllers namespace Jellyfin.Api.Controllers
{ {
@ -154,7 +154,7 @@ namespace Jellyfin.Api.Controllers
[Produces(MediaTypeNames.Application.Octet)] [Produces(MediaTypeNames.Application.Octet)]
[ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)] [ProducesResponseType(StatusCodes.Status404NotFound)]
public async Task<ActionResult> GetRemoteImage([FromQuery, BindRequired] string imageUrl) public async Task<ActionResult> GetRemoteImage([FromQuery, Required] string imageUrl)
{ {
var urlHash = imageUrl.GetMD5(); var urlHash = imageUrl.GetMD5();
var pointerCachePath = GetFullCachePath(urlHash.ToString()); var pointerCachePath = GetFullCachePath(urlHash.ToString());
@ -209,7 +209,7 @@ namespace Jellyfin.Api.Controllers
[ProducesResponseType(StatusCodes.Status404NotFound)] [ProducesResponseType(StatusCodes.Status404NotFound)]
public async Task<ActionResult> DownloadRemoteImage( public async Task<ActionResult> DownloadRemoteImage(
[FromRoute] Guid itemId, [FromRoute] Guid itemId,
[FromQuery, BindRequired] ImageType type, [FromQuery, Required] ImageType type,
[FromQuery] string? imageUrl) [FromQuery] string? imageUrl)
{ {
var item = _libraryManager.GetItemById(itemId); var item = _libraryManager.GetItemById(itemId);

View file

@ -1,12 +1,12 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Linq; using System.Linq;
using Jellyfin.Api.Constants; using Jellyfin.Api.Constants;
using MediaBrowser.Model.Tasks; using MediaBrowser.Model.Tasks;
using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.ModelBinding;
namespace Jellyfin.Api.Controllers namespace Jellyfin.Api.Controllers
{ {
@ -71,7 +71,7 @@ namespace Jellyfin.Api.Controllers
[HttpGet("{taskId}")] [HttpGet("{taskId}")]
[ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)] [ProducesResponseType(StatusCodes.Status404NotFound)]
public ActionResult<TaskInfo> GetTask([FromRoute] string? taskId) public ActionResult<TaskInfo> GetTask([FromRoute, Required] string? taskId)
{ {
var task = _taskManager.ScheduledTasks.FirstOrDefault(i => var task = _taskManager.ScheduledTasks.FirstOrDefault(i =>
string.Equals(i.Id, taskId, StringComparison.OrdinalIgnoreCase)); string.Equals(i.Id, taskId, StringComparison.OrdinalIgnoreCase));
@ -118,7 +118,7 @@ namespace Jellyfin.Api.Controllers
[HttpDelete("Running/{taskId}")] [HttpDelete("Running/{taskId}")]
[ProducesResponseType(StatusCodes.Status204NoContent)] [ProducesResponseType(StatusCodes.Status204NoContent)]
[ProducesResponseType(StatusCodes.Status404NotFound)] [ProducesResponseType(StatusCodes.Status404NotFound)]
public ActionResult StopTask([FromRoute] string? taskId) public ActionResult StopTask([FromRoute, Required] string? taskId)
{ {
var task = _taskManager.ScheduledTasks.FirstOrDefault(o => var task = _taskManager.ScheduledTasks.FirstOrDefault(o =>
o.Id.Equals(taskId, StringComparison.OrdinalIgnoreCase)); o.Id.Equals(taskId, StringComparison.OrdinalIgnoreCase));
@ -144,8 +144,8 @@ namespace Jellyfin.Api.Controllers
[ProducesResponseType(StatusCodes.Status204NoContent)] [ProducesResponseType(StatusCodes.Status204NoContent)]
[ProducesResponseType(StatusCodes.Status404NotFound)] [ProducesResponseType(StatusCodes.Status404NotFound)]
public ActionResult UpdateTask( public ActionResult UpdateTask(
[FromRoute] string? taskId, [FromRoute, Required] string? taskId,
[FromBody, BindRequired] TaskTriggerInfo[] triggerInfos) [FromBody, Required] TaskTriggerInfo[] triggerInfos)
{ {
var task = _taskManager.ScheduledTasks.FirstOrDefault(o => var task = _taskManager.ScheduledTasks.FirstOrDefault(o =>
o.Id.Equals(taskId, StringComparison.OrdinalIgnoreCase)); o.Id.Equals(taskId, StringComparison.OrdinalIgnoreCase));

View file

@ -122,12 +122,13 @@ namespace Jellyfin.Api.Controllers
/// <response code="204">Instruction sent to session.</response> /// <response code="204">Instruction sent to session.</response>
/// <returns>A <see cref="NoContentResult"/>.</returns> /// <returns>A <see cref="NoContentResult"/>.</returns>
[HttpPost("Sessions/{sessionId}/Viewing")] [HttpPost("Sessions/{sessionId}/Viewing")]
[Authorize(Policy = Policies.DefaultAuthorization)]
[ProducesResponseType(StatusCodes.Status204NoContent)] [ProducesResponseType(StatusCodes.Status204NoContent)]
public ActionResult DisplayContent( public ActionResult DisplayContent(
[FromRoute] string? sessionId, [FromRoute, Required] string? sessionId,
[FromQuery] string? itemType, [FromQuery, Required] string? itemType,
[FromQuery] string? itemId, [FromQuery, Required] string? itemId,
[FromQuery] string? itemName) [FromQuery, Required] string? itemName)
{ {
var command = new BrowseRequest var command = new BrowseRequest
{ {
@ -156,9 +157,10 @@ namespace Jellyfin.Api.Controllers
/// <response code="204">Instruction sent to session.</response> /// <response code="204">Instruction sent to session.</response>
/// <returns>A <see cref="NoContentResult"/>.</returns> /// <returns>A <see cref="NoContentResult"/>.</returns>
[HttpPost("Sessions/{sessionId}/Playing")] [HttpPost("Sessions/{sessionId}/Playing")]
[Authorize(Policy = Policies.DefaultAuthorization)]
[ProducesResponseType(StatusCodes.Status204NoContent)] [ProducesResponseType(StatusCodes.Status204NoContent)]
public ActionResult Play( public ActionResult Play(
[FromRoute] string? sessionId, [FromRoute, Required] string? sessionId,
[FromQuery] Guid[] itemIds, [FromQuery] Guid[] itemIds,
[FromQuery] long? startPositionTicks, [FromQuery] long? startPositionTicks,
[FromQuery] PlayCommand playCommand, [FromQuery] PlayCommand playCommand,
@ -190,9 +192,10 @@ namespace Jellyfin.Api.Controllers
/// <response code="204">Playstate command sent to session.</response> /// <response code="204">Playstate command sent to session.</response>
/// <returns>A <see cref="NoContentResult"/>.</returns> /// <returns>A <see cref="NoContentResult"/>.</returns>
[HttpPost("Sessions/{sessionId}/Playing/{command}")] [HttpPost("Sessions/{sessionId}/Playing/{command}")]
[Authorize(Policy = Policies.DefaultAuthorization)]
[ProducesResponseType(StatusCodes.Status204NoContent)] [ProducesResponseType(StatusCodes.Status204NoContent)]
public ActionResult SendPlaystateCommand( public ActionResult SendPlaystateCommand(
[FromRoute] string? sessionId, [FromRoute, Required] string? sessionId,
[FromBody] PlaystateRequest playstateRequest) [FromBody] PlaystateRequest playstateRequest)
{ {
_sessionManager.SendPlaystateCommand( _sessionManager.SendPlaystateCommand(
@ -212,10 +215,11 @@ namespace Jellyfin.Api.Controllers
/// <response code="204">System command sent to session.</response> /// <response code="204">System command sent to session.</response>
/// <returns>A <see cref="NoContentResult"/>.</returns> /// <returns>A <see cref="NoContentResult"/>.</returns>
[HttpPost("Sessions/{sessionId}/System/{command}")] [HttpPost("Sessions/{sessionId}/System/{command}")]
[Authorize(Policy = Policies.DefaultAuthorization)]
[ProducesResponseType(StatusCodes.Status204NoContent)] [ProducesResponseType(StatusCodes.Status204NoContent)]
public ActionResult SendSystemCommand( public ActionResult SendSystemCommand(
[FromRoute] string? sessionId, [FromRoute, Required] string? sessionId,
[FromRoute] string? command) [FromRoute, Required] string? command)
{ {
var name = command; var name = command;
if (Enum.TryParse(name, true, out GeneralCommandType commandType)) if (Enum.TryParse(name, true, out GeneralCommandType commandType))
@ -243,10 +247,11 @@ namespace Jellyfin.Api.Controllers
/// <response code="204">General command sent to session.</response> /// <response code="204">General command sent to session.</response>
/// <returns>A <see cref="NoContentResult"/>.</returns> /// <returns>A <see cref="NoContentResult"/>.</returns>
[HttpPost("Sessions/{sessionId}/Command/{command}")] [HttpPost("Sessions/{sessionId}/Command/{command}")]
[Authorize(Policy = Policies.DefaultAuthorization)]
[ProducesResponseType(StatusCodes.Status204NoContent)] [ProducesResponseType(StatusCodes.Status204NoContent)]
public ActionResult SendGeneralCommand( public ActionResult SendGeneralCommand(
[FromRoute] string? sessionId, [FromRoute, Required] string? sessionId,
[FromRoute] string? command) [FromRoute, Required] string? command)
{ {
var currentSession = RequestHelpers.GetSession(_sessionManager, _authContext, Request); var currentSession = RequestHelpers.GetSession(_sessionManager, _authContext, Request);
@ -269,9 +274,10 @@ namespace Jellyfin.Api.Controllers
/// <response code="204">Full general command sent to session.</response> /// <response code="204">Full general command sent to session.</response>
/// <returns>A <see cref="NoContentResult"/>.</returns> /// <returns>A <see cref="NoContentResult"/>.</returns>
[HttpPost("Sessions/{sessionId}/Command")] [HttpPost("Sessions/{sessionId}/Command")]
[Authorize(Policy = Policies.DefaultAuthorization)]
[ProducesResponseType(StatusCodes.Status204NoContent)] [ProducesResponseType(StatusCodes.Status204NoContent)]
public ActionResult SendFullGeneralCommand( public ActionResult SendFullGeneralCommand(
[FromRoute] string? sessionId, [FromRoute, Required] string? sessionId,
[FromBody, Required] GeneralCommand command) [FromBody, Required] GeneralCommand command)
{ {
var currentSession = RequestHelpers.GetSession(_sessionManager, _authContext, Request); var currentSession = RequestHelpers.GetSession(_sessionManager, _authContext, Request);
@ -302,11 +308,12 @@ namespace Jellyfin.Api.Controllers
/// <response code="204">Message sent.</response> /// <response code="204">Message sent.</response>
/// <returns>A <see cref="NoContentResult"/>.</returns> /// <returns>A <see cref="NoContentResult"/>.</returns>
[HttpPost("Sessions/{sessionId}/Message")] [HttpPost("Sessions/{sessionId}/Message")]
[Authorize(Policy = Policies.DefaultAuthorization)]
[ProducesResponseType(StatusCodes.Status204NoContent)] [ProducesResponseType(StatusCodes.Status204NoContent)]
public ActionResult SendMessageCommand( public ActionResult SendMessageCommand(
[FromRoute] string? sessionId, [FromRoute, Required] string? sessionId,
[FromQuery] string? text, [FromQuery, Required] string? text,
[FromQuery] string? header, [FromQuery, Required] string? header,
[FromQuery] long? timeoutMs) [FromQuery] long? timeoutMs)
{ {
var command = new MessageCommand var command = new MessageCommand
@ -329,9 +336,10 @@ namespace Jellyfin.Api.Controllers
/// <response code="204">User added to session.</response> /// <response code="204">User added to session.</response>
/// <returns>A <see cref="NoContentResult"/>.</returns> /// <returns>A <see cref="NoContentResult"/>.</returns>
[HttpPost("Sessions/{sessionId}/User/{userId}")] [HttpPost("Sessions/{sessionId}/User/{userId}")]
[Authorize(Policy = Policies.DefaultAuthorization)]
[ProducesResponseType(StatusCodes.Status204NoContent)] [ProducesResponseType(StatusCodes.Status204NoContent)]
public ActionResult AddUserToSession( public ActionResult AddUserToSession(
[FromRoute] string? sessionId, [FromRoute, Required] string? sessionId,
[FromRoute] Guid userId) [FromRoute] Guid userId)
{ {
_sessionManager.AddAdditionalUser(sessionId, userId); _sessionManager.AddAdditionalUser(sessionId, userId);
@ -346,6 +354,7 @@ namespace Jellyfin.Api.Controllers
/// <response code="204">User removed from session.</response> /// <response code="204">User removed from session.</response>
/// <returns>A <see cref="NoContentResult"/>.</returns> /// <returns>A <see cref="NoContentResult"/>.</returns>
[HttpDelete("Sessions/{sessionId}/User/{userId}")] [HttpDelete("Sessions/{sessionId}/User/{userId}")]
[Authorize(Policy = Policies.DefaultAuthorization)]
[ProducesResponseType(StatusCodes.Status204NoContent)] [ProducesResponseType(StatusCodes.Status204NoContent)]
public ActionResult RemoveUserFromSession( public ActionResult RemoveUserFromSession(
[FromRoute] string? sessionId, [FromRoute] string? sessionId,
@ -367,9 +376,10 @@ namespace Jellyfin.Api.Controllers
/// <response code="204">Capabilities posted.</response> /// <response code="204">Capabilities posted.</response>
/// <returns>A <see cref="NoContentResult"/>.</returns> /// <returns>A <see cref="NoContentResult"/>.</returns>
[HttpPost("Sessions/Capabilities")] [HttpPost("Sessions/Capabilities")]
[Authorize(Policy = Policies.DefaultAuthorization)]
[ProducesResponseType(StatusCodes.Status204NoContent)] [ProducesResponseType(StatusCodes.Status204NoContent)]
public ActionResult PostCapabilities( public ActionResult PostCapabilities(
[FromQuery] string? id, [FromQuery, Required] string? id,
[FromQuery] string? playableMediaTypes, [FromQuery] string? playableMediaTypes,
[FromQuery] string? supportedCommands, [FromQuery] string? supportedCommands,
[FromQuery] bool supportsMediaControl = false, [FromQuery] bool supportsMediaControl = false,
@ -400,9 +410,10 @@ namespace Jellyfin.Api.Controllers
/// <response code="204">Capabilities updated.</response> /// <response code="204">Capabilities updated.</response>
/// <returns>A <see cref="NoContentResult"/>.</returns> /// <returns>A <see cref="NoContentResult"/>.</returns>
[HttpPost("Sessions/Capabilities/Full")] [HttpPost("Sessions/Capabilities/Full")]
[Authorize(Policy = Policies.DefaultAuthorization)]
[ProducesResponseType(StatusCodes.Status204NoContent)] [ProducesResponseType(StatusCodes.Status204NoContent)]
public ActionResult PostFullCapabilities( public ActionResult PostFullCapabilities(
[FromQuery] string? id, [FromQuery, Required] string? id,
[FromBody, Required] ClientCapabilities capabilities) [FromBody, Required] ClientCapabilities capabilities)
{ {
if (string.IsNullOrWhiteSpace(id)) if (string.IsNullOrWhiteSpace(id))
@ -423,6 +434,7 @@ namespace Jellyfin.Api.Controllers
/// <response code="204">Session reported to server.</response> /// <response code="204">Session reported to server.</response>
/// <returns>A <see cref="NoContentResult"/>.</returns> /// <returns>A <see cref="NoContentResult"/>.</returns>
[HttpPost("Sessions/Viewing")] [HttpPost("Sessions/Viewing")]
[Authorize(Policy = Policies.DefaultAuthorization)]
[ProducesResponseType(StatusCodes.Status204NoContent)] [ProducesResponseType(StatusCodes.Status204NoContent)]
public ActionResult ReportViewing( public ActionResult ReportViewing(
[FromQuery] string? sessionId, [FromQuery] string? sessionId,
@ -440,6 +452,7 @@ namespace Jellyfin.Api.Controllers
/// <response code="204">Session end reported to server.</response> /// <response code="204">Session end reported to server.</response>
/// <returns>A <see cref="NoContentResult"/>.</returns> /// <returns>A <see cref="NoContentResult"/>.</returns>
[HttpPost("Sessions/Logout")] [HttpPost("Sessions/Logout")]
[Authorize(Policy = Policies.DefaultAuthorization)]
[ProducesResponseType(StatusCodes.Status204NoContent)] [ProducesResponseType(StatusCodes.Status204NoContent)]
public ActionResult ReportSessionEnded() public ActionResult ReportSessionEnded()
{ {
@ -455,6 +468,7 @@ namespace Jellyfin.Api.Controllers
/// <response code="200">Auth providers retrieved.</response> /// <response code="200">Auth providers retrieved.</response>
/// <returns>An <see cref="IEnumerable{NameIdPair}"/> with the auth providers.</returns> /// <returns>An <see cref="IEnumerable{NameIdPair}"/> with the auth providers.</returns>
[HttpGet("Auth/Providers")] [HttpGet("Auth/Providers")]
[Authorize(Policy = Policies.RequiresElevation)]
[ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status200OK)]
public ActionResult<IEnumerable<NameIdPair>> GetAuthProviders() public ActionResult<IEnumerable<NameIdPair>> GetAuthProviders()
{ {
@ -468,6 +482,7 @@ namespace Jellyfin.Api.Controllers
/// <returns>An <see cref="IEnumerable{NameIdPair}"/> with the password reset providers.</returns> /// <returns>An <see cref="IEnumerable{NameIdPair}"/> with the password reset providers.</returns>
[HttpGet("Auto/PasswordResetProviders")] [HttpGet("Auto/PasswordResetProviders")]
[ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status200OK)]
[Authorize(Policy = Policies.RequiresElevation)]
public ActionResult<IEnumerable<NameIdPair>> GetPasswordResetProviders() public ActionResult<IEnumerable<NameIdPair>> GetPasswordResetProviders()
{ {
return _userManager.GetPasswordResetProviders(); return _userManager.GetPasswordResetProviders();

View file

@ -113,7 +113,7 @@ namespace Jellyfin.Api.Controllers
[ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status200OK)]
public async Task<ActionResult<IEnumerable<RemoteSubtitleInfo>>> SearchRemoteSubtitles( public async Task<ActionResult<IEnumerable<RemoteSubtitleInfo>>> SearchRemoteSubtitles(
[FromRoute] Guid itemId, [FromRoute] Guid itemId,
[FromRoute] string? language, [FromRoute, Required] string? language,
[FromQuery] bool? isPerfectMatch) [FromQuery] bool? isPerfectMatch)
{ {
var video = (Video)_libraryManager.GetItemById(itemId); var video = (Video)_libraryManager.GetItemById(itemId);
@ -133,7 +133,7 @@ namespace Jellyfin.Api.Controllers
[ProducesResponseType(StatusCodes.Status204NoContent)] [ProducesResponseType(StatusCodes.Status204NoContent)]
public async Task<ActionResult> DownloadRemoteSubtitles( public async Task<ActionResult> DownloadRemoteSubtitles(
[FromRoute] Guid itemId, [FromRoute] Guid itemId,
[FromRoute] string? subtitleId) [FromRoute, Required] string? subtitleId)
{ {
var video = (Video)_libraryManager.GetItemById(itemId); var video = (Video)_libraryManager.GetItemById(itemId);
@ -162,7 +162,7 @@ namespace Jellyfin.Api.Controllers
[Authorize(Policy = Policies.DefaultAuthorization)] [Authorize(Policy = Policies.DefaultAuthorization)]
[ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status200OK)]
[Produces(MediaTypeNames.Application.Octet)] [Produces(MediaTypeNames.Application.Octet)]
public async Task<ActionResult> GetRemoteSubtitles([FromRoute] string? id) public async Task<ActionResult> GetRemoteSubtitles([FromRoute, Required] string? id)
{ {
var result = await _subtitleManager.GetRemoteSubtitles(id, CancellationToken.None).ConfigureAwait(false); var result = await _subtitleManager.GetRemoteSubtitles(id, CancellationToken.None).ConfigureAwait(false);

View file

@ -94,7 +94,7 @@ namespace Jellyfin.Api.Controllers
/// </summary> /// </summary>
/// <param name="filterItemId">Optional. Filter by item id.</param> /// <param name="filterItemId">Optional. Filter by item id.</param>
/// <response code="200">Groups returned.</response> /// <response code="200">Groups returned.</response>
/// <returns>An <see cref="IEnumerable{GrouüInfoView}"/> containing the available SyncPlay groups.</returns> /// <returns>An <see cref="IEnumerable{GroupInfoView}"/> containing the available SyncPlay groups.</returns>
[HttpGet("List")] [HttpGet("List")]
[ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status200OK)]
public ActionResult<IEnumerable<GroupInfoView>> SyncPlayGetGroups([FromQuery] Guid? filterItemId) public ActionResult<IEnumerable<GroupInfoView>> SyncPlayGetGroups([FromQuery] Guid? filterItemId)

View file

@ -23,7 +23,6 @@ namespace Jellyfin.Api.Controllers
/// <summary> /// <summary>
/// The system controller. /// The system controller.
/// </summary> /// </summary>
[Route("System")]
public class SystemController : BaseJellyfinApiController public class SystemController : BaseJellyfinApiController
{ {
private readonly IServerApplicationHost _appHost; private readonly IServerApplicationHost _appHost;
@ -60,8 +59,7 @@ namespace Jellyfin.Api.Controllers
/// <response code="200">Information retrieved.</response> /// <response code="200">Information retrieved.</response>
/// <returns>A <see cref="SystemInfo"/> with info about the system.</returns> /// <returns>A <see cref="SystemInfo"/> with info about the system.</returns>
[HttpGet("Info")] [HttpGet("Info")]
[Authorize(Policy = Policies.IgnoreSchedule)] [Authorize(Policy = Policies.FirstTimeSetupOrIgnoreParentalControl)]
[Authorize(Policy = Policies.FirstTimeSetupOrElevated)]
[ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status200OK)]
public async Task<ActionResult<SystemInfo>> GetSystemInfo() public async Task<ActionResult<SystemInfo>> GetSystemInfo()
{ {
@ -99,8 +97,7 @@ namespace Jellyfin.Api.Controllers
/// <response code="204">Server restarted.</response> /// <response code="204">Server restarted.</response>
/// <returns>No content. Server restarted.</returns> /// <returns>No content. Server restarted.</returns>
[HttpPost("Restart")] [HttpPost("Restart")]
[Authorize(Policy = Policies.LocalAccessOnly)] [Authorize(Policy = Policies.LocalAccessOrRequiresElevation)]
[Authorize(Policy = Policies.RequiresElevation)]
[ProducesResponseType(StatusCodes.Status204NoContent)] [ProducesResponseType(StatusCodes.Status204NoContent)]
public ActionResult RestartApplication() public ActionResult RestartApplication()
{ {

View file

@ -9,7 +9,7 @@ namespace Jellyfin.Api.Controllers
/// <summary> /// <summary>
/// The time sync controller. /// The time sync controller.
/// </summary> /// </summary>
[Route("GetUtcTime")] [Route("")]
public class TimeSyncController : BaseJellyfinApiController public class TimeSyncController : BaseJellyfinApiController
{ {
/// <summary> /// <summary>
@ -17,7 +17,7 @@ namespace Jellyfin.Api.Controllers
/// </summary> /// </summary>
/// <response code="200">Time returned.</response> /// <response code="200">Time returned.</response>
/// <returns>An <see cref="UtcTimeResponse"/> to sync the client and server time.</returns> /// <returns>An <see cref="UtcTimeResponse"/> to sync the client and server time.</returns>
[HttpGet] [HttpGet("GetUtcTime")]
[ProducesResponseType(statusCode: StatusCodes.Status200OK)] [ProducesResponseType(statusCode: StatusCodes.Status200OK)]
public ActionResult<UtcTimeResponse> GetUtcTime() public ActionResult<UtcTimeResponse> GetUtcTime()
{ {

View file

@ -1,5 +1,6 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Globalization; using System.Globalization;
using System.Linq; using System.Linq;
using Jellyfin.Api.Constants; using Jellyfin.Api.Constants;
@ -68,7 +69,7 @@ namespace Jellyfin.Api.Controllers
[HttpGet("NextUp")] [HttpGet("NextUp")]
[ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status200OK)]
public ActionResult<QueryResult<BaseItemDto>> GetNextUp( public ActionResult<QueryResult<BaseItemDto>> GetNextUp(
[FromQuery] Guid? userId, [FromQuery, Required] Guid? userId,
[FromQuery] int? startIndex, [FromQuery] int? startIndex,
[FromQuery] int? limit, [FromQuery] int? limit,
[FromQuery] string? fields, [FromQuery] string? fields,
@ -126,7 +127,7 @@ namespace Jellyfin.Api.Controllers
[HttpGet("Upcoming")] [HttpGet("Upcoming")]
[ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status200OK)]
public ActionResult<QueryResult<BaseItemDto>> GetUpcomingEpisodes( public ActionResult<QueryResult<BaseItemDto>> GetUpcomingEpisodes(
[FromQuery] Guid? userId, [FromQuery, Required] Guid? userId,
[FromQuery] int? startIndex, [FromQuery] int? startIndex,
[FromQuery] int? limit, [FromQuery] int? limit,
[FromQuery] string? fields, [FromQuery] string? fields,
@ -193,8 +194,8 @@ namespace Jellyfin.Api.Controllers
[ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)] [ProducesResponseType(StatusCodes.Status404NotFound)]
public ActionResult<QueryResult<BaseItemDto>> GetEpisodes( public ActionResult<QueryResult<BaseItemDto>> GetEpisodes(
[FromRoute] string? seriesId, [FromRoute, Required] string? seriesId,
[FromQuery] Guid? userId, [FromQuery, Required] Guid? userId,
[FromQuery] string? fields, [FromQuery] string? fields,
[FromQuery] int? season, [FromQuery] int? season,
[FromQuery] string? seasonId, [FromQuery] string? seasonId,
@ -316,8 +317,8 @@ namespace Jellyfin.Api.Controllers
[ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)] [ProducesResponseType(StatusCodes.Status404NotFound)]
public ActionResult<QueryResult<BaseItemDto>> GetSeasons( public ActionResult<QueryResult<BaseItemDto>> GetSeasons(
[FromRoute] string? seriesId, [FromRoute, Required] string? seriesId,
[FromQuery] Guid? userId, [FromQuery, Required] Guid? userId,
[FromQuery] string? fields, [FromQuery] string? fields,
[FromQuery] bool? isSpecialSeason, [FromQuery] bool? isSpecialSeason,
[FromQuery] bool? isMissing, [FromQuery] bool? isMissing,

View file

@ -20,7 +20,6 @@ using MediaBrowser.Model.Users;
using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.ModelBinding;
namespace Jellyfin.Api.Controllers namespace Jellyfin.Api.Controllers
{ {
@ -106,7 +105,7 @@ namespace Jellyfin.Api.Controllers
/// <response code="404">User not found.</response> /// <response code="404">User not found.</response>
/// <returns>An <see cref="UserDto"/> with information about the user or a <see cref="NotFoundResult"/> if the user was not found.</returns> /// <returns>An <see cref="UserDto"/> with information about the user or a <see cref="NotFoundResult"/> if the user was not found.</returns>
[HttpGet("{userId}")] [HttpGet("{userId}")]
[Authorize(Policy = Policies.IgnoreSchedule)] [Authorize(Policy = Policies.IgnoreParentalControl)]
[ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)] [ProducesResponseType(StatusCodes.Status404NotFound)]
public ActionResult<UserDto> GetUserById([FromRoute] Guid userId) public ActionResult<UserDto> GetUserById([FromRoute] Guid userId)
@ -157,8 +156,8 @@ namespace Jellyfin.Api.Controllers
[ProducesResponseType(StatusCodes.Status404NotFound)] [ProducesResponseType(StatusCodes.Status404NotFound)]
public async Task<ActionResult<AuthenticationResult>> AuthenticateUser( public async Task<ActionResult<AuthenticationResult>> AuthenticateUser(
[FromRoute, Required] Guid userId, [FromRoute, Required] Guid userId,
[FromQuery, BindRequired] string? pw, [FromQuery, Required] string? pw,
[FromQuery, BindRequired] string? password) [FromQuery] string? password)
{ {
var user = _userManager.GetUserById(userId); var user = _userManager.GetUserById(userId);
@ -190,7 +189,7 @@ namespace Jellyfin.Api.Controllers
/// <returns>A <see cref="Task"/> containing an <see cref="AuthenticationRequest"/> with information about the new session.</returns> /// <returns>A <see cref="Task"/> containing an <see cref="AuthenticationRequest"/> with information about the new session.</returns>
[HttpPost("AuthenticateByName")] [HttpPost("AuthenticateByName")]
[ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status200OK)]
public async Task<ActionResult<AuthenticationResult>> AuthenticateUserByName([FromBody, BindRequired] AuthenticateUserByName request) public async Task<ActionResult<AuthenticationResult>> AuthenticateUserByName([FromBody, Required] AuthenticateUserByName request)
{ {
var auth = _authContext.GetAuthorizationInfo(Request); var auth = _authContext.GetAuthorizationInfo(Request);
@ -371,7 +370,7 @@ namespace Jellyfin.Api.Controllers
/// <response code="403">User policy update forbidden.</response> /// <response code="403">User policy update forbidden.</response>
/// <returns>A <see cref="NoContentResult"/> indicating success or a <see cref="BadRequestResult"/> or a <see cref="ForbidResult"/> on failure..</returns> /// <returns>A <see cref="NoContentResult"/> indicating success or a <see cref="BadRequestResult"/> or a <see cref="ForbidResult"/> on failure..</returns>
[HttpPost("{userId}/Policy")] [HttpPost("{userId}/Policy")]
[Authorize(Policy = Policies.DefaultAuthorization)] [Authorize(Policy = Policies.RequiresElevation)]
[ProducesResponseType(StatusCodes.Status204NoContent)] [ProducesResponseType(StatusCodes.Status204NoContent)]
[ProducesResponseType(StatusCodes.Status400BadRequest)] [ProducesResponseType(StatusCodes.Status400BadRequest)]
[ProducesResponseType(StatusCodes.Status403Forbidden)] [ProducesResponseType(StatusCodes.Status403Forbidden)]

View file

@ -1,12 +1,11 @@
using System; using System;
using System.ComponentModel.DataAnnotations;
using System.Net.Mime; using System.Net.Mime;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using Jellyfin.Api.Constants;
using MediaBrowser.Common.Extensions; using MediaBrowser.Common.Extensions;
using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.MediaEncoding; using MediaBrowser.Controller.MediaEncoding;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
@ -16,7 +15,6 @@ namespace Jellyfin.Api.Controllers
/// Attachments controller. /// Attachments controller.
/// </summary> /// </summary>
[Route("Videos")] [Route("Videos")]
[Authorize(Policy = Policies.DefaultAuthorization)]
public class VideoAttachmentsController : BaseJellyfinApiController public class VideoAttachmentsController : BaseJellyfinApiController
{ {
private readonly ILibraryManager _libraryManager; private readonly ILibraryManager _libraryManager;
@ -49,9 +47,9 @@ namespace Jellyfin.Api.Controllers
[ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)] [ProducesResponseType(StatusCodes.Status404NotFound)]
public async Task<ActionResult<FileStreamResult>> GetAttachment( public async Task<ActionResult<FileStreamResult>> GetAttachment(
[FromRoute] Guid videoId, [FromRoute, Required] Guid videoId,
[FromRoute] string? mediaSourceId, [FromRoute, Required] string mediaSourceId,
[FromRoute] int index) [FromRoute, Required] int index)
{ {
try try
{ {

View file

@ -1,5 +1,6 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Globalization; using System.Globalization;
using System.Linq; using System.Linq;
using System.Net.Http; using System.Net.Http;
@ -35,7 +36,6 @@ namespace Jellyfin.Api.Controllers
/// <summary> /// <summary>
/// The videos controller. /// The videos controller.
/// </summary> /// </summary>
[Route("Videos")]
public class VideosController : BaseJellyfinApiController public class VideosController : BaseJellyfinApiController
{ {
private readonly ILibraryManager _libraryManager; private readonly ILibraryManager _libraryManager;
@ -196,7 +196,7 @@ namespace Jellyfin.Api.Controllers
[Authorize(Policy = Policies.RequiresElevation)] [Authorize(Policy = Policies.RequiresElevation)]
[ProducesResponseType(StatusCodes.Status204NoContent)] [ProducesResponseType(StatusCodes.Status204NoContent)]
[ProducesResponseType(StatusCodes.Status400BadRequest)] [ProducesResponseType(StatusCodes.Status400BadRequest)]
public ActionResult MergeVersions([FromQuery] string? itemIds) public ActionResult MergeVersions([FromQuery, Required] string? itemIds)
{ {
var items = RequestHelpers.Split(itemIds, ',', true) var items = RequestHelpers.Split(itemIds, ',', true)
.Select(i => _libraryManager.GetItemById(i)) .Select(i => _libraryManager.GetItemById(i))

View file

@ -1,6 +1,7 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using Jellyfin.Api.Constants;
using Jellyfin.Api.Extensions; using Jellyfin.Api.Extensions;
using Jellyfin.Api.Helpers; using Jellyfin.Api.Helpers;
using Jellyfin.Data.Entities; using Jellyfin.Data.Entities;
@ -9,6 +10,7 @@ using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Library;
using MediaBrowser.Model.Dto; using MediaBrowser.Model.Dto;
using MediaBrowser.Model.Querying; using MediaBrowser.Model.Querying;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
@ -17,6 +19,7 @@ namespace Jellyfin.Api.Controllers
/// <summary> /// <summary>
/// Years controller. /// Years controller.
/// </summary> /// </summary>
[Authorize(Policy = Policies.DefaultAuthorization)]
public class YearsController : BaseJellyfinApiController public class YearsController : BaseJellyfinApiController
{ {
private readonly ILibraryManager _libraryManager; private readonly ILibraryManager _libraryManager;

View file

@ -7,8 +7,11 @@ using Jellyfin.Api;
using Jellyfin.Api.Auth; using Jellyfin.Api.Auth;
using Jellyfin.Api.Auth.DefaultAuthorizationPolicy; using Jellyfin.Api.Auth.DefaultAuthorizationPolicy;
using Jellyfin.Api.Auth.DownloadPolicy; using Jellyfin.Api.Auth.DownloadPolicy;
using Jellyfin.Api.Auth.FirstTimeOrIgnoreParentalControlSetupPolicy;
using Jellyfin.Api.Auth.FirstTimeSetupOrDefaultPolicy;
using Jellyfin.Api.Auth.FirstTimeSetupOrElevatedPolicy; 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.LocalAccessPolicy;
using Jellyfin.Api.Auth.RequiresElevationPolicy; using Jellyfin.Api.Auth.RequiresElevationPolicy;
using Jellyfin.Api.Constants; using Jellyfin.Api.Constants;
@ -41,9 +44,12 @@ namespace Jellyfin.Server.Extensions
{ {
serviceCollection.AddSingleton<IAuthorizationHandler, DefaultAuthorizationHandler>(); serviceCollection.AddSingleton<IAuthorizationHandler, DefaultAuthorizationHandler>();
serviceCollection.AddSingleton<IAuthorizationHandler, DownloadHandler>(); serviceCollection.AddSingleton<IAuthorizationHandler, DownloadHandler>();
serviceCollection.AddSingleton<IAuthorizationHandler, FirstTimeSetupOrDefaultHandler>();
serviceCollection.AddSingleton<IAuthorizationHandler, FirstTimeSetupOrElevatedHandler>(); serviceCollection.AddSingleton<IAuthorizationHandler, FirstTimeSetupOrElevatedHandler>();
serviceCollection.AddSingleton<IAuthorizationHandler, IgnoreScheduleHandler>(); serviceCollection.AddSingleton<IAuthorizationHandler, IgnoreParentalControlHandler>();
serviceCollection.AddSingleton<IAuthorizationHandler, FirstTimeOrIgnoreParentalControlSetupHandler>();
serviceCollection.AddSingleton<IAuthorizationHandler, LocalAccessHandler>(); serviceCollection.AddSingleton<IAuthorizationHandler, LocalAccessHandler>();
serviceCollection.AddSingleton<IAuthorizationHandler, LocalAccessOrRequiresElevationHandler>();
serviceCollection.AddSingleton<IAuthorizationHandler, RequiresElevationHandler>(); serviceCollection.AddSingleton<IAuthorizationHandler, RequiresElevationHandler>();
return serviceCollection.AddAuthorizationCore(options => return serviceCollection.AddAuthorizationCore(options =>
{ {
@ -61,6 +67,13 @@ namespace Jellyfin.Server.Extensions
policy.AddAuthenticationSchemes(AuthenticationSchemes.CustomAuthentication); policy.AddAuthenticationSchemes(AuthenticationSchemes.CustomAuthentication);
policy.AddRequirements(new DownloadRequirement()); policy.AddRequirements(new DownloadRequirement());
}); });
options.AddPolicy(
Policies.FirstTimeSetupOrDefault,
policy =>
{
policy.AddAuthenticationSchemes(AuthenticationSchemes.CustomAuthentication);
policy.AddRequirements(new FirstTimeSetupOrDefaultRequirement());
});
options.AddPolicy( options.AddPolicy(
Policies.FirstTimeSetupOrElevated, Policies.FirstTimeSetupOrElevated,
policy => policy =>
@ -69,11 +82,18 @@ namespace Jellyfin.Server.Extensions
policy.AddRequirements(new FirstTimeSetupOrElevatedRequirement()); policy.AddRequirements(new FirstTimeSetupOrElevatedRequirement());
}); });
options.AddPolicy( options.AddPolicy(
Policies.IgnoreSchedule, Policies.IgnoreParentalControl,
policy => policy =>
{ {
policy.AddAuthenticationSchemes(AuthenticationSchemes.CustomAuthentication); 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( options.AddPolicy(
Policies.LocalAccessOnly, Policies.LocalAccessOnly,
@ -82,6 +102,13 @@ namespace Jellyfin.Server.Extensions
policy.AddAuthenticationSchemes(AuthenticationSchemes.CustomAuthentication); policy.AddAuthenticationSchemes(AuthenticationSchemes.CustomAuthentication);
policy.AddRequirements(new LocalAccessRequirement()); policy.AddRequirements(new LocalAccessRequirement());
}); });
options.AddPolicy(
Policies.LocalAccessOrRequiresElevation,
policy =>
{
policy.AddAuthenticationSchemes(AuthenticationSchemes.CustomAuthentication);
policy.AddRequirements(new LocalAccessOrRequiresElevationRequirement());
});
options.AddPolicy( options.AddPolicy(
Policies.RequiresElevation, Policies.RequiresElevation,
policy => policy =>

View file

@ -3,7 +3,7 @@ using System.Collections.Generic;
using System.Threading.Tasks; using System.Threading.Tasks;
using AutoFixture; using AutoFixture;
using AutoFixture.AutoMoq; using AutoFixture.AutoMoq;
using Jellyfin.Api.Auth.IgnoreSchedulePolicy; using Jellyfin.Api.Auth.IgnoreParentalControlPolicy;
using Jellyfin.Api.Constants; using Jellyfin.Api.Constants;
using Jellyfin.Data.Entities; using Jellyfin.Data.Entities;
using Jellyfin.Data.Enums; using Jellyfin.Data.Enums;
@ -20,7 +20,7 @@ namespace Jellyfin.Api.Tests.Auth.IgnoreSchedulePolicy
{ {
private readonly Mock<IConfigurationManager> _configurationManagerMock; private readonly Mock<IConfigurationManager> _configurationManagerMock;
private readonly List<IAuthorizationRequirement> _requirements; private readonly List<IAuthorizationRequirement> _requirements;
private readonly IgnoreScheduleHandler _sut; private readonly IgnoreParentalControlHandler _sut;
private readonly Mock<IUserManager> _userManagerMock; private readonly Mock<IUserManager> _userManagerMock;
private readonly Mock<IHttpContextAccessor> _httpContextAccessor; private readonly Mock<IHttpContextAccessor> _httpContextAccessor;
@ -33,11 +33,11 @@ namespace Jellyfin.Api.Tests.Auth.IgnoreSchedulePolicy
{ {
var fixture = new Fixture().Customize(new AutoMoqCustomization()); var fixture = new Fixture().Customize(new AutoMoqCustomization());
_configurationManagerMock = fixture.Freeze<Mock<IConfigurationManager>>(); _configurationManagerMock = fixture.Freeze<Mock<IConfigurationManager>>();
_requirements = new List<IAuthorizationRequirement> { new IgnoreScheduleRequirement() }; _requirements = new List<IAuthorizationRequirement> { new IgnoreParentalControlRequirement() };
_userManagerMock = fixture.Freeze<Mock<IUserManager>>(); _userManagerMock = fixture.Freeze<Mock<IUserManager>>();
_httpContextAccessor = fixture.Freeze<Mock<IHttpContextAccessor>>(); _httpContextAccessor = fixture.Freeze<Mock<IHttpContextAccessor>>();
_sut = fixture.Create<IgnoreScheduleHandler>(); _sut = fixture.Create<IgnoreParentalControlHandler>();
} }
[Theory] [Theory]