mirror of
				https://github.com/zoriya/Kyoo.git
				synced 2025-11-04 03:27:14 -05:00 
			
		
		
		
	Reworking authorization handling
This commit is contained in:
		
							parent
							
								
									765bd061b7
								
							
						
					
					
						commit
						71c18092e5
					
				@ -1,12 +1,13 @@
 | 
			
		||||
using System;
 | 
			
		||||
using System.Collections.Generic;
 | 
			
		||||
using System.Linq;
 | 
			
		||||
using IdentityServer4.Extensions;
 | 
			
		||||
using IdentityServer4.Models;
 | 
			
		||||
using IdentityServer4.Services;
 | 
			
		||||
using Kyoo.Authentication.Models;
 | 
			
		||||
using Kyoo.Authentication.Views;
 | 
			
		||||
using Kyoo.Controllers;
 | 
			
		||||
using Microsoft.AspNetCore.Authentication.JwtBearer;
 | 
			
		||||
using Microsoft.AspNetCore.Authorization;
 | 
			
		||||
using Kyoo.Models.Permissions;
 | 
			
		||||
using Microsoft.AspNetCore.Builder;
 | 
			
		||||
using Microsoft.AspNetCore.Hosting;
 | 
			
		||||
using Microsoft.AspNetCore.Http;
 | 
			
		||||
@ -86,15 +87,17 @@ namespace Kyoo.Authentication
 | 
			
		||||
 | 
			
		||||
			services.AddControllers();
 | 
			
		||||
			
 | 
			
		||||
			// TODO handle direct-videos with bearers (probably add a ?token query param and a app.Use to translate that for videos)
 | 
			
		||||
			// TODO handle direct-videos with bearers (probably add a cookie and a app.Use to translate that for videos)
 | 
			
		||||
			
 | 
			
		||||
			// TODO Check if tokens should be stored.
 | 
			
		||||
			
 | 
			
		||||
			// TODO remove unused/commented code, add documentation.
 | 
			
		||||
 | 
			
		||||
			services.Configure<PermissionOption>(_configuration.GetSection(PermissionOption.Path));
 | 
			
		||||
			services.Configure<CertificateOption>(_configuration.GetSection(CertificateOption.Path));
 | 
			
		||||
			services.Configure<AuthenticationOption>(_configuration.GetSection(AuthenticationOption.Path));
 | 
			
		||||
			
 | 
			
		||||
			
 | 
			
		||||
			List<Client> clients = new();
 | 
			
		||||
			_configuration.GetSection("authentication:clients").Bind(clients);
 | 
			
		||||
			CertificateOption certificateOptions = new();
 | 
			
		||||
			_configuration.GetSection(CertificateOption.Path).Bind(certificateOptions);
 | 
			
		||||
			
 | 
			
		||||
