From 312010050fb5bbcd7627dfe95d42ec23916950b8 Mon Sep 17 00:00:00 2001 From: Francis Pion Date: Thu, 11 Jul 2024 12:51:23 -0400 Subject: [PATCH] Authorization. --- .../Authorization/UserAuthorizationHandler.cs | 33 +++++++++++ .../UserAuthorizationRequirement.cs | 5 ++ .../src/Logitar.Cms.Web/Constants/Policies.cs | 6 ++ .../Controllers/AccountController.cs | 8 ++- .../Controllers/UserProfile.cs | 55 +++++++++++++++++++ .../DependencyInjectionExtensions.cs | 8 ++- 6 files changed, 111 insertions(+), 4 deletions(-) create mode 100644 backend/src/Logitar.Cms.Web/Authorization/UserAuthorizationHandler.cs create mode 100644 backend/src/Logitar.Cms.Web/Authorization/UserAuthorizationRequirement.cs create mode 100644 backend/src/Logitar.Cms.Web/Constants/Policies.cs create mode 100644 backend/src/Logitar.Cms.Web/Controllers/UserProfile.cs diff --git a/backend/src/Logitar.Cms.Web/Authorization/UserAuthorizationHandler.cs b/backend/src/Logitar.Cms.Web/Authorization/UserAuthorizationHandler.cs new file mode 100644 index 0000000..bab41ec --- /dev/null +++ b/backend/src/Logitar.Cms.Web/Authorization/UserAuthorizationHandler.cs @@ -0,0 +1,33 @@ +using Logitar.Cms.Contracts.Users; +using Microsoft.AspNetCore.Authorization; + +namespace Logitar.Cms.Web.Authorization; + +public class UserAuthorizationHandler : AuthorizationHandler +{ + private readonly IHttpContextAccessor _httpContextAccessor; + + public UserAuthorizationHandler(IHttpContextAccessor httpContextAccessor) + { + _httpContextAccessor = httpContextAccessor; + } + + protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, UserAuthorizationRequirement requirement) + { + HttpContext? httpContext = _httpContextAccessor.HttpContext; + if (httpContext != null) + { + User? user = httpContext.GetUser(); + if (user == null) + { + context.Fail(new AuthorizationFailureReason(this, "The actor must be an authenticated user.")); + } + else + { + context.Succeed(requirement); + } + } + + return Task.CompletedTask; + } +} diff --git a/backend/src/Logitar.Cms.Web/Authorization/UserAuthorizationRequirement.cs b/backend/src/Logitar.Cms.Web/Authorization/UserAuthorizationRequirement.cs new file mode 100644 index 0000000..3eb13e9 --- /dev/null +++ b/backend/src/Logitar.Cms.Web/Authorization/UserAuthorizationRequirement.cs @@ -0,0 +1,5 @@ +using Microsoft.AspNetCore.Authorization; + +namespace Logitar.Cms.Web.Authorization; + +public class UserAuthorizationRequirement : IAuthorizationRequirement; diff --git a/backend/src/Logitar.Cms.Web/Constants/Policies.cs b/backend/src/Logitar.Cms.Web/Constants/Policies.cs new file mode 100644 index 0000000..9bb4c25 --- /dev/null +++ b/backend/src/Logitar.Cms.Web/Constants/Policies.cs @@ -0,0 +1,6 @@ +namespace Logitar.Cms.Web.Constants; + +public static class Policies +{ + public const string User = nameof(User); +} diff --git a/backend/src/Logitar.Cms.Web/Controllers/AccountController.cs b/backend/src/Logitar.Cms.Web/Controllers/AccountController.cs index 28ae22b..b752bad 100644 --- a/backend/src/Logitar.Cms.Web/Controllers/AccountController.cs +++ b/backend/src/Logitar.Cms.Web/Controllers/AccountController.cs @@ -4,6 +4,7 @@ using Logitar.Cms.Core; using Logitar.Cms.Core.Sessions.Commands; using Logitar.Cms.Web.Authentication; +using Logitar.Cms.Web.Constants; using Logitar.Cms.Web.Models.Account; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; @@ -23,12 +24,13 @@ public AccountController(IOpenAuthenticationService openAuthenticationService, I _pipeline = pipeline; } - [Authorize] // TODO(fpion): will fail when using API keys + [Authorize(Policy = Policies.User)] [HttpGet("profile")] - public ActionResult GetProfile() // TODO(fpion): other return type + public ActionResult GetProfile() { User user = HttpContext.GetUser() ?? throw new InvalidOperationException("An authenticated user is required."); - return Ok(user); + UserProfile profile = new(user); + return Ok(profile); } [HttpPost("sign/in")] diff --git a/backend/src/Logitar.Cms.Web/Controllers/UserProfile.cs b/backend/src/Logitar.Cms.Web/Controllers/UserProfile.cs new file mode 100644 index 0000000..bd3a5cf --- /dev/null +++ b/backend/src/Logitar.Cms.Web/Controllers/UserProfile.cs @@ -0,0 +1,55 @@ +using Logitar.Cms.Contracts; +using Logitar.Cms.Contracts.Users; + +namespace Logitar.Cms.Web.Controllers; + +public record UserProfile +{ + public string Username { get; set; } + + public DateTime? PasswordChangedOn { get; set; } + + public string? EmailAddress { get; set; } + + public string? FirstName { get; set; } + public string? MiddleName { get; set; } + public string? LastName { get; set; } + public string? FullName { get; set; } + + public Locale? Locale { get; set; } + + public string? Picture { get; set; } + + public DateTime CreatedOn { get; set; } + public DateTime UpdatedOn { get; set; } + public DateTime AuthenticatedOn { get; set; } + + public UserProfile() : this(string.Empty) + { + } + + public UserProfile(string username) + { + Username = username; + } + + public UserProfile(User user) : this(user.UniqueName) + { + PasswordChangedOn = user.PasswordChangedOn; + + EmailAddress = user.Email?.Address; + + FirstName = user.FirstName; + MiddleName = user.MiddleName; + LastName = user.LastName; + FullName = user.FullName; + + Locale = user.Locale; + + Picture = user.Picture; + + CreatedOn = user.CreatedOn; + UpdatedOn = user.UpdatedOn; + AuthenticatedOn = user.AuthenticatedOn ?? user.UpdatedOn; + } +} diff --git a/backend/src/Logitar.Cms.Web/DependencyInjectionExtensions.cs b/backend/src/Logitar.Cms.Web/DependencyInjectionExtensions.cs index f19dc80..85f6360 100644 --- a/backend/src/Logitar.Cms.Web/DependencyInjectionExtensions.cs +++ b/backend/src/Logitar.Cms.Web/DependencyInjectionExtensions.cs @@ -1,5 +1,6 @@ using Logitar.Cms.Core; using Logitar.Cms.Web.Authentication; +using Logitar.Cms.Web.Authorization; using Logitar.Cms.Web.Constants; using Logitar.Cms.Web.Filters; using Logitar.Cms.Web.Settings; @@ -40,7 +41,12 @@ public static IServiceCollection AddLogitarCmsWeb(this IServiceCollection servic services.AddTransient(); services.AddAuthorizationBuilder() - .SetDefaultPolicy(new AuthorizationPolicyBuilder(authenticationSchemes).RequireAuthenticatedUser().Build()); + .SetDefaultPolicy(new AuthorizationPolicyBuilder(authenticationSchemes).RequireAuthenticatedUser().Build()) + .AddPolicy(Policies.User, new AuthorizationPolicyBuilder(authenticationSchemes) + .RequireAuthenticatedUser() + .AddRequirements(new UserAuthorizationRequirement()) + .Build()); + services.AddSingleton(); CookiesSettings cookiesSettings = configuration.GetSection(CookiesSettings.SectionKey).Get() ?? new(); services.AddSingleton(cookiesSettings);