From ef30805d0e9979b9ed43d2ec172ba22f137ed502 Mon Sep 17 00:00:00 2001 From: Thomas Rittson Date: Tue, 1 Apr 2025 11:10:23 +1000 Subject: [PATCH] WIP: parsing claims from context --- .../Authorization/ClaimsExtensions.cs | 76 +++++++++++++++++++ .../OrganizationRequirementHandler.cs | 7 +- src/Api/Startup.cs | 5 +- src/Core/Context/CurrentContext.cs | 2 +- 4 files changed, 82 insertions(+), 8 deletions(-) create mode 100644 src/Api/AdminConsole/Authorization/ClaimsExtensions.cs diff --git a/src/Api/AdminConsole/Authorization/ClaimsExtensions.cs b/src/Api/AdminConsole/Authorization/ClaimsExtensions.cs new file mode 100644 index 0000000000..5aa6c1fd77 --- /dev/null +++ b/src/Api/AdminConsole/Authorization/ClaimsExtensions.cs @@ -0,0 +1,76 @@ +#nullable enable + +using System.Security.Claims; +using Bit.Core.Context; +using Bit.Core.Enums; +using Bit.Core.Identity; + +namespace Bit.Api.AdminConsole.Authorization; + +public static class ClaimsExtensions +{ + public static CurrentContextOrganization? GetCurrentContextOrganization(this ClaimsPrincipal user, Guid organizationId) + { + var claimsDict = user.Claims + .GroupBy(c => c.Type) + .ToDictionary(c => c.Key, c => c.Select(v => v)); + + var accessSecretsManager = claimsDict.TryGetValue(Claims.SecretsManagerAccess, out var value) + ? value + .Where(s => Guid.TryParse(s.Value, out _)) + .Select(s => new Guid(s.Value)) + .ToHashSet() + : []; + + var role = claimsDict.GetRoleForOrganizationId(organizationId); + if (!role.HasValue) + { + // Not an organization member + return null; + } + + return new CurrentContextOrganization + { + Id = organizationId, + Type = role.Value, + AccessSecretsManager = accessSecretsManager.Contains(organizationId), + Permissions = role == OrganizationUserType.Custom + ? CurrentContext.SetOrganizationPermissionsFromClaims(organizationId.ToString(), claimsDict) + : null + }; + } + + private static bool ContainsOrganizationId(this Dictionary> claimsDict, string claimType, + Guid organizationId) + => claimsDict.TryGetValue(claimType, out var claimValue) && + claimValue.Any(c => c.Value.EqualsGuid(organizationId)); + + private static OrganizationUserType? GetRoleForOrganizationId(this Dictionary> claimsDict, + Guid organizationId) + { + if (claimsDict.ContainsOrganizationId(Claims.OrganizationOwner, organizationId)) + { + return OrganizationUserType.Owner; + } + + if (claimsDict.ContainsOrganizationId(Claims.OrganizationAdmin, organizationId)) + { + return OrganizationUserType.Admin; + } + + if (claimsDict.ContainsOrganizationId(Claims.OrganizationCustom, organizationId)) + { + return OrganizationUserType.Custom; + } + + if (claimsDict.ContainsOrganizationId(Claims.OrganizationUser, organizationId)) + { + return OrganizationUserType.User; + } + + return null; + } + + private static bool EqualsGuid(this string value, Guid guid) + => Guid.TryParse(value, out var parsedValue) && parsedValue == guid; +} diff --git a/src/Api/AdminConsole/Authorization/OrganizationRequirementHandler.cs b/src/Api/AdminConsole/Authorization/OrganizationRequirementHandler.cs index 040cd63491..c90af3fe5d 100644 --- a/src/Api/AdminConsole/Authorization/OrganizationRequirementHandler.cs +++ b/src/Api/AdminConsole/Authorization/OrganizationRequirementHandler.cs @@ -1,7 +1,5 @@ #nullable enable -using Bit.Api.AdminConsole.Context; -using Bit.Core.Context; using Microsoft.AspNetCore.Authorization; namespace Bit.Api.AdminConsole.Authorization; @@ -12,8 +10,6 @@ namespace Bit.Api.AdminConsole.Authorization; /// determine whether the action is authorized. /// public class OrganizationRequirementHandler( - ICurrentContext currentContext, - IProviderOrganizationContext providerOrganizationContext, IHttpContextAccessor httpContextAccessor) : AuthorizationHandler { @@ -25,7 +21,8 @@ public class OrganizationRequirementHandler( throw new Exception("No organizationId found in route. IOrganizationRequirement cannot be used on this endpoint."); } - var organizationClaims = currentContext.GetOrganization(organizationId.Value); + var organizationClaims = context.User.GetCurrentContextOrganization(organizationId.Value); + var providerOrganizationContext = null; // TODO var authorized = await requirement.AuthorizeAsync(organizationId.Value, organizationClaims, providerOrganizationContext); diff --git a/src/Api/Startup.cs b/src/Api/Startup.cs index b59a01dfef..7e5fea7832 100644 --- a/src/Api/Startup.cs +++ b/src/Api/Startup.cs @@ -257,10 +257,11 @@ public class Startup // Add authentication and authorization to the request pipeline. app.UseAuthentication(); - // Note: ICurrentContext is used in authorization middleware so it must be registered first - app.UseMiddleware(); app.UseAuthorization(); + // Add current context + app.UseMiddleware(); + // Add endpoints to the request pipeline. app.UseEndpoints(endpoints => { diff --git a/src/Core/Context/CurrentContext.cs b/src/Core/Context/CurrentContext.cs index cbd90055b0..e12096a0ed 100644 --- a/src/Core/Context/CurrentContext.cs +++ b/src/Core/Context/CurrentContext.cs @@ -512,7 +512,7 @@ public class CurrentContext : ICurrentContext return claims[type].FirstOrDefault()?.Value; } - private Permissions SetOrganizationPermissionsFromClaims(string organizationId, Dictionary> claimsDict) + public static Permissions SetOrganizationPermissionsFromClaims(string organizationId, Dictionary> claimsDict) { bool hasClaim(string claimKey) {