@ -108,11 +111,9 @@ namespace Kyoo.Authentication
 | 
			
		||||
				.AddInMemoryIdentityResources(IdentityContext.GetIdentityResources())
 | 
			
		||||
				.AddInMemoryApiScopes(IdentityContext.GetScopes())
 | 
			
		||||
				.AddInMemoryApiResources(IdentityContext.GetApis())
 | 
			
		||||
				.AddInMemoryClients(IdentityContext.GetClients())
 | 
			
		||||
				.AddInMemoryClients(_configuration.GetSection("authentication:clients"))
 | 
			
		||||
				.AddInMemoryClients(IdentityContext.GetClients().Concat(clients))
 | 
			
		||||
				.AddProfileService<AccountApi>()
 | 
			
		||||
				.AddSigninKeys(certificateOptions);
 | 
			
		||||
			// TODO split scopes (kyoo.read should be task.read, video.read etc)
 | 
			
		||||
			
 | 
			
		||||
			services.AddAuthentication()
 | 
			
		||||
				.AddJwtBearer(options =>
 | 
			
		||||
@ -121,25 +122,7 @@ namespace Kyoo.Authentication
 | 
			
		||||
					options.Audience = "kyoo";
 | 
			
		||||
					options.RequireHttpsMetadata = false;
 | 
			
		||||
				});
 | 
			
		||||
			
 | 
			
		||||
			services.AddAuthorization(options =>
 | 
			
		||||
			{
 | 
			
		||||
				AuthorizationPolicyBuilder scheme = new(JwtBearerDefaults.AuthenticationScheme);
 | 
			
		||||
				options.DefaultPolicy = scheme.RequireAuthenticatedUser().Build();
 | 
			
		||||
			
 | 
			
		||||
				string[] permissions = {"Read", "Write", "Play", "Admin"};
 | 
			
		||||
				foreach (string permission in permissions)
 | 
			
		||||
				{
 | 
			
		||||
					options.AddPolicy(permission, policy =>
 | 
			
		||||
					{
 | 
			
		||||
						policy.AuthenticationSchemes.Add(JwtBearerDefaults.AuthenticationScheme);
 | 
			
		||||
						policy.AddRequirements(new AuthRequirement(permission));
 | 
			
		||||
						// Scopes are disables to support default permissions.
 | 
			
		||||
						// To enable them, use the following line: policy.RequireScope($"kyoo.{permission.ToLower()}");
 | 
			
		||||
					});
 | 
			
		||||
				}
 | 
			
		||||
			});
 | 
			
		||||
			services.AddSingleton<IAuthorizationHandler, AuthorizationValidatorHandler>();
 | 
			
		||||
			services.AddSingleton<IPermissionValidator, PermissionValidatorFactory>();
 | 
			
		||||
 | 
			
		||||
			DefaultCorsPolicyService cors = new(_loggerFactory.CreateLogger<DefaultCorsPolicyService>())
 | 
			
		||||
			{
 | 
			
		||||
 | 
			
		||||
@ -1,51 +0,0 @@
 | 
			
		||||
using System.Collections.Generic;
 | 
			
		||||
using System.Linq;
 | 
			
		||||
using System.Security.Claims;
 | 
			
		||||
using System.Threading.Tasks;
 | 
			
		||||
using IdentityServer4.Extensions;
 | 
			
		||||
using Kyoo.Authentication.Models;
 | 
			
		||||
using Microsoft.AspNetCore.Authorization;
 | 
			
		||||
using Microsoft.Extensions.Options;
 | 
			
		||||
 | 
			
		||||
namespace Kyoo.Authentication
 | 
			
		||||
{
 | 
			
		||||
	/// <summary>
 | 
			
		||||
	/// The default IAuthorizationHandler implementation.
 | 
			
		||||
	/// </summary>
 | 
			
		||||
	public class AuthorizationValidatorHandler : AuthorizationHandler<AuthRequirement>
 | 
			
		||||
	{
 | 
			
		||||
		/// <summary>
 | 
			
		||||
		/// The permissions options to retrieve default permissions.
 | 
			
		||||
		/// </summary>
 | 
			
		||||
		private readonly IOptionsMonitor<PermissionOption> _options;
 | 
			
		||||
		
 | 
			
		||||
		/// <summary>
 | 
			
		||||
		/// Create a new <see cref="AuthorizationValidatorHandler"/>.
 | 
			
		||||
		/// </summary>
 | 
			
		||||
		/// <param name="options">The option containing default values.</param>
 | 
			
		||||
		public AuthorizationValidatorHandler(IOptionsMonitor<PermissionOption> options)
 | 
			
		||||
		{
 | 
			
		||||
			_options = options;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
		/// <inheritdoc />
 | 
			
		||||
		protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, AuthRequirement requirement)
 | 
			
		||||
		{
 | 
			
		||||
			if (context.User.IsAuthenticated())
 | 
			
		||||
			{
 | 
			
		||||
				Claim perms = context.User.Claims.FirstOrDefault(x => x.Type == "permissions");
 | 
			
		||||
				if (perms != null && perms.Value.Split(",").Contains(requirement.Permission.ToLower()))
 | 
			
		||||
					context.Succeed(requirement);
 | 
			
		||||
			}
 | 
			
		||||
			else
 | 
			
		||||
			{
 | 
			
		||||
				ICollection<string> defaultPerms = _options.CurrentValue.Default;
 | 
			
		||||
				if (defaultPerms?.Contains(requirement.Permission.ToLower()) == true)
 | 
			
		||||
					context.Succeed(requirement);
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			return Task.CompletedTask;
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										126
									
								
								Kyoo.Authentication/Controllers/PremissionValidator.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										126
									
								
								Kyoo.Authentication/Controllers/PremissionValidator.cs
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,126 @@
 | 
			
		||||
using System;
 | 
			
		||||
using System.Linq;
 | 
			
		||||
using System.Threading.Tasks;
 | 
			
		||||
using Kyoo.Authentication.Models;
 | 
			
		||||
using Kyoo.Models.Permissions;
 | 
			
		||||
using Microsoft.AspNetCore.Authentication;
 | 
			
		||||
using Microsoft.AspNetCore.Authentication.JwtBearer;
 | 
			
		||||
using Microsoft.AspNetCore.Http;
 | 
			
		||||
using Microsoft.AspNetCore.Mvc;
 | 
			
		||||
using Microsoft.AspNetCore.Mvc.Filters;
 | 
			
		||||
using Microsoft.Extensions.Options;
 | 
			
		||||
 | 
			
		||||
namespace Kyoo.Authentication
 | 
			
		||||
{
 | 
			
		||||
	/// <summary>
 | 
			
		||||
	/// A permission validator to validate permission with user Permission array
 | 
			
		||||
	/// or the default array from the configurations if the user is not logged. 
 | 
			
		||||
	/// </summary>
 | 
			
		||||
	public class PermissionValidatorFactory : IPermissionValidator
 | 
			
		||||
	{
 | 
			
		||||
		/// <summary>
 | 
			
		||||
		/// The permissions options to retrieve default permissions.
 | 
			
		||||
		/// </summary>
 | 
			
		||||
		private readonly IOptionsMonitor<PermissionOption> _options;
 | 
			
		||||
 | 
			
		||||
		/// <summary>
 | 
			
		||||
		/// Create a new factory with the given options
 | 
			
		||||
		/// </summary>
 | 
			
		||||
		/// <param name="options">The option containing default values.</param>
 | 
			
		||||
		public PermissionValidatorFactory(IOptionsMonitor<PermissionOption> options)
 | 
			
		||||
		{
 | 
			
		||||
			_options = options;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		/// <inheritdoc />
 | 
			
		||||
		public IFilterMetadata Create(PermissionAttribute attribute)
 | 
			
		||||
		{
 | 
			
		||||
			return new PermissionValidator(attribute.AsPermissionString(), _options);
 | 
			
		||||
		}
 | 
			
		||||
		
 | 
			
		||||
		/// <inheritdoc />
 | 
			
		||||
		public IFilterMetadata Create(PartialPermissionAttribute attribute)
 | 
			
		||||
		{
 | 
			
		||||
			return new PermissionValidator((object)attribute.Type ?? attribute.Kind, _options);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		/// <summary>
 | 
			
		||||
		/// The authorization filter used by <see cref="PermissionValidatorFactory"/>
 | 
			
		||||
		/// </summary>
 | 
			
		||||
		private class PermissionValidator : IAsyncAuthorizationFilter
 | 
			
		||||
		{
 | 
			
		||||
			/// <summary>
 | 
			
		||||
			/// The permission to validate
 | 
			
		||||
			/// </summary>
 | 
			
		||||
			private readonly string _permission;
 | 
			
		||||
			/// <summary>
 | 
			
		||||
			/// Information about partial items.
 | 
			
		||||
			/// </summary>
 | 
			
		||||
			private readonly object _partialInfo;
 | 
			
		||||
			/// <summary>
 | 
			
		||||
			/// The permissions options to retrieve default permissions.
 | 
			
		||||
			/// </summary>
 | 
			
		||||
			private readonly IOptionsMonitor<PermissionOption> _options;
 | 
			
		||||
 | 
			
		||||
			/// <summary>
 | 
			
		||||
			/// Create a new permission validator with the given options
 | 
			
		||||
			/// </summary>
 | 
			
		||||
			/// <param name="permission">The permission to validate</param>
 | 
			
		||||
			/// <param name="options">The option containing default values.</param>
 | 
			
		||||
			public PermissionValidator(string permission, IOptionsMonitor<PermissionOption> options)
 | 
			
		||||
			{
 | 
			
		||||
				_permission = permission;
 | 
			
		||||
				_options = options;
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			/// <summary>
 | 
			
		||||
			/// Create a new permission validator with the given options
 | 
			
		||||
			/// </summary>
 | 
			
		||||
			/// <param name="partialInfo">The partial permission to validate</param>
 | 
			
		||||
			/// <param name="options">The option containing default values.</param>
 | 
			
		||||
			public PermissionValidator(object partialInfo, IOptionsMonitor<PermissionOption> options)
 | 
			
		||||
			{
 | 
			
		||||
				_partialInfo = partialInfo;
 | 
			
		||||
				_options = options;
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
			/// <inheritdoc />
 | 
			
		||||
			public async Task OnAuthorizationAsync(AuthorizationFilterContext context)
 | 
			
		||||
			{
 | 
			
		||||
				string permission = _permission;
 | 
			
		||||
 | 
			
		||||
				if (_partialInfo != null)
 | 
			
		||||
				{
 | 
			
		||||
					switch (context.HttpContext.Items["PermissionType"])
 | 
			
		||||
					{
 | 
			
		||||
						case string perm when _partialInfo is Kind kind:
 | 
			
		||||
							 permission = $"{perm}.{kind.ToString().ToLower()}";
 | 
			
		||||
							break;
 | 
			
		||||
						case Kind kind when _partialInfo is string partial:
 | 
			
		||||
							permission = $"{partial}.{kind.ToString().ToLower()}";
 | 
			
		||||
							break;
 | 
			
		||||
						case null:
 | 
			
		||||
							context.HttpContext.Items["PermissionType"] = _partialInfo;
 | 
			
		||||
							return;
 | 
			
		||||
						default:
 | 
			
		||||
							throw new ArgumentException("Multiple non-matching partial permission attribute " +
 | 
			
		||||
							                            "are not supported.");
 | 
			
		||||
					}
 | 
			
		||||
				}
 | 
			
		||||
 | 
			
		||||
				AuthenticateResult res = await context.HttpContext.AuthenticateAsync(JwtBearerDefaults.AuthenticationScheme);
 | 
			
		||||
				if (res.Succeeded)
 | 
			
		||||
				{
 | 
			
		||||
					if (res.Principal.GetPermissions().All(x => x != permission))
 | 
			
		||||
						context.Result = new StatusCodeResult(StatusCodes.Status403Forbidden);
 | 
			
		||||
				}
 | 
			
		||||
				else
 | 
			
		||||
				{
 | 
			
		||||
					if (res.Failure != null || _options.CurrentValue.Default.All(x => x != permission))
 | 
			
		||||
						context.Result = new StatusCodeResult(StatusCodes.Status401Unauthorized);
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
@ -1,4 +1,5 @@
 | 
			
		||||
using System.Collections.Generic;
 | 
			
		||||
using System.Linq;
 | 
			
		||||
using System.Security.Claims;
 | 
			
		||||
using IdentityModel;
 | 
			
		||||
using IdentityServer4;
 | 
			
		||||
@ -35,8 +36,19 @@ namespace Kyoo.Authentication
 | 
			
		||||
		{
 | 
			
		||||
			return new(user.ID.ToString())
 | 
			
		||||
			{
 | 
			
		||||
				DisplayName = user.Username
 | 
			
		||||
				DisplayName = user.Username,
 | 
			
		||||
				AdditionalClaims = new[] {new Claim("permissions", string.Join(',', user.Permissions))}
 | 
			
		||||
			};
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		/// <summary>
 | 
			
		||||
		/// Get the permissions of an user.
 | 
			
		||||
		/// </summary>
 | 
			
		||||
		/// <param name="user">The user</param>
 | 
			
		||||
		/// <returns>The list of permissions</returns>
 | 
			
		||||
		public static ICollection<string> GetPermissions(this ClaimsPrincipal user)
 | 
			
		||||
		{
 | 
			
		||||
			return user.Claims.FirstOrDefault(x => x.Type == "permissions")?.Value.Split(',');
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
@ -1,24 +0,0 @@
 | 
			
		||||
using Microsoft.AspNetCore.Authorization;
 | 
			
		||||
 | 
			
		||||
namespace Kyoo.Authentication
 | 
			
		||||
{
 | 
			
		||||
	/// <summary>
 | 
			
		||||
	/// The requirement of Kyoo's authentication policies.
 | 
			
		||||
	/// </summary>
 | 
			
		||||
	public class AuthRequirement : IAuthorizationRequirement
 | 
			
		||||
	{
 | 
			
		||||
		/// <summary>
 | 
			
		||||
		/// The name of the permission
 | 
			
		||||
		/// </summary>
 | 
			
		||||
		public string Permission { get; }
 | 
			
		||||
 | 
			
		||||
		/// <summary>
 | 
			
		||||
		/// Create a new <see cref="AuthRequirement"/> for the given permission.
 | 
			
		||||
		/// </summary>
 | 
			
		||||
		/// <param name="permission">The permission needed</param>
 | 
			
		||||
		public AuthRequirement(string permission)
 | 
			
		||||
		{
 | 
			
		||||
			Permission = permission;
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
@ -13,8 +13,8 @@ using Kyoo.Authentication.Models.DTO;
 | 
			
		||||
using Kyoo.Controllers;
 | 
			
		||||
using Kyoo.Models;
 | 
			
		||||
using Kyoo.Models.Exceptions;
 | 
			
		||||
using Microsoft.AspNetCore.Authorization;
 | 
			
		||||
using Microsoft.AspNetCore.Authentication;
 | 
			
		||||
using Microsoft.AspNetCore.Authorization;
 | 
			
		||||
using Microsoft.AspNetCore.Http;
 | 
			
		||||
using Microsoft.AspNetCore.Mvc;
 | 
			
		||||
using Microsoft.Extensions.Options;
 | 
			
		||||
 | 
			
		||||
@ -1,23 +1,149 @@
 | 
			
		||||
using System;
 | 
			
		||||
using Microsoft.AspNetCore.Mvc.Filters;
 | 
			
		||||
using Microsoft.Extensions.DependencyInjection;
 | 
			
		||||
 | 
			
		||||
namespace Kyoo.Models.Attributes
 | 
			
		||||
namespace Kyoo.Models.Permissions
 | 
			
		||||
{
 | 
			
		||||
	/// <summary>
 | 
			
		||||
	/// Specify permissions needed for the API.
 | 
			
		||||
	/// The kind of permission needed.
 | 
			
		||||
	/// </summary>
 | 
			
		||||
	[AttributeUsage(AttributeTargets.Method)]
 | 
			
		||||
	public class PermissionAttribute : Attribute
 | 
			
		||||
	{
 | 
			
		||||
	public enum Kind
 | 
			
		||||
	{
 | 
			
		||||
		Read,
 | 
			
		||||
		Write,
 | 
			
		||||
			Admin
 | 
			
		||||
		Create,
 | 
			
		||||
		Delete
 | 
			
		||||
	}
 | 
			
		||||
	
 | 
			
		||||
	/// <summary>
 | 
			
		||||
	/// Specify permissions needed for the API.
 | 
			
		||||
	/// </summary>
 | 
			
		||||
	[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = true)]
 | 
			
		||||
	public class PermissionAttribute : Attribute, IFilterFactory
 | 
			
		||||
	{
 | 
			
		||||
		/// <summary>
 | 
			
		||||
		/// The needed permission as string.
 | 
			
		||||
		/// </summary>
 | 
			
		||||
		private readonly string _permission;
 | 
			
		||||
 | 
			
		||||
		/// <summary>
 | 
			
		||||
		/// Ask a permission to run an action. 
 | 
			
		||||
		/// </summary>
 | 
			
		||||
		/// <param name="type">
 | 
			
		||||
		/// The type of the action
 | 
			
		||||
		/// (if the type ends with api, it will be removed. This allow you to use nameof(YourApi)).
 | 
			
		||||
		/// </param>
 | 
			
		||||
		/// <param name="permission">The kind of permission needed</param>
 | 
			
		||||
		public PermissionAttribute(string type, Kind permission)
 | 
			
		||||
		{
 | 
			
		||||
			
 | 
			
		||||
			if (type.EndsWith("API", StringComparison.OrdinalIgnoreCase))
 | 
			
		||||
				type = type[..^3];
 | 
			
		||||
			_permission = $"{type.ToLower()}.{permission.ToString().ToLower()}";
 | 
			
		||||
		}
 | 
			
		||||
		
 | 
			
		||||
		/// <inheritdoc />
 | 
			
		||||
		public IFilterMetadata CreateInstance(IServiceProvider serviceProvider)
 | 
			
		||||
		{
 | 
			
		||||
			return serviceProvider.GetRequiredService<IPermissionValidator>().Create(this);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		/// <inheritdoc />
 | 
			
		||||
		public bool IsReusable => true;
 | 
			
		||||
 | 
			
		||||
		/// <summary>
 | 
			
		||||
		/// Return this permission attribute as a string
 | 
			
		||||
		/// </summary>
 | 
			
		||||
		/// <returns>The string representation.</returns>
 | 
			
		||||
		public string AsPermissionString()
 | 
			
		||||
		{
 | 
			
		||||
			return _permission;
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	/// <summary>
 | 
			
		||||
	/// Specify one part of a permissions needed for the API (the kind or the type).
 | 
			
		||||
	/// </summary>
 | 
			
		||||
	[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = true)]
 | 
			
		||||
	public class PartialPermissionAttribute : Attribute, IFilterFactory
 | 
			
		||||
	{
 | 
			
		||||
		/// <summary>
 | 
			
		||||
		/// The needed permission type.
 | 
			
		||||
		/// </summary>
 | 
			
		||||
		public string Type { get; }
 | 
			
		||||
		/// <summary>
 | 
			
		||||
		/// The needed permission kind.
 | 
			
		||||
		/// </summary>
 | 
			
		||||
		public Kind Kind { get; }
 | 
			
		||||
		
 | 
			
		||||
		/// <summary>
 | 
			
		||||
		/// Ask a permission to run an action. 
 | 
			
		||||
		/// </summary>
 | 
			
		||||
		/// <remarks>
 | 
			
		||||
		/// With this attribute, you can only specify a type or a kind.
 | 
			
		||||
		/// To have a valid permission attribute, you must specify the kind and the permission using two attributes.
 | 
			
		||||
		/// Those attributes can be dispatched at different places (one on the class, one on the method for example).
 | 
			
		||||
		/// If you don't put exactly two of those attributes, the permission attribute will be ill-formed and will
 | 
			
		||||
		/// lead to unspecified behaviors. 
 | 
			
		||||
		/// </remarks>
 | 
			
		||||
		/// <param name="type">
 | 
			
		||||
		/// The type of the action
 | 
			
		||||
		/// (if the type ends with api, it will be removed. This allow you to use nameof(YourApi)).
 | 
			
		||||
		/// </param>
 | 
			
		||||
		public PartialPermissionAttribute(string type)
 | 
			
		||||
		{
 | 
			
		||||
			if (type.EndsWith("API", StringComparison.OrdinalIgnoreCase))
 | 
			
		||||
				type = type[..^3];
 | 
			
		||||
			Type = type.ToLower();
 | 
			
		||||
		}
 | 
			
		||||
		
 | 
			
		||||
		/// <summary>
 | 
			
		||||
		/// Ask a permission to run an action. 
 | 
			
		||||
		/// </summary>
 | 
			
		||||
		/// <remarks>
 | 
			
		||||
		/// With this attribute, you can only specify a type or a kind.
 | 
			
		||||
		/// To have a valid permission attribute, you must specify the kind and the permission using two attributes.
 | 
			
		||||
		/// Those attributes can be dispatched at different places (one on the class, one on the method for example).
 | 
			
		||||
		/// If you don't put exactly two of those attributes, the permission attribute will be ill-formed and will
 | 
			
		||||
		/// lead to unspecified behaviors. 
 | 
			
		||||
		/// </remarks>
 | 
			
		||||
		/// <param name="permission">The kind of permission needed</param>
 | 
			
		||||
		public PartialPermissionAttribute(Kind permission)
 | 
			
		||||
		{
 | 
			
		||||
			Kind = permission;
 | 
			
		||||
		}
 | 
			
		||||
		
 | 
			
		||||
		/// <inheritdoc />
 | 
			
		||||
		public IFilterMetadata CreateInstance(IServiceProvider serviceProvider)
 | 
			
		||||
		{
 | 
			
		||||
			return serviceProvider.GetRequiredService<IPermissionValidator>().Create(this);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		/// <inheritdoc />
 | 
			
		||||
		public bool IsReusable => true;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
	/// <summary>
 | 
			
		||||
	/// A service to validate permissions
 | 
			
		||||
	/// </summary>
 | 
			
		||||
	public interface IPermissionValidator
 | 
			
		||||
	{
 | 
			
		||||
		/// <summary>
 | 
			
		||||
		/// Create an IAuthorizationFilter that will be used to validate permissions.
 | 
			
		||||
		/// This can registered with any lifetime.
 | 
			
		||||
		/// </summary>
 | 
			
		||||
		/// <param name="attribute">The permission attribute to validate</param>
 | 
			
		||||
		/// <returns>An authorization filter used to validate the permission</returns>
 | 
			
		||||
		IFilterMetadata Create(PermissionAttribute attribute);
 | 
			
		||||
		
 | 
			
		||||
		/// <summary>
 | 
			
		||||
		/// Create an IAuthorizationFilter that will be used to validate permissions.
 | 
			
		||||
		/// This can registered with any lifetime.
 | 
			
		||||
		/// </summary>
 | 
			
		||||
		/// <param name="attribute">
 | 
			
		||||
		/// A partial attribute to validate. See <see cref="PartialPermissionAttribute"/>.
 | 
			
		||||
		/// </param>
 | 
			
		||||
		/// <returns>An authorization filter used to validate the permission</returns>
 | 
			
		||||
		IFilterMetadata Create(PartialPermissionAttribute attribute);
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
@ -5,7 +5,7 @@ using System.Threading.Tasks;
 | 
			
		||||
using Kyoo.Controllers;
 | 
			
		||||
using Kyoo.Models;
 | 
			
		||||
using Kyoo.Models.Exceptions;
 | 
			
		||||
using Microsoft.AspNetCore.Authorization;
 | 
			
		||||
using Kyoo.Models.Permissions;
 | 
			
		||||
using Microsoft.AspNetCore.Mvc;
 | 
			
		||||
using Microsoft.Extensions.Configuration;
 | 
			
		||||
 | 
			
		||||
@ -26,7 +26,7 @@ namespace Kyoo.CommonApi
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
		[HttpGet("{id:int}")]
 | 
			
		||||
		[Authorize(Policy = "Read")]
 | 
			
		||||
		[PartialPermission(Kind.Read)]
 | 
			
		||||
		public virtual async Task<ActionResult<T>> Get(int id)
 | 
			
		||||
		{
 | 
			
		||||
			T ret = await _repository.GetOrDefault(id);
 | 
			
		||||
@ -36,7 +36,7 @@ namespace Kyoo.CommonApi
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		[HttpGet("{slug}")]
 | 
			
		||||
		[Authorize(Policy = "Read")]
 | 
			
		||||
		[PartialPermission(Kind.Read)]
 | 
			
		||||
		public virtual async Task<ActionResult<T>> Get(string slug)
 | 
			
		||||
		{
 | 
			
		||||
			T ret = await _repository.Get(slug);
 | 
			
		||||
@ -46,7 +46,7 @@ namespace Kyoo.CommonApi
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		[HttpGet("count")]
 | 
			
		||||
		[Authorize(Policy = "Read")]
 | 
			
		||||
		[PartialPermission(Kind.Read)]
 | 
			
		||||
		public virtual async Task<ActionResult<int>> GetCount([FromQuery] Dictionary<string, string> where)
 | 
			
		||||
		{
 | 
			
		||||
			try
 | 
			
		||||
@ -60,7 +60,7 @@ namespace Kyoo.CommonApi
 | 
			
		||||
		}
 | 
			
		||||
		
 | 
			
		||||
		[HttpGet]
 | 
			
		||||
		[Authorize(Policy = "Read")]
 | 
			
		||||
		[PartialPermission(Kind.Read)]
 | 
			
		||||
		public virtual async Task<ActionResult<Page<T>>> GetAll([FromQuery] string sortBy,
 | 
			
		||||
			[FromQuery] int afterID,
 | 
			
		||||
			[FromQuery] Dictionary<string, string> where,
 | 
			
		||||
@ -90,7 +90,7 @@ namespace Kyoo.CommonApi
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		[HttpPost]
 | 
			
		||||
		[Authorize(Policy = "Write")]
 | 
			
		||||
		[PartialPermission(Kind.Create)]
 | 
			
		||||
		public virtual async Task<ActionResult<T>> Create([FromBody] T resource)
 | 
			
		||||
		{
 | 
			
		||||
			try
 | 
			
		||||
@ -109,7 +109,7 @@ namespace Kyoo.CommonApi
 | 
			
		||||
		}
 | 
			
		||||
		
 | 
			
		||||
		[HttpPut]
 | 
			
		||||
		[Authorize(Policy = "Write")]
 | 
			
		||||
		[PartialPermission(Kind.Write)]
 | 
			
		||||
		public virtual async Task<ActionResult<T>> Edit([FromQuery] bool resetOld, [FromBody] T resource)
 | 
			
		||||
		{
 | 
			
		||||
			try
 | 
			
		||||
@ -128,7 +128,7 @@ namespace Kyoo.CommonApi
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		[HttpPut("{id:int}")]
 | 
			
		||||
		[Authorize(Policy = "Write")]
 | 
			
		||||
		[PartialPermission(Kind.Write)]
 | 
			
		||||
		public virtual async Task<ActionResult<T>> Edit(int id, [FromQuery] bool resetOld, [FromBody] T resource)
 | 
			
		||||
		{
 | 
			
		||||
			resource.ID = id;
 | 
			
		||||
@ -143,7 +143,7 @@ namespace Kyoo.CommonApi
 | 
			
		||||
		}
 | 
			
		||||
		
 | 
			
		||||
		[HttpPut("{slug}")]
 | 
			
		||||
		[Authorize(Policy = "Write")]
 | 
			
		||||
		[PartialPermission(Kind.Write)]
 | 
			
		||||
		public virtual async Task<ActionResult<T>> Edit(string slug, [FromQuery] bool resetOld, [FromBody] T resource)
 | 
			
		||||
		{
 | 
			
		||||
			try
 | 
			
		||||
@ -159,7 +159,7 @@ namespace Kyoo.CommonApi
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		[HttpDelete("{id:int}")]
 | 
			
		||||
		[Authorize(Policy = "Write")]
 | 
			
		||||
		[PartialPermission(Kind.Delete)]
 | 
			
		||||
		public virtual async Task<IActionResult> Delete(int id)
 | 
			
		||||
		{
 | 
			
		||||
			try
 | 
			
		||||
@ -175,7 +175,7 @@ namespace Kyoo.CommonApi
 | 
			
		||||
		}
 | 
			
		||||
		
 | 
			
		||||
		[HttpDelete("{slug}")]
 | 
			
		||||
		[Authorize(Policy = "Write")]
 | 
			
		||||
		[PartialPermission(Kind.Delete)]
 | 
			
		||||
		public virtual async Task<IActionResult> Delete(string slug)
 | 
			
		||||
		{
 | 
			
		||||
			try
 | 
			
		||||
@ -190,7 +190,7 @@ namespace Kyoo.CommonApi
 | 
			
		||||
			return Ok();
 | 
			
		||||
		}
 | 
			
		||||
		
 | 
			
		||||
		[Authorize(Policy = "Write")]
 | 
			
		||||
		[PartialPermission(Kind.Delete)]
 | 
			
		||||
		public virtual async Task<IActionResult> Delete(Dictionary<string, string> where)
 | 
			
		||||
		{
 | 
			
		||||
			try
 | 
			
		||||
 | 
			
		||||
@ -7,6 +7,7 @@ using Kyoo.Postgresql;
 | 
			
		||||
using Kyoo.Tasks;
 | 
			
		||||
using Microsoft.AspNetCore.Builder;
 | 
			
		||||
using Microsoft.AspNetCore.Hosting;
 | 
			
		||||
using Microsoft.AspNetCore.Mvc;
 | 
			
		||||
using Microsoft.AspNetCore.SpaServices.AngularCli;
 | 
			
		||||
using Microsoft.AspNetCore.StaticFiles;
 | 
			
		||||
using Microsoft.Extensions.Configuration;
 | 
			
		||||
@ -131,7 +132,7 @@ namespace Kyoo
 | 
			
		||||
 | 
			
		||||
			app.UseEndpoints(endpoints =>
 | 
			
		||||
			{
 | 
			
		||||
				endpoints.MapControllerRoute("Kyoo", "api/{controller=Home}/{action=Index}/{id?}");
 | 
			
		||||
				endpoints.MapControllers();
 | 
			
		||||
			});
 | 
			
		||||
			
 | 
			
		||||
			
 | 
			
		||||
 | 
			
		||||
@ -6,7 +6,7 @@ using Kyoo.Models;
 | 
			
		||||
using Microsoft.AspNetCore.Mvc;
 | 
			
		||||
using System.Threading.Tasks;
 | 
			
		||||
using Kyoo.CommonApi;
 | 
			
		||||
using Microsoft.AspNetCore.Authorization;
 | 
			
		||||
using Kyoo.Models.Permissions;
 | 
			
		||||
using Microsoft.Extensions.Configuration;
 | 
			
		||||
 | 
			
		||||
namespace Kyoo.Api
 | 
			
		||||
@ -14,6 +14,7 @@ namespace Kyoo.Api
 | 
			
		||||
	[Route("api/collection")]
 | 
			
		||||
	[Route("api/collections")]
 | 
			
		||||
	[ApiController]
 | 
			
		||||
	[PartialPermission(nameof(CollectionApi))]
 | 
			
		||||
	public class CollectionApi : CrudApi<Collection>
 | 
			
		||||
	{
 | 
			
		||||
		private readonly ILibraryManager _libraryManager;
 | 
			
		||||
@ -26,7 +27,7 @@ namespace Kyoo.Api
 | 
			
		||||
		
 | 
			
		||||
		[HttpGet("{id:int}/show")]
 | 
			
		||||
		[HttpGet("{id:int}/shows")]
 | 
			
		||||
		[Authorize(Policy = "Read")]
 | 
			
		||||
		[PartialPermission(Kind.Read)]
 | 
			
		||||
		public async Task<ActionResult<Page<Show>>> GetShows(int id,
 | 
			
		||||
			[FromQuery] string sortBy,
 | 
			
		||||
			[FromQuery] int afterID,
 | 
			
		||||
@ -52,7 +53,7 @@ namespace Kyoo.Api
 | 
			
		||||
		
 | 
			
		||||
		[HttpGet("{slug}/show")]
 | 
			
		||||
		[HttpGet("{slug}/shows")]
 | 
			
		||||
		[Authorize(Policy = "Read")]
 | 
			
		||||
		[PartialPermission(Kind.Read)]
 | 
			
		||||
		public async Task<ActionResult<Page<Show>>> GetShows(string slug,
 | 
			
		||||
			[FromQuery] string sortBy,
 | 
			
		||||
			[FromQuery] int afterID,
 | 
			
		||||
@ -66,7 +67,7 @@ namespace Kyoo.Api
 | 
			
		||||
					new Sort<Show>(sortBy),
 | 
			
		||||
					new Pagination(limit, afterID));
 | 
			
		||||
 | 
			
		||||
				if (!resources.Any() && await _libraryManager.Get<Collection>(slug) == null)
 | 
			
		||||
				if (!resources.Any() && await _libraryManager.GetOrDefault<Collection>(slug) == null)
 | 
			
		||||
					return NotFound();
 | 
			
		||||
				return Page(resources, limit);
 | 
			
		||||
			}
 | 
			
		||||
@ -78,7 +79,7 @@ namespace Kyoo.Api
 | 
			
		||||
		
 | 
			
		||||
		[HttpGet("{id:int}/library")]
 | 
			
		||||
		[HttpGet("{id:int}/libraries")]
 | 
			
		||||
		[Authorize(Policy = "Read")]
 | 
			
		||||
		[PartialPermission(Kind.Read)]
 | 
			
		||||
		public async Task<ActionResult<Page<Library>>> GetLibraries(int id,
 | 
			
		||||
			[FromQuery] string sortBy,
 | 
			
		||||
			[FromQuery] int afterID,
 | 
			
		||||
@ -104,7 +105,7 @@ namespace Kyoo.Api
 | 
			
		||||
		
 | 
			
		||||
		[HttpGet("{slug}/library")]
 | 
			
		||||
		[HttpGet("{slug}/libraries")]
 | 
			
		||||
		[Authorize(Policy = "Read")]
 | 
			
		||||
		[PartialPermission(Kind.Read)]
 | 
			
		||||
		public async Task<ActionResult<Page<Library>>> GetLibraries(string slug,
 | 
			
		||||
			[FromQuery] string sortBy,
 | 
			
		||||
			[FromQuery] int afterID,
 | 
			
		||||
@ -118,7 +119,7 @@ namespace Kyoo.Api
 | 
			
		||||
					new Sort<Library>(sortBy),
 | 
			
		||||
					new Pagination(limit, afterID));
 | 
			
		||||
 | 
			
		||||
				if (!resources.Any() && await _libraryManager.Get<Collection>(slug) == null)
 | 
			
		||||
				if (!resources.Any() && await _libraryManager.GetOrDefault<Collection>(slug) == null)
 | 
			
		||||
					return NotFound();
 | 
			
		||||
				return Page(resources, limit);
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
@ -7,7 +7,7 @@ using System.Threading.Tasks;
 | 
			
		||||
using Kyoo.CommonApi;
 | 
			
		||||
using Kyoo.Controllers;
 | 
			
		||||
using Kyoo.Models.Exceptions;
 | 
			
		||||
using Microsoft.AspNetCore.Authorization;
 | 
			
		||||
using Kyoo.Models.Permissions;
 | 
			
		||||
using Microsoft.Extensions.Configuration;
 | 
			
		||||
 | 
			
		||||
namespace Kyoo.Api
 | 
			
		||||
@ -15,6 +15,7 @@ namespace Kyoo.Api
 | 
			
		||||
	[Route("api/episode")]
 | 
			
		||||
	[Route("api/episodes")]
 | 
			
		||||
	[ApiController]
 | 
			
		||||
	[PartialPermission(nameof(EpisodeApi))]
 | 
			
		||||
	public class EpisodeApi : CrudApi<Episode>
 | 
			
		||||
	{
 | 
			
		||||
		private readonly ILibraryManager _libraryManager;
 | 
			
		||||
@ -33,21 +34,27 @@ namespace Kyoo.Api
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		[HttpGet("{episodeID:int}/show")]
 | 
			
		||||
		[Authorize(Policy = "Read")]
 | 
			
		||||
		[PartialPermission(Kind.Read)]
 | 
			
		||||
		public async Task<ActionResult<Show>> GetShow(int episodeID)
 | 
			
		||||
		{
 | 
			
		||||
			return await _libraryManager.Get<Show>(x => x.Episodes.Any(y => y.ID  == episodeID));
 | 
			
		||||
			Show ret =  await _libraryManager.GetOrDefault<Show>(x => x.Episodes.Any(y => y.ID  == episodeID));
 | 
			
		||||
			if (ret == null)
 | 
			
		||||
				return NotFound();
 | 
			
		||||
			return ret;
 | 
			
		||||
		}
 | 
			
		||||
		
 | 
			
		||||
		[HttpGet("{showSlug}-s{seasonNumber:int}e{episodeNumber:int}/show")]
 | 
			
		||||
		[Authorize(Policy = "Read")]
 | 
			
		||||
		[PartialPermission(Kind.Read)]
 | 
			
		||||
		public async Task<ActionResult<Show>> GetShow(string showSlug, int seasonNumber, int episodeNumber)
 | 
			
		||||
		{
 | 
			
		||||
			return await _libraryManager.Get<Show>(showSlug);
 | 
			
		||||
			Show ret = await _libraryManager.GetOrDefault<Show>(showSlug);
 | 
			
		||||
			if (ret == null)
 | 
			
		||||
				return NotFound();
 | 
			
		||||
			return ret;
 | 
			
		||||
		}
 | 
			
		||||
		
 | 
			
		||||
		[HttpGet("{showID:int}-{seasonNumber:int}e{episodeNumber:int}/show")]
 | 
			
		||||
		[Authorize(Policy = "Read")]
 | 
			
		||||
		[PartialPermission(Kind.Read)]
 | 
			
		||||
		public async Task<ActionResult<Show>> GetShow(int showID, int seasonNumber, int episodeNumber)
 | 
			
		||||
		{
 | 
			
		||||
			Show ret = await _libraryManager.GetOrDefault<Show>(showID);
 | 
			
		||||
@ -57,7 +64,7 @@ namespace Kyoo.Api
 | 
			
		||||
		}
 | 
			
		||||
		
 | 
			
		||||
		[HttpGet("{episodeID:int}/season")]
 | 
			
		||||
		[Authorize(Policy = "Read")]
 | 
			
		||||
		[PartialPermission(Kind.Read)]
 | 
			
		||||
		public async Task<ActionResult<Season>> GetSeason(int episodeID)
 | 
			
		||||
		{
 | 
			
		||||
			Season ret = await _libraryManager.GetOrDefault<Season>(x => x.Episodes.Any(y => y.ID == episodeID));
 | 
			
		||||
@ -67,7 +74,7 @@ namespace Kyoo.Api
 | 
			
		||||
		}
 | 
			
		||||
		
 | 
			
		||||
		[HttpGet("{showSlug}-s{seasonNumber:int}e{episodeNumber:int}/season")]
 | 
			
		||||
		[Authorize(Policy = "Read")]
 | 
			
		||||
		[PartialPermission(Kind.Read)]
 | 
			
		||||
		public async Task<ActionResult<Season>> GetSeason(string showSlug, int seasonNumber, int episodeNumber)
 | 
			
		||||
		{
 | 
			
		||||
			try
 | 
			
		||||
@ -81,7 +88,7 @@ namespace Kyoo.Api
 | 
			
		||||
		}
 | 
			
		||||
		
 | 
			
		||||
		[HttpGet("{showID:int}-{seasonNumber:int}e{episodeNumber:int}/season")]
 | 
			
		||||
		[Authorize(Policy = "Read")]
 | 
			
		||||
		[PartialPermission(Kind.Read)]
 | 
			
		||||
		public async Task<ActionResult<Season>> GetSeason(int showID, int seasonNumber, int episodeNumber)
 | 
			
		||||
		{
 | 
			
		||||
			try
 | 
			
		||||
@ -96,7 +103,7 @@ namespace Kyoo.Api
 | 
			
		||||
		
 | 
			
		||||
		[HttpGet("{episodeID:int}/track")]
 | 
			
		||||
		[HttpGet("{episodeID:int}/tracks")]
 | 
			
		||||
		[Authorize(Policy = "Read")]
 | 
			
		||||
		[PartialPermission(Kind.Read)]
 | 
			
		||||
		public async Task<ActionResult<Page<Track>>> GetEpisode(int episodeID,
 | 
			
		||||
			[FromQuery] string sortBy,
 | 
			
		||||
			[FromQuery] int afterID,
 | 
			
		||||
@ -122,7 +129,7 @@ namespace Kyoo.Api
 | 
			
		||||
		
 | 
			
		||||
		[HttpGet("{showID:int}-s{seasonNumber:int}e{episodeNumber:int}/track")]
 | 
			
		||||
		[HttpGet("{showID:int}-s{seasonNumber:int}e{episodeNumber:int}/tracks")]
 | 
			
		||||
		[Authorize(Policy = "Read")]
 | 
			
		||||
		[PartialPermission(Kind.Read)]
 | 
			
		||||
		public async Task<ActionResult<Page<Track>>> GetEpisode(int showID,
 | 
			
		||||
			int seasonNumber,
 | 
			
		||||
			int episodeNumber,
 | 
			
		||||
@ -152,7 +159,7 @@ namespace Kyoo.Api
 | 
			
		||||
		
 | 
			
		||||
		[HttpGet("{slug}-s{seasonNumber:int}e{episodeNumber:int}/track")]
 | 
			
		||||
		[HttpGet("{slug}-s{seasonNumber:int}e{episodeNumber:int}/tracks")]
 | 
			
		||||
		[Authorize(Policy = "Read")]
 | 
			
		||||
		[PartialPermission(Kind.Read)]
 | 
			
		||||
		public async Task<ActionResult<Page<Track>>> GetEpisode(string slug,
 | 
			
		||||
			int seasonNumber,
 | 
			
		||||
			int episodeNumber,
 | 
			
		||||
 | 
			
		||||
@ -5,7 +5,7 @@ using System.Threading.Tasks;
 | 
			
		||||
using Kyoo.CommonApi;
 | 
			
		||||
using Kyoo.Controllers;
 | 
			
		||||
using Kyoo.Models;
 | 
			
		||||
using Microsoft.AspNetCore.Authorization;
 | 
			
		||||
using Kyoo.Models.Permissions;
 | 
			
		||||
using Microsoft.AspNetCore.Mvc;
 | 
			
		||||
using Microsoft.Extensions.Configuration;
 | 
			
		||||
 | 
			
		||||
@ -14,6 +14,7 @@ namespace Kyoo.Api
 | 
			
		||||
	[Route("api/genre")]
 | 
			
		||||
	[Route("api/genres")]
 | 
			
		||||
	[ApiController]
 | 
			
		||||
	[PartialPermission(nameof(GenreApi))]
 | 
			
		||||
	public class GenreApi : CrudApi<Genre>
 | 
			
		||||
	{
 | 
			
		||||
		private readonly ILibraryManager _libraryManager;
 | 
			
		||||
@ -26,7 +27,7 @@ namespace Kyoo.Api
 | 
			
		||||
		
 | 
			
		||||
		[HttpGet("{id:int}/show")]
 | 
			
		||||
		[HttpGet("{id:int}/shows")]
 | 
			
		||||
		[Authorize(Policy = "Read")]
 | 
			
		||||
		[PartialPermission(Kind.Read)]
 | 
			
		||||
		public async Task<ActionResult<Page<Show>>> GetShows(int id,
 | 
			
		||||
			[FromQuery] string sortBy,
 | 
			
		||||
			[FromQuery] int afterID,
 | 
			
		||||
@ -52,7 +53,7 @@ namespace Kyoo.Api
 | 
			
		||||
		
 | 
			
		||||
		[HttpGet("{slug}/show")]
 | 
			
		||||
		[HttpGet("{slug}/shows")]
 | 
			
		||||
		[Authorize(Policy = "Read")]
 | 
			
		||||
		[PartialPermission(Kind.Read)]
 | 
			
		||||
		public async Task<ActionResult<Page<Show>>> GetShows(string slug,
 | 
			
		||||
			[FromQuery] string sortBy,
 | 
			
		||||
			[FromQuery] int afterID,
 | 
			
		||||
@ -66,7 +67,7 @@ namespace Kyoo.Api
 | 
			
		||||
					new Sort<Show>(sortBy),
 | 
			
		||||
					new Pagination(limit, afterID));
 | 
			
		||||
 | 
			
		||||
				if (!resources.Any() && await _libraryManager.Get<Genre>(slug) == null)
 | 
			
		||||
				if (!resources.Any() && await _libraryManager.GetOrDefault<Genre>(slug) == null)
 | 
			
		||||
					return NotFound();
 | 
			
		||||
				return Page(resources, limit);
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
@ -6,7 +6,7 @@ using Kyoo.Models;
 | 
			
		||||
using Microsoft.AspNetCore.Mvc;
 | 
			
		||||
using System.Threading.Tasks;
 | 
			
		||||
using Kyoo.CommonApi;
 | 
			
		||||
using Microsoft.AspNetCore.Authorization;
 | 
			
		||||
using Kyoo.Models.Permissions;
 | 
			
		||||
using Microsoft.Extensions.Configuration;
 | 
			
		||||
 | 
			
		||||
namespace Kyoo.Api
 | 
			
		||||
@ -14,6 +14,7 @@ namespace Kyoo.Api
 | 
			
		||||
	[Route("api/library")]
 | 
			
		||||
	[Route("api/libraries")]
 | 
			
		||||
	[ApiController]
 | 
			
		||||
	[PartialPermission(nameof(LibraryAPI))]
 | 
			
		||||
	public class LibraryAPI : CrudApi<Library>
 | 
			
		||||
	{
 | 
			
		||||
		private readonly ILibraryManager _libraryManager;
 | 
			
		||||
@ -26,7 +27,7 @@ namespace Kyoo.Api
 | 
			
		||||
			_taskManager = taskManager;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		[Authorize(Policy = "Write")]
 | 
			
		||||
		[PartialPermission(Kind.Create)]
 | 
			
		||||
		public override async Task<ActionResult<Library>> Create(Library resource)
 | 
			
		||||
		{
 | 
			
		||||
			ActionResult<Library> result = await base.Create(resource);
 | 
			
		||||
@ -37,7 +38,7 @@ namespace Kyoo.Api
 | 
			
		||||
		
 | 
			
		||||
		[HttpGet("{id:int}/show")]
 | 
			
		||||
		[HttpGet("{id:int}/shows")]
 | 
			
		||||
		[Authorize(Policy = "Read")]
 | 
			
		||||
		[PartialPermission(Kind.Read)]
 | 
			
		||||
		public async Task<ActionResult<Page<Show>>> GetShows(int id,
 | 
			
		||||
			[FromQuery] string sortBy,
 | 
			
		||||
			[FromQuery] int afterID,
 | 
			
		||||
@ -63,7 +64,7 @@ namespace Kyoo.Api
 | 
			
		||||
 | 
			
		||||
		[HttpGet("{slug}/show")]
 | 
			
		||||
		[HttpGet("{slug}/shows")]
 | 
			
		||||
		[Authorize(Policy = "Read")]
 | 
			
		||||
		[PartialPermission(Kind.Read)]
 | 
			
		||||
		public async Task<ActionResult<Page<Show>>> GetShows(string slug,
 | 
			
		||||
			[FromQuery] string sortBy,
 | 
			
		||||
			[FromQuery] int afterID,
 | 
			
		||||
@ -77,7 +78,7 @@ namespace Kyoo.Api
 | 
			
		||||
					new Sort<Show>(sortBy),
 | 
			
		||||
					new Pagination(limit, afterID));
 | 
			
		||||
 | 
			
		||||
				if (!resources.Any() && await _libraryManager.Get<Library>(slug) == null)
 | 
			
		||||
				if (!resources.Any() && await _libraryManager.GetOrDefault<Library>(slug) == null)
 | 
			
		||||
					return NotFound();
 | 
			
		||||
				return Page(resources, limit);
 | 
			
		||||
			}
 | 
			
		||||
@ -89,7 +90,7 @@ namespace Kyoo.Api
 | 
			
		||||
		
 | 
			
		||||
		[HttpGet("{id:int}/collection")]
 | 
			
		||||
		[HttpGet("{id:int}/collections")]
 | 
			
		||||
		[Authorize(Policy = "Read")]
 | 
			
		||||
		[PartialPermission(Kind.Read)]
 | 
			
		||||
		public async Task<ActionResult<Page<Collection>>> GetCollections(int id,
 | 
			
		||||
			[FromQuery] string sortBy,
 | 
			
		||||
			[FromQuery] int afterID,
 | 
			
		||||
@ -115,7 +116,7 @@ namespace Kyoo.Api
 | 
			
		||||
 | 
			
		||||
		[HttpGet("{slug}/collection")]
 | 
			
		||||
		[HttpGet("{slug}/collections")]
 | 
			
		||||
		[Authorize(Policy = "Read")]
 | 
			
		||||
		[PartialPermission(Kind.Read)]
 | 
			
		||||
		public async Task<ActionResult<Page<Collection>>> GetCollections(string slug,
 | 
			
		||||
			[FromQuery] string sortBy,
 | 
			
		||||
			[FromQuery] int afterID,
 | 
			
		||||
@ -129,7 +130,7 @@ namespace Kyoo.Api
 | 
			
		||||
					new Sort<Collection>(sortBy),
 | 
			
		||||
					new Pagination(limit, afterID));
 | 
			
		||||
 | 
			
		||||
				if (!resources.Any() && await _libraryManager.Get<Library>(slug) == null)
 | 
			
		||||
				if (!resources.Any() && await _libraryManager.GetOrDefault<Library>(slug) == null)
 | 
			
		||||
					return NotFound();
 | 
			
		||||
				return Page(resources, limit);
 | 
			
		||||
			}
 | 
			
		||||
@ -141,7 +142,7 @@ namespace Kyoo.Api
 | 
			
		||||
		
 | 
			
		||||
		[HttpGet("{id:int}/item")]
 | 
			
		||||
		[HttpGet("{id:int}/items")]
 | 
			
		||||
		[Authorize(Policy = "Read")]
 | 
			
		||||
		[PartialPermission(Kind.Read)]
 | 
			
		||||
		public async Task<ActionResult<Page<LibraryItem>>> GetItems(int id,
 | 
			
		||||
			[FromQuery] string sortBy,
 | 
			
		||||
			[FromQuery] int afterID,
 | 
			
		||||
@ -167,7 +168,7 @@ namespace Kyoo.Api
 | 
			
		||||
		
 | 
			
		||||
		[HttpGet("{slug}/item")]
 | 
			
		||||
		[HttpGet("{slug}/items")]
 | 
			
		||||
		[Authorize(Policy = "Read")]
 | 
			
		||||
		[PartialPermission(Kind.Read)]
 | 
			
		||||
		public async Task<ActionResult<Page<LibraryItem>>> GetItems(string slug,
 | 
			
		||||
			[FromQuery] string sortBy,
 | 
			
		||||
			[FromQuery] int afterID,
 | 
			
		||||
@ -181,7 +182,7 @@ namespace Kyoo.Api
 | 
			
		||||
					new Sort<LibraryItem>(sortBy),
 | 
			
		||||
					new Pagination(limit, afterID));
 | 
			
		||||
 | 
			
		||||
				if (!resources.Any() && await _libraryManager.Get<Library>(slug) == null)
 | 
			
		||||
				if (!resources.Any() && await _libraryManager.GetOrDefault<Library>(slug) == null)
 | 
			
		||||
					return NotFound();
 | 
			
		||||
				return Page(resources, limit);
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
@ -6,7 +6,7 @@ using Kyoo.CommonApi;
 | 
			
		||||
using Kyoo.Controllers;
 | 
			
		||||
using Kyoo.Models;
 | 
			
		||||
using Kyoo.Models.Exceptions;
 | 
			
		||||
using Microsoft.AspNetCore.Authorization;
 | 
			
		||||
using Kyoo.Models.Permissions;
 | 
			
		||||
using Microsoft.AspNetCore.Mvc;
 | 
			
		||||
using Microsoft.Extensions.Configuration;
 | 
			
		||||
 | 
			
		||||
@ -29,7 +29,7 @@ namespace Kyoo.Api
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		[HttpGet]
 | 
			
		||||
		[Authorize(Policy = "Read")]
 | 
			
		||||
		[Permission(nameof(LibraryItemApi), Kind.Read)]
 | 
			
		||||
		public async Task<ActionResult<Page<LibraryItem>>> GetAll([FromQuery] string sortBy,
 | 
			
		||||
			[FromQuery] int afterID,
 | 
			
		||||
			[FromQuery] Dictionary<string, string> where,
 | 
			
		||||
 | 
			
		||||
@ -5,7 +5,7 @@ using Kyoo.CommonApi;
 | 
			
		||||
using Kyoo.Controllers;
 | 
			
		||||
using Kyoo.Models;
 | 
			
		||||
using Kyoo.Models.Exceptions;
 | 
			
		||||
using Microsoft.AspNetCore.Authorization;
 | 
			
		||||
using Kyoo.Models.Permissions;
 | 
			
		||||
using Microsoft.AspNetCore.Mvc;
 | 
			
		||||
using Microsoft.Extensions.Configuration;
 | 
			
		||||
 | 
			
		||||
@ -13,6 +13,7 @@ namespace Kyoo.Api
 | 
			
		||||
{
 | 
			
		||||
	[Route("api/people")]
 | 
			
		||||
	[ApiController]
 | 
			
		||||
	[PartialPermission(nameof(PeopleApi))]
 | 
			
		||||
	public class PeopleApi : CrudApi<People>
 | 
			
		||||
	{
 | 
			
		||||
		private readonly ILibraryManager _libraryManager;
 | 
			
		||||
@ -32,7 +33,7 @@ namespace Kyoo.Api
 | 
			
		||||
 | 
			
		||||
		[HttpGet("{id:int}/role")]
 | 
			
		||||
		[HttpGet("{id:int}/roles")]
 | 
			
		||||
		[Authorize(Policy = "Read")]
 | 
			
		||||
		[PartialPermission(Kind.Read)]
 | 
			
		||||
		public async Task<ActionResult<Page<PeopleRole>>> GetRoles(int id,
 | 
			
		||||
			[FromQuery] string sortBy,
 | 
			
		||||
			[FromQuery] int afterID,
 | 
			
		||||
@ -60,7 +61,7 @@ namespace Kyoo.Api
 | 
			
		||||
 | 
			
		||||
		[HttpGet("{slug}/role")]
 | 
			
		||||
		[HttpGet("{slug}/roles")]
 | 
			
		||||
		[Authorize(Policy = "Read")]
 | 
			
		||||
		[PartialPermission(Kind.Read)]
 | 
			
		||||
		public async Task<ActionResult<Page<PeopleRole>>> GetRoles(string slug,
 | 
			
		||||
			[FromQuery] string sortBy,
 | 
			
		||||
			[FromQuery] int afterID,
 | 
			
		||||
 | 
			
		||||
@ -2,6 +2,7 @@ using System.Threading.Tasks;
 | 
			
		||||
using Kyoo.CommonApi;
 | 
			
		||||
using Kyoo.Controllers;
 | 
			
		||||
using Kyoo.Models;
 | 
			
		||||
using Kyoo.Models.Permissions;
 | 
			
		||||
using Microsoft.AspNetCore.Mvc;
 | 
			
		||||
using Microsoft.Extensions.Configuration;
 | 
			
		||||
 | 
			
		||||
@ -10,13 +11,14 @@ namespace Kyoo.Api
 | 
			
		||||
	[Route("api/provider")]
 | 
			
		||||
	[Route("api/providers")]
 | 
			
		||||
	[ApiController]
 | 
			
		||||
	public class ProviderAPI : CrudApi<Provider>
 | 
			
		||||
	[PartialPermission(nameof(ProviderApi))]
 | 
			
		||||
	public class ProviderApi : CrudApi<Provider>
 | 
			
		||||
	{
 | 
			
		||||
		private readonly IThumbnailsManager _thumbnails;
 | 
			
		||||
		private readonly ILibraryManager _libraryManager;
 | 
			
		||||
		private readonly IFileManager _files;
 | 
			
		||||
		
 | 
			
		||||
		public ProviderAPI(ILibraryManager libraryManager,
 | 
			
		||||
		public ProviderApi(ILibraryManager libraryManager,
 | 
			
		||||
			IConfiguration config,
 | 
			
		||||
			IFileManager files,
 | 
			
		||||
			IThumbnailsManager thumbnails)
 | 
			
		||||
 | 
			
		||||
@ -2,7 +2,7 @@
 | 
			
		||||
using System.Threading.Tasks;
 | 
			
		||||
using Kyoo.Controllers;
 | 
			
		||||
using Kyoo.Models;
 | 
			
		||||
using Microsoft.AspNetCore.Authorization;
 | 
			
		||||
using Kyoo.Models.Permissions;
 | 
			
		||||
using Microsoft.AspNetCore.Mvc;
 | 
			
		||||
 | 
			
		||||
namespace Kyoo.Api
 | 
			
		||||
@ -19,7 +19,12 @@ namespace Kyoo.Api
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		[HttpGet]
 | 
			
		||||
		[Authorize(Policy="Read")]
 | 
			
		||||
		[Permission(nameof(Collection), Kind.Read)]
 | 
			
		||||
		[Permission(nameof(Show), Kind.Read)]
 | 
			
		||||
		[Permission(nameof(Episode), Kind.Read)]
 | 
			
		||||
		[Permission(nameof(People), Kind.Read)]
 | 
			
		||||
		[Permission(nameof(Genre), Kind.Read)]
 | 
			
		||||
		[Permission(nameof(Studio), Kind.Read)]
 | 
			
		||||
		public async Task<ActionResult<SearchResult>> Search(string query)
 | 
			
		||||
		{
 | 
			
		||||
			return new SearchResult
 | 
			
		||||
@ -36,7 +41,7 @@ namespace Kyoo.Api
 | 
			
		||||
		
 | 
			
		||||
		[HttpGet("collection")]
 | 
			
		||||
		[HttpGet("collections")]
 | 
			
		||||
		[Authorize(Policy="Read")]
 | 
			
		||||
		[Permission(nameof(Collection), Kind.Read)]
 | 
			
		||||
		public Task<ICollection<Collection>> SearchCollections(string query)
 | 
			
		||||
		{
 | 
			
		||||
			return _libraryManager.Search<Collection>(query);
 | 
			
		||||
@ -44,7 +49,7 @@ namespace Kyoo.Api
 | 
			
		||||
		
 | 
			
		||||
		[HttpGet("show")]
 | 
			
		||||
		[HttpGet("shows")]
 | 
			
		||||
		[Authorize(Policy="Read")]
 | 
			
		||||
		[Permission(nameof(Show), Kind.Read)]
 | 
			
		||||
		public Task<ICollection<Show>> SearchShows(string query)
 | 
			
		||||
		{
 | 
			
		||||
			return _libraryManager.Search<Show>(query);
 | 
			
		||||
@ -52,14 +57,14 @@ namespace Kyoo.Api
 | 
			
		||||
		
 | 
			
		||||
		[HttpGet("episode")]
 | 
			
		||||
		[HttpGet("episodes")]
 | 
			
		||||
		[Authorize(Policy="Read")]
 | 
			
		||||
		[Permission(nameof(Episode), Kind.Read)]
 | 
			
		||||
		public Task<ICollection<Episode>> SearchEpisodes(string query)
 | 
			
		||||
		{
 | 
			
		||||
			return _libraryManager.Search<Episode>(query);
 | 
			
		||||
		}
 | 
			
		||||
		
 | 
			
		||||
		[HttpGet("people")]
 | 
			
		||||
		[Authorize(Policy="Read")]
 | 
			
		||||
		[Permission(nameof(People), Kind.Read)]
 | 
			
		||||
		public Task<ICollection<People>> SearchPeople(string query)
 | 
			
		||||
		{
 | 
			
		||||
			return _libraryManager.Search<People>(query);
 | 
			
		||||
@ -67,7 +72,7 @@ namespace Kyoo.Api
 | 
			
		||||
		
 | 
			
		||||
		[HttpGet("genre")]
 | 
			
		||||
		[HttpGet("genres")]
 | 
			
		||||
		[Authorize(Policy="Read")]
 | 
			
		||||
		[Permission(nameof(Genre), Kind.Read)]
 | 
			
		||||
		public Task<ICollection<Genre>> SearchGenres(string query)
 | 
			
		||||
		{
 | 
			
		||||
			return _libraryManager.Search<Genre>(query);
 | 
			
		||||
@ -75,7 +80,7 @@ namespace Kyoo.Api
 | 
			
		||||
		
 | 
			
		||||
		[HttpGet("studio")]
 | 
			
		||||
		[HttpGet("studios")]
 | 
			
		||||
		[Authorize(Policy="Read")]
 | 
			
		||||
		[Permission(nameof(Studio), Kind.Read)]
 | 
			
		||||
		public Task<ICollection<Studio>> SearchStudios(string query)
 | 
			
		||||
		{
 | 
			
		||||
			return _libraryManager.Search<Studio>(query);
 | 
			
		||||
 | 
			
		||||
@ -4,9 +4,9 @@ using System.Threading.Tasks;
 | 
			
		||||
using Kyoo.CommonApi;
 | 
			
		||||
using Kyoo.Controllers;
 | 
			
		||||
using Kyoo.Models;
 | 
			
		||||
using Microsoft.AspNetCore.Authorization;
 | 
			
		||||
using Microsoft.AspNetCore.Mvc;
 | 
			
		||||
using System.Linq;
 | 
			
		||||
using Kyoo.Models.Permissions;
 | 
			
		||||
using Microsoft.Extensions.Configuration;
 | 
			
		||||
 | 
			
		||||
namespace Kyoo.Api
 | 
			
		||||
@ -14,6 +14,7 @@ namespace Kyoo.Api
 | 
			
		||||
	[Route("api/season")]
 | 
			
		||||
	[Route("api/seasons")]
 | 
			
		||||
	[ApiController]
 | 
			
		||||
	[PartialPermission(nameof(SeasonApi))]
 | 
			
		||||
	public class SeasonApi : CrudApi<Season>
 | 
			
		||||
	{
 | 
			
		||||
		private readonly ILibraryManager _libraryManager;
 | 
			
		||||
@ -33,7 +34,7 @@ namespace Kyoo.Api
 | 
			
		||||
		
 | 
			
		||||
		[HttpGet("{seasonID:int}/episode")]
 | 
			
		||||
		[HttpGet("{seasonID:int}/episodes")]
 | 
			
		||||
		[Authorize(Policy = "Read")]
 | 
			
		||||
		[PartialPermission(Kind.Read)]
 | 
			
		||||
		public async Task<ActionResult<Page<Episode>>> GetEpisode(int seasonID,
 | 
			
		||||
			[FromQuery] string sortBy,
 | 
			
		||||
			[FromQuery] int afterID,
 | 
			
		||||
@ -59,7 +60,7 @@ namespace Kyoo.Api
 | 
			
		||||
		
 | 
			
		||||
		[HttpGet("{showSlug}-s{seasonNumber:int}/episode")]
 | 
			
		||||
		[HttpGet("{showSlug}-s{seasonNumber:int}/episodes")]
 | 
			
		||||
		[Authorize(Policy = "Read")]
 | 
			
		||||
		[PartialPermission(Kind.Read)]
 | 
			
		||||
		public async Task<ActionResult<Page<Episode>>> GetEpisode(string showSlug,
 | 
			
		||||
			int seasonNumber,
 | 
			
		||||
			[FromQuery] string sortBy,
 | 
			
		||||
@ -87,7 +88,7 @@ namespace Kyoo.Api
 | 
			
		||||
		
 | 
			
		||||
		[HttpGet("{showID:int}-s{seasonNumber:int}/episode")]
 | 
			
		||||
		[HttpGet("{showID:int}-s{seasonNumber:int}/episodes")]
 | 
			
		||||
		[Authorize(Policy = "Read")]
 | 
			
		||||
		[PartialPermission(Kind.Read)]
 | 
			
		||||
		public async Task<ActionResult<Page<Episode>>> GetEpisode(int showID,
 | 
			
		||||
			int seasonNumber,
 | 
			
		||||
			[FromQuery] string sortBy,
 | 
			
		||||
@ -113,21 +114,27 @@ namespace Kyoo.Api
 | 
			
		||||
		}
 | 
			
		||||
		
 | 
			
		||||
		[HttpGet("{seasonID:int}/show")]
 | 
			
		||||
		[Authorize(Policy = "Read")]
 | 
			
		||||
		[PartialPermission(Kind.Read)]
 | 
			
		||||
		public async Task<ActionResult<Show>> GetShow(int seasonID)
 | 
			
		||||
		{
 | 
			
		||||
			return await _libraryManager.Get<Show>(x => x.Seasons.Any(y => y.ID == seasonID));
 | 
			
		||||
			Show ret = await _libraryManager.GetOrDefault<Show>(x => x.Seasons.Any(y => y.ID == seasonID));
 | 
			
		||||
			if (ret == null)
 | 
			
		||||
				return NotFound();
 | 
			
		||||
			return ret;
 | 
			
		||||
		}
 | 
			
		||||
		
 | 
			
		||||
		[HttpGet("{showSlug}-s{seasonNumber:int}/show")]
 | 
			
		||||
		[Authorize(Policy = "Read")]
 | 
			
		||||
		[PartialPermission(Kind.Read)]
 | 
			
		||||
		public async Task<ActionResult<Show>> GetShow(string showSlug, int seasonNumber)
 | 
			
		||||
		{
 | 
			
		||||
			return await _libraryManager.Get<Show>(showSlug);
 | 
			
		||||
			Show ret = await _libraryManager.GetOrDefault<Show>(showSlug);
 | 
			
		||||
			if (ret == null)
 | 
			
		||||
				return NotFound();
 | 
			
		||||
			return ret;
 | 
			
		||||
		}
 | 
			
		||||
		
 | 
			
		||||
		[HttpGet("{showID:int}-s{seasonNumber:int}/show")]
 | 
			
		||||
		[Authorize(Policy = "Read")]
 | 
			
		||||
		[PartialPermission(Kind.Read)]
 | 
			
		||||
		public async Task<ActionResult<Show>> GetShow(int showID, int seasonNumber)
 | 
			
		||||
		{
 | 
			
		||||
			Show ret = await _libraryManager.GetOrDefault<Show>(showID);
 | 
			
		||||
 | 
			
		||||
@ -8,7 +8,7 @@ using System.Threading.Tasks;
 | 
			
		||||
using Kyoo.CommonApi;
 | 
			
		||||
using Kyoo.Controllers;
 | 
			
		||||
using Kyoo.Models.Exceptions;
 | 
			
		||||
using Microsoft.AspNetCore.Authorization;
 | 
			
		||||
using Kyoo.Models.Permissions;
 | 
			
		||||
using Microsoft.Extensions.Configuration;
 | 
			
		||||
 | 
			
		||||
namespace Kyoo.Api
 | 
			
		||||
@ -16,6 +16,7 @@ namespace Kyoo.Api
 | 
			
		||||
	[Route("api/show")]
 | 
			
		||||
	[Route("api/shows")]
 | 
			
		||||
	[ApiController]
 | 
			
		||||
	[PartialPermission(nameof(ShowApi))]
 | 
			
		||||
	public class ShowApi : CrudApi<Show>
 | 
			
		||||
	{
 | 
			
		||||
		private readonly ILibraryManager _libraryManager;
 | 
			
		||||
@ -35,7 +36,7 @@ namespace Kyoo.Api
 | 
			
		||||
 | 
			
		||||
		[HttpGet("{showID:int}/season")]
 | 
			
		||||
		[HttpGet("{showID:int}/seasons")]
 | 
			
		||||
		[Authorize(Policy = "Read")]
 | 
			
		||||
		[PartialPermission(Kind.Read)]
 | 
			
		||||
		public async Task<ActionResult<Page<Season>>> GetSeasons(int showID,
 | 
			
		||||
			[FromQuery] string sortBy,
 | 
			
		||||
			[FromQuery] int afterID,
 | 
			
		||||
@ -61,7 +62,7 @@ namespace Kyoo.Api
 | 
			
		||||
 | 
			
		||||
		[HttpGet("{slug}/season")]
 | 
			
		||||
		[HttpGet("{slug}/seasons")]
 | 
			
		||||
		[Authorize(Policy = "Read")]
 | 
			
		||||
		[PartialPermission(Kind.Read)]
 | 
			
		||||
		public async Task<ActionResult<Page<Season>>> GetSeasons(string slug,
 | 
			
		||||
			[FromQuery] string sortBy,
 | 
			
		||||
			[FromQuery] int afterID,
 | 
			
		||||
@ -87,7 +88,7 @@ namespace Kyoo.Api
 | 
			
		||||
		
 | 
			
		||||
		[HttpGet("{showID:int}/episode")]
 | 
			
		||||
		[HttpGet("{showID:int}/episodes")]
 | 
			
		||||
		[Authorize(Policy = "Read")]
 | 
			
		||||
		[PartialPermission(Kind.Read)]
 | 
			
		||||
		public async Task<ActionResult<Page<Episode>>> GetEpisodes(int showID,
 | 
			
		||||
			[FromQuery] string sortBy,
 | 
			
		||||
			[FromQuery] int afterID,
 | 
			
		||||
@ -113,7 +114,7 @@ namespace Kyoo.Api
 | 
			
		||||
 | 
			
		||||
		[HttpGet("{slug}/episode")]
 | 
			
		||||
		[HttpGet("{slug}/episodes")]
 | 
			
		||||
		[Authorize(Policy = "Read")]
 | 
			
		||||
		[PartialPermission(Kind.Read)]
 | 
			
		||||
		public async Task<ActionResult<Page<Episode>>> GetEpisodes(string slug,
 | 
			
		||||
			[FromQuery] string sortBy,
 | 
			
		||||
			[FromQuery] int afterID,
 | 
			
		||||
@ -138,7 +139,7 @@ namespace Kyoo.Api
 | 
			
		||||
		}
 | 
			
		||||
		
 | 
			
		||||
		[HttpGet("{showID:int}/people")]
 | 
			
		||||
		[Authorize(Policy = "Read")]
 | 
			
		||||
		[PartialPermission(Kind.Read)]
 | 
			
		||||
		public async Task<ActionResult<Page<PeopleRole>>> GetPeople(int showID,
 | 
			
		||||
			[FromQuery] string sortBy,
 | 
			
		||||
			[FromQuery] int afterID,
 | 
			
		||||
@ -163,7 +164,7 @@ namespace Kyoo.Api
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		[HttpGet("{slug}/people")]
 | 
			
		||||
		[Authorize(Policy = "Read")]
 | 
			
		||||
		[PartialPermission(Kind.Read)]
 | 
			
		||||
		public async Task<ActionResult<Page<PeopleRole>>> GetPeople(string slug,
 | 
			
		||||
			[FromQuery] string sortBy,
 | 
			
		||||
			[FromQuery] int afterID,
 | 
			
		||||
@ -189,7 +190,7 @@ namespace Kyoo.Api
 | 
			
		||||
		
 | 
			
		||||
		[HttpGet("{showID:int}/genre")]
 | 
			
		||||
		[HttpGet("{showID:int}/genres")]
 | 
			
		||||
		[Authorize(Policy = "Read")]
 | 
			
		||||
		[PartialPermission(Kind.Read)]
 | 
			
		||||
		public async Task<ActionResult<Page<Genre>>> GetGenres(int showID,
 | 
			
		||||
			[FromQuery] string sortBy,
 | 
			
		||||
			[FromQuery] int afterID,
 | 
			
		||||
@ -215,7 +216,7 @@ namespace Kyoo.Api
 | 
			
		||||
 | 
			
		||||
		[HttpGet("{slug}/genre")]
 | 
			
		||||
		[HttpGet("{slug}/genres")]
 | 
			
		||||
		[Authorize(Policy = "Read")]
 | 
			
		||||
		[PartialPermission(Kind.Read)]
 | 
			
		||||
		public async Task<ActionResult<Page<Genre>>> GetGenre(string slug,
 | 
			
		||||
			[FromQuery] string sortBy,
 | 
			
		||||
			[FromQuery] int afterID,
 | 
			
		||||
@ -240,7 +241,7 @@ namespace Kyoo.Api
 | 
			
		||||
		}
 | 
			
		||||
		
 | 
			
		||||
		[HttpGet("{showID:int}/studio")]
 | 
			
		||||
		[Authorize(Policy = "Read")]
 | 
			
		||||
		[PartialPermission(Kind.Read)]
 | 
			
		||||
		public async Task<ActionResult<Studio>> GetStudio(int showID)
 | 
			
		||||
		{
 | 
			
		||||
			try
 | 
			
		||||
@ -254,7 +255,7 @@ namespace Kyoo.Api
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		[HttpGet("{slug}/studio")]
 | 
			
		||||
		[Authorize(Policy = "Read")]
 | 
			
		||||
		[PartialPermission(Kind.Read)]
 | 
			
		||||
		public async Task<ActionResult<Studio>> GetStudio(string slug)
 | 
			
		||||
		{
 | 
			
		||||
			try
 | 
			
		||||
@ -269,7 +270,7 @@ namespace Kyoo.Api
 | 
			
		||||
		
 | 
			
		||||
		[HttpGet("{showID:int}/library")]
 | 
			
		||||
		[HttpGet("{showID:int}/libraries")]
 | 
			
		||||
		[Authorize(Policy = "Read")]
 | 
			
		||||
		[PartialPermission(Kind.Read)]
 | 
			
		||||
		public async Task<ActionResult<Page<Library>>> GetLibraries(int showID,
 | 
			
		||||
			[FromQuery] string sortBy,
 | 
			
		||||
			[FromQuery] int afterID,
 | 
			
		||||
@ -295,7 +296,7 @@ namespace Kyoo.Api
 | 
			
		||||
 | 
			
		||||
		[HttpGet("{slug}/library")]
 | 
			
		||||
		[HttpGet("{slug}/libraries")]
 | 
			
		||||
		[Authorize(Policy = "Read")]
 | 
			
		||||
		[PartialPermission(Kind.Read)]
 | 
			
		||||
		public async Task<ActionResult<Page<Library>>> GetLibraries(string slug,
 | 
			
		||||
			[FromQuery] string sortBy,
 | 
			
		||||
			[FromQuery] int afterID,
 | 
			
		||||
@ -321,7 +322,7 @@ namespace Kyoo.Api
 | 
			
		||||
		
 | 
			
		||||
		[HttpGet("{showID:int}/collection")]
 | 
			
		||||
		[HttpGet("{showID:int}/collections")]
 | 
			
		||||
		[Authorize(Policy = "Read")]
 | 
			
		||||
		[PartialPermission(Kind.Read)]
 | 
			
		||||
		public async Task<ActionResult<Page<Collection>>> GetCollections(int showID,
 | 
			
		||||
			[FromQuery] string sortBy,
 | 
			
		||||
			[FromQuery] int afterID,
 | 
			
		||||
@ -347,7 +348,7 @@ namespace Kyoo.Api
 | 
			
		||||
 | 
			
		||||
		[HttpGet("{slug}/collection")]
 | 
			
		||||
		[HttpGet("{slug}/collections")]
 | 
			
		||||
		[Authorize(Policy = "Read")]
 | 
			
		||||
		[PartialPermission(Kind.Read)]
 | 
			
		||||
		public async Task<ActionResult<Page<Collection>>> GetCollections(string slug,
 | 
			
		||||
			[FromQuery] string sortBy,
 | 
			
		||||
			[FromQuery] int afterID,
 | 
			
		||||
@ -373,7 +374,7 @@ namespace Kyoo.Api
 | 
			
		||||
 | 
			
		||||
		[HttpGet("{slug}/font")]
 | 
			
		||||
		[HttpGet("{slug}/fonts")]
 | 
			
		||||
		[Authorize(Policy = "Read")]
 | 
			
		||||
		[PartialPermission(Kind.Read)]
 | 
			
		||||
		public async Task<ActionResult<Dictionary<string, string>>> GetFonts(string slug)
 | 
			
		||||
		{
 | 
			
		||||
			try
 | 
			
		||||
@ -392,7 +393,7 @@ namespace Kyoo.Api
 | 
			
		||||
		
 | 
			
		||||
		[HttpGet("{showSlug}/font/{slug}")]
 | 
			
		||||
		[HttpGet("{showSlug}/fonts/{slug}")]
 | 
			
		||||
		[Authorize(Policy = "Read")]
 | 
			
		||||
		[PartialPermission(Kind.Read)]
 | 
			
		||||
		public async Task<IActionResult> GetFont(string showSlug, string slug)
 | 
			
		||||
		{
 | 
			
		||||
			try
 | 
			
		||||
 | 
			
		||||
@ -5,7 +5,7 @@ using System.Threading.Tasks;
 | 
			
		||||
using Kyoo.CommonApi;
 | 
			
		||||
using Kyoo.Controllers;
 | 
			
		||||
using Kyoo.Models;
 | 
			
		||||
using Microsoft.AspNetCore.Authorization;
 | 
			
		||||
using Kyoo.Models.Permissions;
 | 
			
		||||
using Microsoft.AspNetCore.Mvc;
 | 
			
		||||
using Microsoft.Extensions.Configuration;
 | 
			
		||||
 | 
			
		||||
@ -14,6 +14,7 @@ namespace Kyoo.Api
 | 
			
		||||
	[Route("api/studio")]
 | 
			
		||||
	[Route("api/studios")]
 | 
			
		||||
	[ApiController]
 | 
			
		||||
	[PartialPermission(nameof(ShowApi))]
 | 
			
		||||
	public class StudioAPI : CrudApi<Studio>
 | 
			
		||||
	{
 | 
			
		||||
		private readonly ILibraryManager _libraryManager;
 | 
			
		||||
@ -26,7 +27,7 @@ namespace Kyoo.Api
 | 
			
		||||
		
 | 
			
		||||
		[HttpGet("{id:int}/show")]
 | 
			
		||||
		[HttpGet("{id:int}/shows")]
 | 
			
		||||
		[Authorize(Policy = "Read")]
 | 
			
		||||
		[PartialPermission(Kind.Read)]
 | 
			
		||||
		public async Task<ActionResult<Page<Show>>> GetShows(int id,
 | 
			
		||||
			[FromQuery] string sortBy,
 | 
			
		||||
			[FromQuery] int afterID,
 | 
			
		||||
@ -52,7 +53,7 @@ namespace Kyoo.Api
 | 
			
		||||
		
 | 
			
		||||
		[HttpGet("{slug}/show")]
 | 
			
		||||
		[HttpGet("{slug}/shows")]
 | 
			
		||||
		[Authorize(Policy = "Read")]
 | 
			
		||||
		[PartialPermission(Kind.Read)]
 | 
			
		||||
		public async Task<ActionResult<Page<Show>>> GetShows(string slug,
 | 
			
		||||
			[FromQuery] string sortBy,
 | 
			
		||||
			[FromQuery] int afterID,
 | 
			
		||||
 | 
			
		||||
@ -5,7 +5,7 @@ using System.Collections.Generic;
 | 
			
		||||
using System.IO;
 | 
			
		||||
using System.Threading.Tasks;
 | 
			
		||||
using Kyoo.Controllers;
 | 
			
		||||
using Microsoft.AspNetCore.Authorization;
 | 
			
		||||
using Kyoo.Models.Permissions;
 | 
			
		||||
 | 
			
		||||
namespace Kyoo.Api
 | 
			
		||||
{
 | 
			
		||||
@ -23,8 +23,8 @@ namespace Kyoo.Api
 | 
			
		||||
		}
 | 
			
		||||
		
 | 
			
		||||
		
 | 
			
		||||
		[HttpGet("{slug}.{extension?}")]
 | 
			
		||||
		[Authorize(Policy="Play")]
 | 
			
		||||
		[HttpGet("{slug}.{extension}")]
 | 
			
		||||
		[Permission(nameof(SubtitleApi), Kind.Read)]
 | 
			
		||||
		public async Task<IActionResult> GetSubtitle(string slug, string extension)
 | 
			
		||||
		{
 | 
			
		||||
			Track subtitle;
 | 
			
		||||
 | 
			
		||||
@ -2,17 +2,14 @@
 | 
			
		||||
using System.Collections.Generic;
 | 
			
		||||
using Microsoft.AspNetCore.Mvc;
 | 
			
		||||
using Kyoo.Controllers;
 | 
			
		||||
using Kyoo.Models.Attributes;
 | 
			
		||||
using Kyoo.Models.Exceptions;
 | 
			
		||||
using Microsoft.AspNetCore.Authorization;
 | 
			
		||||
using static Kyoo.Models.Attributes.PermissionAttribute;
 | 
			
		||||
using Kyoo.Models.Permissions;
 | 
			
		||||
 | 
			
		||||
namespace Kyoo.Api
 | 
			
		||||
{
 | 
			
		||||
	[Route("api/task")]
 | 
			
		||||
	[Route("api/tasks")]
 | 
			
		||||
	[ApiController]
 | 
			
		||||
	// [Authorize(Policy="Admin")]
 | 
			
		||||
	public class TaskApi : ControllerBase
 | 
			
		||||
	{
 | 
			
		||||
		private readonly ITaskManager _taskManager;
 | 
			
		||||
@ -24,7 +21,7 @@ namespace Kyoo.Api
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
		[HttpGet]
 | 
			
		||||
		[Permission("task", Kind.Read)]
 | 
			
		||||
		[Permission(nameof(TaskApi), Kind.Read)]
 | 
			
		||||
		public ActionResult<ICollection<ITask>> GetTasks()
 | 
			
		||||
		{
 | 
			
		||||
			return Ok(_taskManager.GetAllTasks());
 | 
			
		||||
@ -32,6 +29,7 @@ namespace Kyoo.Api
 | 
			
		||||
		
 | 
			
		||||
		[HttpGet("{taskSlug}")]
 | 
			
		||||
		[HttpPut("{taskSlug}")]
 | 
			
		||||
		[Permission(nameof(TaskApi), Kind.Create)]
 | 
			
		||||
		public IActionResult RunTask(string taskSlug, [FromQuery] Dictionary<string, object> args)
 | 
			
		||||
		{
 | 
			
		||||
			try
 | 
			
		||||
 | 
			
		||||
@ -4,7 +4,7 @@ using Kyoo.CommonApi;
 | 
			
		||||
using Kyoo.Controllers;
 | 
			
		||||
using Kyoo.Models;
 | 
			
		||||
using Kyoo.Models.Exceptions;
 | 
			
		||||
using Microsoft.AspNetCore.Authorization;
 | 
			
		||||
using Kyoo.Models.Permissions;
 | 
			
		||||
using Microsoft.AspNetCore.Mvc;
 | 
			
		||||
using Microsoft.Extensions.Configuration;
 | 
			
		||||
 | 
			
		||||
@ -13,6 +13,7 @@ namespace Kyoo.Api
 | 
			
		||||
	[Route("api/track")]
 | 
			
		||||
	[Route("api/tracks")]
 | 
			
		||||
	[ApiController]
 | 
			
		||||
	[PartialPermission(nameof(Track))]
 | 
			
		||||
	public class TrackApi : CrudApi<Track>
 | 
			
		||||
	{
 | 
			
		||||
		private readonly ILibraryManager _libraryManager;
 | 
			
		||||
@ -24,7 +25,7 @@ namespace Kyoo.Api
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		[HttpGet("{id:int}/episode")]
 | 
			
		||||
		[Authorize(Policy = "Read")]
 | 
			
		||||
		[PartialPermission(Kind.Read)]
 | 
			
		||||
		public async Task<ActionResult<Episode>> GetEpisode(int id)
 | 
			
		||||
		{
 | 
			
		||||
			try
 | 
			
		||||
@ -38,7 +39,7 @@ namespace Kyoo.Api
 | 
			
		||||
		}
 | 
			
		||||
		
 | 
			
		||||
		[HttpGet("{slug}/episode")]
 | 
			
		||||
		[Authorize(Policy = "Read")]
 | 
			
		||||
		[PartialPermission(Kind.Read)]
 | 
			
		||||
		public async Task<ActionResult<Episode>> GetEpisode(string slug)
 | 
			
		||||
		{
 | 
			
		||||
			try
 | 
			
		||||
 | 
			
		||||
@ -5,7 +5,7 @@ using Microsoft.AspNetCore.Mvc;
 | 
			
		||||
using Microsoft.Extensions.Configuration;
 | 
			
		||||
using System.Threading.Tasks;
 | 
			
		||||
using Kyoo.Models.Exceptions;
 | 
			
		||||
using Microsoft.AspNetCore.Authorization;
 | 
			
		||||
using Kyoo.Models.Permissions;
 | 
			
		||||
using Microsoft.AspNetCore.Mvc.Filters;
 | 
			
		||||
 | 
			
		||||
namespace Kyoo.Api
 | 
			
		||||
@ -44,7 +44,7 @@ namespace Kyoo.Api
 | 
			
		||||
		
 | 
			
		||||
		[HttpGet("{slug}")]
 | 
			
		||||
		[HttpGet("direct/{slug}")]
 | 
			
		||||
		[Authorize(Policy="Play")]
 | 
			
		||||
		// TODO enable the following line, this is disabled since the web app can't use bearers. [Permission("video", Kind.Read)]
 | 
			
		||||
		public async Task<IActionResult> Direct(string slug)
 | 
			
		||||
		{
 | 
			
		||||
			try
 | 
			
		||||
@ -59,7 +59,7 @@ namespace Kyoo.Api
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		[HttpGet("transmux/{slug}/master.m3u8")]
 | 
			
		||||
		[Authorize(Policy="Play")]
 | 
			
		||||
		[Permission("video", Kind.Read)]
 | 
			
		||||
		public async Task<IActionResult> Transmux(string slug)
 | 
			
		||||
		{
 | 
			
		||||
			try
 | 
			
		||||
@ -78,7 +78,7 @@ namespace Kyoo.Api
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		[HttpGet("transcode/{slug}/master.m3u8")]
 | 
			
		||||
		[Authorize(Policy="Play")]
 | 
			
		||||
		[Permission("video", Kind.Read)]
 | 
			
		||||
		public async Task<IActionResult> Transcode(string slug)
 | 
			
		||||
		{
 | 
			
		||||
			try
 | 
			
		||||
@ -98,7 +98,7 @@ namespace Kyoo.Api
 | 
			
		||||
		
 | 
			
		||||
		
 | 
			
		||||
		[HttpGet("transmux/{episodeLink}/segments/{chunk}")]
 | 
			
		||||
		[Authorize(Policy="Play")]
 | 
			
		||||
		[Permission("video", Kind.Read)]
 | 
			
		||||
		public IActionResult GetTransmuxedChunk(string episodeLink, string chunk)
 | 
			
		||||
		{
 | 
			
		||||
			string path = Path.GetFullPath(Path.Combine(_transmuxPath, episodeLink));
 | 
			
		||||
@ -107,7 +107,7 @@ namespace Kyoo.Api
 | 
			
		||||
		}
 | 
			
		||||
		
 | 
			
		||||
		[HttpGet("transcode/{episodeLink}/segments/{chunk}")]
 | 
			
		||||
		[Authorize(Policy="Play")]
 | 
			
		||||
		[Permission("video", Kind.Read)]
 | 
			
		||||
		public IActionResult GetTranscodedChunk(string episodeLink, string chunk)
 | 
			
		||||
		{
 | 
			
		||||
			string path = Path.GetFullPath(Path.Combine(_transcodePath, episodeLink));
 | 
			
		||||
 | 
			
		||||
@ -2,7 +2,7 @@
 | 
			
		||||
using Kyoo.Controllers;
 | 
			
		||||
using Kyoo.Models;
 | 
			
		||||
using Kyoo.Models.Exceptions;
 | 
			
		||||
using Microsoft.AspNetCore.Authorization;
 | 
			
		||||
using Kyoo.Models.Permissions;
 | 
			
		||||
using Microsoft.AspNetCore.Mvc;
 | 
			
		||||
 | 
			
		||||
namespace Kyoo.Api
 | 
			
		||||
@ -19,7 +19,7 @@ namespace Kyoo.Api
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		[HttpGet("{slug}")]
 | 
			
		||||
		[Authorize(Policy="Read")]
 | 
			
		||||
		[Permission("video", Kind.Read)]
 | 
			
		||||
		public async Task<ActionResult<WatchItem>> GetWatchItem(string slug)
 | 
			
		||||
		{
 | 
			
		||||
			try
 | 
			
		||||
 | 
			
		||||
@ -32,8 +32,8 @@
 | 
			
		||||
      "password": "passphrase"
 | 
			
		||||
    },
 | 
			
		||||
    "permissions": {
 | 
			
		||||
      "default": [],
 | 
			
		||||
      "newUser": ["read", "play", "write", "admin"]
 | 
			
		||||
      "default": ["read", "play", "write", "admin"],
 | 
			
		||||
      "newUser": ["read", "play", "write", "admin", "task.read"]
 | 
			
		||||
    },
 | 
			
		||||
    "profilePicturePath": "users/",
 | 
			
		||||
    "clients": []
 | 
			
		||||
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user