#region license // This file is part of Vocaluxe. // // Vocaluxe 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 // (at your option) any later version. // // Vocaluxe 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 Vocaluxe. If not, see . #endregion using System; using System.Linq; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.Filters; using Microsoft.Extensions.DependencyInjection; using VocaluxeWeb.Contracts; using VocaluxeWeb.Remote; using VocaluxeWeb.Services; namespace VocaluxeWeb.Api { [ApiController] [Produces("application/json")] public abstract class ApiControllerBase : ControllerBase { internal const string SessionItemKey = "vlx.session"; /// The logged in session; set for actions with . protected CSession CurrentSession => HttpContext.Items[SessionItemKey] as CSession; protected bool HasPermission(EPermission permission) { var session = CurrentSession; var settings = HttpContext.RequestServices.GetRequiredService().Current; return session != null && settings.GetPermissions(session.Role).Contains(permission); } protected ObjectResult Problem(int status, string title) { return new ObjectResult(new ProblemDetails { Status = status, Title = title }) { StatusCode = status, ContentTypes = { "application/problem+json" } }; } public static ProfileDto ToDto(RemoteProfile profile) { return new ProfileDto { Id = profile.Id, Name = profile.Name, Role = profile.Role, HasPin = profile.PinHash != null, HasAvatar = !string.IsNullOrEmpty(profile.AvatarPath), Difficulty = profile.Difficulty, IsActive = profile.IsActive }; } } /// The request needs a logged in profile and, optionally, a permission. Admins have all permissions. [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)] public sealed class RequireSessionAttribute : Attribute, IAuthorizationFilter { private readonly EPermission? _Permission; public RequireSessionAttribute() { } public RequireSessionAttribute(EPermission permission) { _Permission = permission; } public void OnAuthorization(AuthorizationFilterContext context) { var services = context.HttpContext.RequestServices; var session = services.GetRequiredService().Get(context.HttpContext); if (session == null) { context.Result = _Problem(StatusCodes.Status401Unauthorized, "Please log in."); return; } context.HttpContext.Items[ApiControllerBase.SessionItemKey] = session; if (_Permission.HasValue && !services.GetRequiredService().Current.GetPermissions(session.Role).Contains(_Permission.Value)) { context.Result = _Problem(StatusCodes.Status403Forbidden, "You are not allowed to do this."); } } private static ObjectResult _Problem(int status, string title) { return new ObjectResult(new ProblemDetails { Status = status, Title = title }) { StatusCode = status, ContentTypes = { "application/problem+json" } }; } } /// Only admins. [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)] public sealed class RequireAdminAttribute : Attribute, IAuthorizationFilter { public void OnAuthorization(AuthorizationFilterContext context) { var session = context.HttpContext.RequestServices.GetRequiredService().Get(context.HttpContext); if (session == null || session.Role != ERemoteRole.Admin) { var status = session == null ? StatusCodes.Status401Unauthorized : StatusCodes.Status403Forbidden; context.Result = new ObjectResult(new ProblemDetails { Status = status, Title = "Admins only." }) { StatusCode = status, ContentTypes = { "application/problem+json" } }; return; } context.HttpContext.Items[ApiControllerBase.SessionItemKey] = session; } } }