From faa2ff8b1d81320e11544529ac381d6c8bba5b84 Mon Sep 17 00:00:00 2001 From: Thomas Rittson Date: Tue, 1 Apr 2025 15:21:39 +1000 Subject: [PATCH] Use closures --- .../Authorization/ClaimsExtensions.cs | 110 +++++++++++------- .../AdminConsole/Models/Data/Permissions.cs | 24 ++-- src/Core/Identity/Claims.cs | 29 ++--- 3 files changed, 98 insertions(+), 65 deletions(-) diff --git a/src/Api/AdminConsole/Authorization/ClaimsExtensions.cs b/src/Api/AdminConsole/Authorization/ClaimsExtensions.cs index ef55a0ebbb..2921ab2d52 100644 --- a/src/Api/AdminConsole/Authorization/ClaimsExtensions.cs +++ b/src/Api/AdminConsole/Authorization/ClaimsExtensions.cs @@ -10,25 +10,46 @@ namespace Bit.Api.AdminConsole.Authorization; public static class ClaimsExtensions { - // Relevant claim types for organization roles, SM access, and custom permissions - private static readonly IEnumerable _relevantClaimTypes = new List{ + /// + /// A delegate that returns true if the user has the specified claim type for an organization, false otherwise. + /// + private delegate bool HasClaim(string claimType); + + // Relevant claim types required to build a CurrentContextOrganization object. + private static readonly IEnumerable _relevantClaimTypes = new HashSet{ Claims.OrganizationOwner, Claims.OrganizationAdmin, Claims.OrganizationCustom, Claims.OrganizationUser, Claims.SecretsManagerAccess, - }.Concat(new Permissions().ClaimsMap.Select(c => c.ClaimName)); + Claims.CustomPermissions.AccessEventLogs, + Claims.CustomPermissions.AccessImportExport, + Claims.CustomPermissions.AccessReports, + Claims.CustomPermissions.CreateNewCollections, + Claims.CustomPermissions.EditAnyCollection, + Claims.CustomPermissions.DeleteAnyCollection, + Claims.CustomPermissions.ManageGroups, + Claims.CustomPermissions.ManagePolicies, + Claims.CustomPermissions.ManageSso, + Claims.CustomPermissions.ManageUsers, + Claims.CustomPermissions.ManageResetPassword, + Claims.CustomPermissions.ManageScim, + }; + /// + /// Parses a user's claims and returns an object representing their claims for the specified organization. + /// + /// The user who has the claims. + /// The organizationId to look for in the claims. + /// + /// A representing the user's claims for that organization, or null + /// if the user does not have any claims for that organization. + /// public static CurrentContextOrganization? GetCurrentContextOrganization(this ClaimsPrincipal user, Guid organizationId) { - var claimsDict = user.Claims - .Where(c => _relevantClaimTypes.Contains(c.Type) && Guid.TryParse(c.Value, out _)) - .GroupBy(c => c.Type) - .ToDictionary( - c => c.Key, - c => c.Select(v => new Guid(v.Value))); + var hasClaim = GetClaimsParser(user, organizationId); - var role = claimsDict.GetRoleForOrganizationId(organizationId); + var role = GetRoleFromClaims(hasClaim); if (!role.HasValue) { // Not an organization member @@ -39,37 +60,48 @@ public static class ClaimsExtensions { Id = organizationId, Type = role.Value, - AccessSecretsManager = claimsDict.ContainsOrganizationId(Claims.SecretsManagerAccess, organizationId), + AccessSecretsManager = hasClaim(Claims.SecretsManagerAccess), Permissions = role == OrganizationUserType.Custom - ? claimsDict.GetPermissionsFromClaims(organizationId) + ? GetPermissionsFromClaims(hasClaim) : null }; } - private static bool ContainsOrganizationId(this Dictionary> claimsDict, string claimType, - Guid organizationId) - => claimsDict.TryGetValue(claimType, out var claimValue) && - claimValue.Any(guid => guid == organizationId); - - private static OrganizationUserType? GetRoleForOrganizationId(this Dictionary> claimsDict, - Guid organizationId) + /// + /// Creates a delegate specific to the user and organization. + /// + private static HasClaim GetClaimsParser(ClaimsPrincipal user, Guid organizationId) { - if (claimsDict.ContainsOrganizationId(Claims.OrganizationOwner, organizationId)) + var claimsDict = user.Claims + .Where(c => _relevantClaimTypes.Contains(c.Type) && Guid.TryParse(c.Value, out _)) + .GroupBy(c => c.Type) + .ToDictionary( + c => c.Key, + c => c.Select(v => new Guid(v.Value))); + + return claimType + => claimsDict.TryGetValue(claimType, out var claimValue) && + claimValue.Any(v => v == organizationId); + } + + private static OrganizationUserType? GetRoleFromClaims(HasClaim hasClaim) + { + if (hasClaim(Claims.OrganizationOwner)) { return OrganizationUserType.Owner; } - if (claimsDict.ContainsOrganizationId(Claims.OrganizationAdmin, organizationId)) + if (hasClaim(Claims.OrganizationAdmin)) { return OrganizationUserType.Admin; } - if (claimsDict.ContainsOrganizationId(Claims.OrganizationCustom, organizationId)) + if (hasClaim(Claims.OrganizationCustom)) { return OrganizationUserType.Custom; } - if (claimsDict.ContainsOrganizationId(Claims.OrganizationUser, organizationId)) + if (hasClaim(Claims.OrganizationUser)) { return OrganizationUserType.User; } @@ -77,22 +109,20 @@ public static class ClaimsExtensions return null; } - private static Permissions GetPermissionsFromClaims(this Dictionary> claimsDict, Guid organizationId) + private static Permissions GetPermissionsFromClaims(HasClaim hasClaim) + => new() { - return new Permissions - { - AccessEventLogs = claimsDict.ContainsOrganizationId(Claims.AccessEventLogs, organizationId), - AccessImportExport = claimsDict.ContainsOrganizationId(Claims.AccessImportExport, organizationId), - AccessReports = claimsDict.ContainsOrganizationId(Claims.AccessReports, organizationId), - CreateNewCollections = claimsDict.ContainsOrganizationId(Claims.CreateNewCollections, organizationId), - EditAnyCollection = claimsDict.ContainsOrganizationId(Claims.EditAnyCollection, organizationId), - DeleteAnyCollection = claimsDict.ContainsOrganizationId(Claims.DeleteAnyCollection, organizationId), - ManageGroups = claimsDict.ContainsOrganizationId(Claims.ManageGroups, organizationId), - ManagePolicies = claimsDict.ContainsOrganizationId(Claims.ManagePolicies, organizationId), - ManageSso = claimsDict.ContainsOrganizationId(Claims.ManageSso, organizationId), - ManageUsers = claimsDict.ContainsOrganizationId(Claims.ManageUsers, organizationId), - ManageResetPassword = claimsDict.ContainsOrganizationId(Claims.ManageResetPassword, organizationId), - ManageScim = claimsDict.ContainsOrganizationId(Claims.ManageScim, organizationId), - }; - } + AccessEventLogs = hasClaim(Claims.CustomPermissions.AccessEventLogs), + AccessImportExport = hasClaim(Claims.CustomPermissions.AccessImportExport), + AccessReports = hasClaim(Claims.CustomPermissions.AccessReports), + CreateNewCollections = hasClaim(Claims.CustomPermissions.CreateNewCollections), + EditAnyCollection = hasClaim(Claims.CustomPermissions.EditAnyCollection), + DeleteAnyCollection = hasClaim(Claims.CustomPermissions.DeleteAnyCollection), + ManageGroups = hasClaim(Claims.CustomPermissions.ManageGroups), + ManagePolicies = hasClaim(Claims.CustomPermissions.ManagePolicies), + ManageSso = hasClaim(Claims.CustomPermissions.ManageSso), + ManageUsers = hasClaim(Claims.CustomPermissions.ManageUsers), + ManageResetPassword = hasClaim(Claims.CustomPermissions.ManageResetPassword), + ManageScim = hasClaim(Claims.CustomPermissions.ManageScim), + }; } diff --git a/src/Core/AdminConsole/Models/Data/Permissions.cs b/src/Core/AdminConsole/Models/Data/Permissions.cs index a50f08e0f7..def468f18d 100644 --- a/src/Core/AdminConsole/Models/Data/Permissions.cs +++ b/src/Core/AdminConsole/Models/Data/Permissions.cs @@ -21,17 +21,17 @@ public class Permissions [JsonIgnore] public List<(bool Permission, string ClaimName)> ClaimsMap => new() { - (AccessEventLogs, Claims.AccessEventLogs), - (AccessImportExport, Claims.AccessImportExport), - (AccessReports, Claims.AccessReports), - (CreateNewCollections, Claims.CreateNewCollections), - (EditAnyCollection, Claims.EditAnyCollection), - (DeleteAnyCollection, Claims.DeleteAnyCollection), - (ManageGroups, Claims.ManageGroups), - (ManagePolicies, Claims.ManagePolicies), - (ManageSso, Claims.ManageSso), - (ManageUsers, Claims.ManageUsers), - (ManageResetPassword, Claims.ManageResetPassword), - (ManageScim, Claims.ManageScim), + (AccessEventLogs, Claims.CustomPermissions.AccessEventLogs), + (AccessImportExport, Claims.CustomPermissions.AccessImportExport), + (AccessReports, Claims.CustomPermissions.AccessReports), + (CreateNewCollections, Claims.CustomPermissions.CreateNewCollections), + (EditAnyCollection, Claims.CustomPermissions.EditAnyCollection), + (DeleteAnyCollection, Claims.CustomPermissions.DeleteAnyCollection), + (ManageGroups, Claims.CustomPermissions.ManageGroups), + (ManagePolicies, Claims.CustomPermissions.ManagePolicies), + (ManageSso, Claims.CustomPermissions.ManageSso), + (ManageUsers, Claims.CustomPermissions.ManageUsers), + (ManageResetPassword, Claims.CustomPermissions.ManageResetPassword), + (ManageScim, Claims.CustomPermissions.ManageScim), }; } diff --git a/src/Core/Identity/Claims.cs b/src/Core/Identity/Claims.cs index 9478ab3ca8..fad7b37b5f 100644 --- a/src/Core/Identity/Claims.cs +++ b/src/Core/Identity/Claims.cs @@ -23,17 +23,20 @@ public static class Claims // General public const string Type = "type"; - // Organization permissions - public const string AccessEventLogs = "accesseventlogs"; - public const string AccessImportExport = "accessimportexport"; - public const string AccessReports = "accessreports"; - public const string CreateNewCollections = "createnewcollections"; - public const string EditAnyCollection = "editanycollection"; - public const string DeleteAnyCollection = "deleteanycollection"; - public const string ManageGroups = "managegroups"; - public const string ManagePolicies = "managepolicies"; - public const string ManageSso = "managesso"; - public const string ManageUsers = "manageusers"; - public const string ManageResetPassword = "manageresetpassword"; - public const string ManageScim = "managescim"; + // Organization custom permissions + public static class CustomPermissions + { + public const string AccessEventLogs = "accesseventlogs"; + public const string AccessImportExport = "accessimportexport"; + public const string AccessReports = "accessreports"; + public const string CreateNewCollections = "createnewcollections"; + public const string EditAnyCollection = "editanycollection"; + public const string DeleteAnyCollection = "deleteanycollection"; + public const string ManageGroups = "managegroups"; + public const string ManagePolicies = "managepolicies"; + public const string ManageSso = "managesso"; + public const string ManageUsers = "manageusers"; + public const string ManageResetPassword = "manageresetpassword"; + public const string ManageScim = "managescim"; + } }