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