// Kyoo - A portable and vast media library solution.
// Copyright (c) Kyoo.
//
// See AUTHORS.md and LICENSE file in the project root for full license information.
//
// Kyoo is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// any later version.
//
// Kyoo is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Kyoo. If not, see .
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Kyoo.Abstractions.Controllers;
using Kyoo.Abstractions.Models.Permissions;
using Kyoo.Authentication.Models;
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 PermissionValidator : 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 PermissionValidator(IOptionsMonitor options)
{
_options = options;
}
///
public IFilterMetadata Create(PermissionAttribute attribute)
{
return new PermissionValidatorFilter(attribute.Type, attribute.Kind, attribute.Group, _options);
}
///
public IFilterMetadata Create(PartialPermissionAttribute attribute)
{
return new PermissionValidatorFilter((object)attribute.Type ?? attribute.Kind, _options);
}
///
/// The authorization filter used by .
///
private class PermissionValidatorFilter : IAsyncAuthorizationFilter
{
///
/// The permission to validate.
///
private readonly string _permission;
///
/// The kind of permission needed.
///
private readonly Kind? _kind;
///
/// The group of he permission.
///
private readonly Group _group = Group.Overall;
///
/// 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 kind of permission needed.
/// The group of the permission.
/// The option containing default values.
public PermissionValidatorFilter(string permission, Kind kind, Group group,
IOptionsMonitor options)
{
_permission = permission;
_kind = kind;
_group = group;
_options = options;
}
///
/// Create a new permission validator with the given options.
///
/// The partial permission to validate.
/// The option containing default values.
public PermissionValidatorFilter(object partialInfo, IOptionsMonitor options)
{
if (partialInfo is Kind kind)
_kind = kind;
else if (partialInfo is string perm)
_permission = perm;
else
throw new ArgumentException($"{nameof(partialInfo)} can only be a permission string or a kind.");
_options = options;
}
///
public async Task OnAuthorizationAsync(AuthorizationFilterContext context)
{
string permission = _permission;
Kind? kind = _kind;
if (permission == null || kind == null)
{
switch (context.HttpContext.Items["PermissionType"])
{
case string perm:
permission = perm;
break;
case Kind kin:
kind = kin;
break;
case null when kind != null:
context.HttpContext.Items["PermissionType"] = kind;
return;
case null when permission != null:
context.HttpContext.Items["PermissionType"] = permission;
return;
default:
throw new ArgumentException("Multiple non-matching partial permission attribute " +
"are not supported.");
}
if (permission == null || kind == null)
{
throw new ArgumentException("The permission type or kind is still missing after two partial " +
"permission attributes, this is unsupported.");
}
}
string permStr = $"{permission.ToLower()}.{kind.ToString()!.ToLower()}";
string overallStr = $"{_group.ToString().ToLower()}.{kind.ToString()!.ToLower()}";
AuthenticateResult res = await context.HttpContext.AuthenticateAsync(JwtBearerDefaults.AuthenticationScheme);
if (res.Succeeded)
{
ICollection permissions = res.Principal.GetPermissions();
if (permissions.All(x => x != permStr && x != overallStr))
context.Result = new StatusCodeResult(StatusCodes.Status403Forbidden);
}
else
{
ICollection permissions = _options.CurrentValue.Default ?? Array.Empty();
if (res.Failure != null || permissions.All(x => x != permStr && x != overallStr))
context.Result = new StatusCodeResult(StatusCodes.Status401Unauthorized);
}
}
}
}
}