diff --git a/src/Api/AdminConsole/Authorization/IOrganizationRequirement.cs b/src/Api/AdminConsole/Authorization/IOrganizationRequirement.cs index 5c6a6a73b9..0031de660b 100644 --- a/src/Api/AdminConsole/Authorization/IOrganizationRequirement.cs +++ b/src/Api/AdminConsole/Authorization/IOrganizationRequirement.cs @@ -1,6 +1,5 @@ #nullable enable -using Bit.Api.AdminConsole.Context; using Bit.Core.Context; using Microsoft.AspNetCore.Authorization; @@ -15,7 +14,6 @@ namespace Bit.Api.AdminConsole.Authorization; public interface IOrganizationRequirement : IAuthorizationRequirement { public Task AuthorizeAsync( - Guid organizationId, CurrentContextOrganization? organizationClaims, - IProviderOrganizationContext providerOrganizationContext); + Func> isProviderUserForOrg); } diff --git a/src/Api/AdminConsole/Authorization/ClaimsExtensions.cs b/src/Api/AdminConsole/Authorization/OrganizationClaimsExtensions.cs similarity index 98% rename from src/Api/AdminConsole/Authorization/ClaimsExtensions.cs rename to src/Api/AdminConsole/Authorization/OrganizationClaimsExtensions.cs index 99040bc0c6..293de524e9 100644 --- a/src/Api/AdminConsole/Authorization/ClaimsExtensions.cs +++ b/src/Api/AdminConsole/Authorization/OrganizationClaimsExtensions.cs @@ -8,7 +8,7 @@ using Bit.Core.Models.Data; namespace Bit.Api.AdminConsole.Authorization; -public static class ClaimsExtensions +public static class OrganizationClaimsExtensions { /// /// A delegate that returns true if the user has the specified claim type for an organization, false otherwise. diff --git a/src/Api/AdminConsole/Authorization/OrganizationRequirementHandler.cs b/src/Api/AdminConsole/Authorization/OrganizationRequirementHandler.cs index 63c29f4073..01ecd6e4bd 100644 --- a/src/Api/AdminConsole/Authorization/OrganizationRequirementHandler.cs +++ b/src/Api/AdminConsole/Authorization/OrganizationRequirementHandler.cs @@ -1,6 +1,9 @@ #nullable enable +using Bit.Core.AdminConsole.Repositories; +using Bit.Core.Services; using Microsoft.AspNetCore.Authorization; +using BadRequestException = Bit.Core.Exceptions.BadRequestException; namespace Bit.Api.AdminConsole.Authorization; @@ -10,17 +13,31 @@ namespace Bit.Api.AdminConsole.Authorization; /// determine whether the action is authorized. /// public class OrganizationRequirementHandler( - IHttpContextAccessor httpContextAccessor) + IHttpContextAccessor httpContextAccessor, + IProviderUserRepository providerUserRepository, + IUserService userService) : AuthorizationHandler { protected override async Task HandleRequirementAsync(AuthorizationHandlerContext context, IOrganizationRequirement requirement) { - var organizationId = httpContextAccessor.GetOrganizationId(); + var httpContext = httpContextAccessor.HttpContext; + if (httpContext == null) + { + throw new InvalidOperationException("This method should only be called in the context of an HTTP Request."); + } - var organizationClaims = context.User.GetCurrentContextOrganization(organizationId); - var providerOrganizationContext = null; // TODO + var organizationId = httpContext.GetOrganizationId(); + var organizationClaims = httpContext.User.GetCurrentContextOrganization(organizationId); - var authorized = await requirement.AuthorizeAsync(organizationId, organizationClaims, providerOrganizationContext); + var userId = userService.GetProperUserId(httpContext.User); + if (userId == null) + { + throw new BadRequestException("This method should only be called on the private api with a logged in user."); + } + + Task IsProviderUserForOrg() => httpContextAccessor.HttpContext.IsProviderUserForOrgAsync(providerUserRepository, userId.Value, organizationId); + + var authorized = await requirement.AuthorizeAsync(organizationClaims, IsProviderUserForOrg); if (authorized) { diff --git a/src/Api/AdminConsole/Authorization/OrganizationRequirementHelpers.cs b/src/Api/AdminConsole/Authorization/OrganizationRequirementHelpers.cs index febf70f8b8..92271b1c05 100644 --- a/src/Api/AdminConsole/Authorization/OrganizationRequirementHelpers.cs +++ b/src/Api/AdminConsole/Authorization/OrganizationRequirementHelpers.cs @@ -4,14 +4,9 @@ namespace Bit.Api.AdminConsole.Authorization; public static class OrganizationRequirementHelpers { - public static Guid GetOrganizationId(this IHttpContextAccessor httpContextAccessor) + public static Guid GetOrganizationId(this HttpContext httpContext) { - if (httpContextAccessor.HttpContext is null) - { - throw new InvalidOperationException("This method should only be called in the context of an HTTP Request."); - } - - httpContextAccessor.HttpContext.GetRouteData().Values.TryGetValue("orgId", out var orgIdParam); + httpContext.GetRouteData().Values.TryGetValue("orgId", out var orgIdParam); if (orgIdParam == null || !Guid.TryParse(orgIdParam.ToString(), out var orgId)) { throw new InvalidOperationException( diff --git a/src/Api/AdminConsole/Authorization/ProviderOrganizationHttpContextFeature.cs b/src/Api/AdminConsole/Authorization/ProviderOrganizationHttpContextFeature.cs new file mode 100644 index 0000000000..efd1cb54c1 --- /dev/null +++ b/src/Api/AdminConsole/Authorization/ProviderOrganizationHttpContextFeature.cs @@ -0,0 +1,36 @@ +using Bit.Core.AdminConsole.Enums.Provider; +using Bit.Core.AdminConsole.Models.Data.Provider; +using Bit.Core.AdminConsole.Repositories; + +namespace Bit.Api.AdminConsole.Authorization; + +public static class ProviderOrganizationHttpContextFeature +{ + private static async Task> GetProviderUserOrganizationsAsync( + this HttpContext httpContext, + IProviderUserRepository providerUserRepository, + Guid userId) + { + var providerUserOrganizations = httpContext.Features.Get>(); + if (providerUserOrganizations != null) + { + return providerUserOrganizations; + } + + providerUserOrganizations = (await providerUserRepository.GetManyOrganizationDetailsByUserAsync( + userId, ProviderUserStatusType.Confirmed)).ToList(); + httpContext.Features.Set(providerUserOrganizations); + + return providerUserOrganizations; + } + + public static async Task IsProviderUserForOrgAsync( + this HttpContext httpContext, + IProviderUserRepository providerUserRepository, + Guid userId, + Guid organizationId) + { + var organizations = await httpContext.GetProviderUserOrganizationsAsync(providerUserRepository, userId); + return organizations.Any(o => o.OrganizationId == organizationId); + } +} diff --git a/src/Api/AdminConsole/Authorization/Requirements/ManageUsersRequirement.cs b/src/Api/AdminConsole/Authorization/Requirements/ManageUsersRequirement.cs index e107202f33..3bfff00c48 100644 --- a/src/Api/AdminConsole/Authorization/Requirements/ManageUsersRequirement.cs +++ b/src/Api/AdminConsole/Authorization/Requirements/ManageUsersRequirement.cs @@ -1,6 +1,5 @@ #nullable enable -using Bit.Api.AdminConsole.Context; using Bit.Core.Context; using Bit.Core.Enums; @@ -9,11 +8,10 @@ namespace Bit.Api.AdminConsole.Authorization.Requirements; public class ManageUsersRequirement : IOrganizationRequirement { public async Task AuthorizeAsync( - Guid organizationId, CurrentContextOrganization? organizationClaims, - IProviderOrganizationContext providerOrganizationContext) + Func> isProviderUserForOrg) => organizationClaims is { Type: OrganizationUserType.Owner or OrganizationUserType.Admin } or { Permissions.ManageUsers: true } - || await providerOrganizationContext.ProviderUserForOrgAsync(organizationId); + || await isProviderUserForOrg(); } diff --git a/src/Api/AdminConsole/Authorization/Requirements/MemberOrProviderRequirement.cs b/src/Api/AdminConsole/Authorization/Requirements/MemberOrProviderRequirement.cs index b18928dbb8..030509adef 100644 --- a/src/Api/AdminConsole/Authorization/Requirements/MemberOrProviderRequirement.cs +++ b/src/Api/AdminConsole/Authorization/Requirements/MemberOrProviderRequirement.cs @@ -1,6 +1,5 @@ #nullable enable -using Bit.Api.AdminConsole.Context; using Bit.Core.Context; namespace Bit.Api.AdminConsole.Authorization.Requirements; @@ -11,8 +10,7 @@ namespace Bit.Api.AdminConsole.Authorization.Requirements; public class MemberOrProviderRequirement : IOrganizationRequirement { public async Task AuthorizeAsync( - Guid organizationId, CurrentContextOrganization? organizationClaims, - IProviderOrganizationContext providerOrganizationContext) - => organizationClaims is not null || await providerOrganizationContext.ProviderUserForOrgAsync(organizationId); + Func> isProviderUserForOrg) + => organizationClaims is not null || await isProviderUserForOrg(); } diff --git a/test/Api.Test/AdminConsole/Authorization/OrganizationRequirementHandlerTests.cs b/test/Api.Test/AdminConsole/Authorization/OrganizationRequirementHandlerTests.cs index fc156c7eac..d4b7864412 100644 --- a/test/Api.Test/AdminConsole/Authorization/OrganizationRequirementHandlerTests.cs +++ b/test/Api.Test/AdminConsole/Authorization/OrganizationRequirementHandlerTests.cs @@ -1,6 +1,5 @@ using System.Security.Claims; using Bit.Api.AdminConsole.Authorization; -using Bit.Api.AdminConsole.Context; using Bit.Test.Common.AutoFixture; using Bit.Test.Common.AutoFixture.Attributes; using Microsoft.AspNetCore.Authorization; @@ -50,7 +49,7 @@ public class OrganizationRequirementHandlerTests // Arrange requirement var testRequirement = Substitute.For(); testRequirement - .AuthorizeAsync(organizationId, null, Arg.Any()) + .AuthorizeAsync(organizationId, null, Arg.Any>>()) .ReturnsForAnyArgs(false); var authContext = new AuthorizationHandlerContext([testRequirement], new ClaimsPrincipal(), null); @@ -58,7 +57,7 @@ public class OrganizationRequirementHandlerTests await sutProvider.Sut.HandleAsync(authContext); // Assert - await testRequirement.Received(1).AuthorizeAsync(organizationId, null, Arg.Any()); + await testRequirement.Received(1).AuthorizeAsync(organizationId, null, Arg.Any>>()); Assert.False(authContext.HasSucceeded); } @@ -71,7 +70,7 @@ public class OrganizationRequirementHandlerTests // Arrange requirement var testRequirement = Substitute.For(); testRequirement - .AuthorizeAsync(organizationId, null, Arg.Any()) + .AuthorizeAsync(organizationId, null, Arg.Any>>()) .ReturnsForAnyArgs(true); var authContext = new AuthorizationHandlerContext([testRequirement], new ClaimsPrincipal(), null); @@ -79,7 +78,7 @@ public class OrganizationRequirementHandlerTests await sutProvider.Sut.HandleAsync(authContext); // Assert - await testRequirement.Received(1).AuthorizeAsync(organizationId, null, Arg.Any()); + await testRequirement.Received(1).AuthorizeAsync(organizationId, null, Arg.Any>>()); Assert.True(authContext.HasSucceeded); }