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 { /// /// A permission validator to validate permission with user Permission array /// or the default array from the configurations if the user is not logged. /// public class PermissionValidatorFactory : IPermissionValidator { /// /// The permissions options to retrieve default permissions. /// private readonly IOptionsMonitor _options; /// /// Create a new factory with the given options /// /// The option containing default values. public PermissionValidatorFactory(IOptionsMonitor options) { _options = options; } /// public IFilterMetadata Create(PermissionAttribute attribute) { return new PermissionValidator(attribute.AsPermissionString(), _options); } /// public IFilterMetadata Create(PartialPermissionAttribute attribute) { return new PermissionValidator((object)attribute.Type ?? attribute.Kind, _options); } /// /// The authorization filter used by /// private class PermissionValidator : IAsyncAuthorizationFilter { /// /// The permission to validate /// private readonly string _permission; /// /// Information about partial items. /// private readonly object _partialInfo; /// /// The permissions options to retrieve default permissions. /// private readonly IOptionsMonitor _options; /// /// Create a new permission validator with the given options /// /// The permission to validate /// The option containing default values. public PermissionValidator(string permission, IOptionsMonitor options) { _permission = permission; _options = options; } /// /// Create a new permission validator with the given options /// /// The partial permission to validate /// The option containing default values. public PermissionValidator(object partialInfo, IOptionsMonitor options) { _partialInfo = partialInfo; _options = options; } /// 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); } } } } }