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);
}
}
}
}
}