1
0
mirror of https://github.com/bitwarden/server.git synced 2025-04-05 05:00:19 -05:00

Use httpContext features for providers

This commit is contained in:
Thomas Rittson 2025-04-02 12:15:19 +10:00
parent 48697c4900
commit 038e6e63b6
No known key found for this signature in database
GPG Key ID: CDDDA03861C35E27
8 changed files with 70 additions and 29 deletions

View File

@ -1,6 +1,5 @@
#nullable enable #nullable enable
using Bit.Api.AdminConsole.Context;
using Bit.Core.Context; using Bit.Core.Context;
using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Authorization;
@ -15,7 +14,6 @@ namespace Bit.Api.AdminConsole.Authorization;
public interface IOrganizationRequirement : IAuthorizationRequirement public interface IOrganizationRequirement : IAuthorizationRequirement
{ {
public Task<bool> AuthorizeAsync( public Task<bool> AuthorizeAsync(
Guid organizationId,
CurrentContextOrganization? organizationClaims, CurrentContextOrganization? organizationClaims,
IProviderOrganizationContext providerOrganizationContext); Func<Task<bool>> isProviderUserForOrg);
} }

View File

@ -8,7 +8,7 @@ using Bit.Core.Models.Data;
namespace Bit.Api.AdminConsole.Authorization; namespace Bit.Api.AdminConsole.Authorization;
public static class ClaimsExtensions public static class OrganizationClaimsExtensions
{ {
/// <summary> /// <summary>
/// A delegate that returns true if the user has the specified claim type for an organization, false otherwise. /// A delegate that returns true if the user has the specified claim type for an organization, false otherwise.

View File

@ -1,6 +1,9 @@
#nullable enable #nullable enable
using Bit.Core.AdminConsole.Repositories;
using Bit.Core.Services;
using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Authorization;
using BadRequestException = Bit.Core.Exceptions.BadRequestException;
namespace Bit.Api.AdminConsole.Authorization; namespace Bit.Api.AdminConsole.Authorization;
@ -10,17 +13,31 @@ namespace Bit.Api.AdminConsole.Authorization;
/// determine whether the action is authorized. /// determine whether the action is authorized.
/// </summary> /// </summary>
public class OrganizationRequirementHandler( public class OrganizationRequirementHandler(
IHttpContextAccessor httpContextAccessor) IHttpContextAccessor httpContextAccessor,
IProviderUserRepository providerUserRepository,
IUserService userService)
: AuthorizationHandler<IOrganizationRequirement> : AuthorizationHandler<IOrganizationRequirement>
{ {
protected override async Task HandleRequirementAsync(AuthorizationHandlerContext context, IOrganizationRequirement requirement) 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 organizationId = httpContext.GetOrganizationId();
var providerOrganizationContext = null; // TODO 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<bool> IsProviderUserForOrg() => httpContextAccessor.HttpContext.IsProviderUserForOrgAsync(providerUserRepository, userId.Value, organizationId);
var authorized = await requirement.AuthorizeAsync(organizationClaims, IsProviderUserForOrg);
if (authorized) if (authorized)
{ {

View File

@ -4,14 +4,9 @@ namespace Bit.Api.AdminConsole.Authorization;
public static class OrganizationRequirementHelpers public static class OrganizationRequirementHelpers
{ {
public static Guid GetOrganizationId(this IHttpContextAccessor httpContextAccessor) public static Guid GetOrganizationId(this HttpContext httpContext)
{ {
if (httpContextAccessor.HttpContext is null) httpContext.GetRouteData().Values.TryGetValue("orgId", out var orgIdParam);
{
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);
if (orgIdParam == null || !Guid.TryParse(orgIdParam.ToString(), out var orgId)) if (orgIdParam == null || !Guid.TryParse(orgIdParam.ToString(), out var orgId))
{ {
throw new InvalidOperationException( throw new InvalidOperationException(

View File

@ -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<IEnumerable<ProviderUserOrganizationDetails>> GetProviderUserOrganizationsAsync(
this HttpContext httpContext,
IProviderUserRepository providerUserRepository,
Guid userId)
{
var providerUserOrganizations = httpContext.Features.Get<IEnumerable<ProviderUserOrganizationDetails>>();
if (providerUserOrganizations != null)
{
return providerUserOrganizations;
}
providerUserOrganizations = (await providerUserRepository.GetManyOrganizationDetailsByUserAsync(
userId, ProviderUserStatusType.Confirmed)).ToList();
httpContext.Features.Set(providerUserOrganizations);
return providerUserOrganizations;
}
public static async Task<bool> IsProviderUserForOrgAsync(
this HttpContext httpContext,
IProviderUserRepository providerUserRepository,
Guid userId,
Guid organizationId)
{
var organizations = await httpContext.GetProviderUserOrganizationsAsync(providerUserRepository, userId);
return organizations.Any(o => o.OrganizationId == organizationId);
}
}

View File

@ -1,6 +1,5 @@
#nullable enable #nullable enable
using Bit.Api.AdminConsole.Context;
using Bit.Core.Context; using Bit.Core.Context;
using Bit.Core.Enums; using Bit.Core.Enums;
@ -9,11 +8,10 @@ namespace Bit.Api.AdminConsole.Authorization.Requirements;
public class ManageUsersRequirement : IOrganizationRequirement public class ManageUsersRequirement : IOrganizationRequirement
{ {
public async Task<bool> AuthorizeAsync( public async Task<bool> AuthorizeAsync(
Guid organizationId,
CurrentContextOrganization? organizationClaims, CurrentContextOrganization? organizationClaims,
IProviderOrganizationContext providerOrganizationContext) Func<Task<bool>> isProviderUserForOrg)
=> organizationClaims is => organizationClaims is
{ Type: OrganizationUserType.Owner or OrganizationUserType.Admin } or { Type: OrganizationUserType.Owner or OrganizationUserType.Admin } or
{ Permissions.ManageUsers: true } { Permissions.ManageUsers: true }
|| await providerOrganizationContext.ProviderUserForOrgAsync(organizationId); || await isProviderUserForOrg();
} }

View File

@ -1,6 +1,5 @@
#nullable enable #nullable enable
using Bit.Api.AdminConsole.Context;
using Bit.Core.Context; using Bit.Core.Context;
namespace Bit.Api.AdminConsole.Authorization.Requirements; namespace Bit.Api.AdminConsole.Authorization.Requirements;
@ -11,8 +10,7 @@ namespace Bit.Api.AdminConsole.Authorization.Requirements;
public class MemberOrProviderRequirement : IOrganizationRequirement public class MemberOrProviderRequirement : IOrganizationRequirement
{ {
public async Task<bool> AuthorizeAsync( public async Task<bool> AuthorizeAsync(
Guid organizationId,
CurrentContextOrganization? organizationClaims, CurrentContextOrganization? organizationClaims,
IProviderOrganizationContext providerOrganizationContext) Func<Task<bool>> isProviderUserForOrg)
=> organizationClaims is not null || await providerOrganizationContext.ProviderUserForOrgAsync(organizationId); => organizationClaims is not null || await isProviderUserForOrg();
} }

View File

@ -1,6 +1,5 @@
using System.Security.Claims; using System.Security.Claims;
using Bit.Api.AdminConsole.Authorization; using Bit.Api.AdminConsole.Authorization;
using Bit.Api.AdminConsole.Context;
using Bit.Test.Common.AutoFixture; using Bit.Test.Common.AutoFixture;
using Bit.Test.Common.AutoFixture.Attributes; using Bit.Test.Common.AutoFixture.Attributes;
using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Authorization;
@ -50,7 +49,7 @@ public class OrganizationRequirementHandlerTests
// Arrange requirement // Arrange requirement
var testRequirement = Substitute.For<IOrganizationRequirement>(); var testRequirement = Substitute.For<IOrganizationRequirement>();
testRequirement testRequirement
.AuthorizeAsync(organizationId, null, Arg.Any<IProviderOrganizationContext>()) .AuthorizeAsync(organizationId, null, Arg.Any<Func<Task<bool>>>())
.ReturnsForAnyArgs(false); .ReturnsForAnyArgs(false);
var authContext = new AuthorizationHandlerContext([testRequirement], new ClaimsPrincipal(), null); var authContext = new AuthorizationHandlerContext([testRequirement], new ClaimsPrincipal(), null);
@ -58,7 +57,7 @@ public class OrganizationRequirementHandlerTests
await sutProvider.Sut.HandleAsync(authContext); await sutProvider.Sut.HandleAsync(authContext);
// Assert // Assert
await testRequirement.Received(1).AuthorizeAsync(organizationId, null, Arg.Any<IProviderOrganizationContext>()); await testRequirement.Received(1).AuthorizeAsync(organizationId, null, Arg.Any<Func<Task<bool>>>());
Assert.False(authContext.HasSucceeded); Assert.False(authContext.HasSucceeded);
} }
@ -71,7 +70,7 @@ public class OrganizationRequirementHandlerTests
// Arrange requirement // Arrange requirement
var testRequirement = Substitute.For<IOrganizationRequirement>(); var testRequirement = Substitute.For<IOrganizationRequirement>();
testRequirement testRequirement
.AuthorizeAsync(organizationId, null, Arg.Any<IProviderOrganizationContext>()) .AuthorizeAsync(organizationId, null, Arg.Any<Func<Task<bool>>>())
.ReturnsForAnyArgs(true); .ReturnsForAnyArgs(true);
var authContext = new AuthorizationHandlerContext([testRequirement], new ClaimsPrincipal(), null); var authContext = new AuthorizationHandlerContext([testRequirement], new ClaimsPrincipal(), null);
@ -79,7 +78,7 @@ public class OrganizationRequirementHandlerTests
await sutProvider.Sut.HandleAsync(authContext); await sutProvider.Sut.HandleAsync(authContext);
// Assert // Assert
await testRequirement.Received(1).AuthorizeAsync(organizationId, null, Arg.Any<IProviderOrganizationContext>()); await testRequirement.Received(1).AuthorizeAsync(organizationId, null, Arg.Any<Func<Task<bool>>>());
Assert.True(authContext.HasSucceeded); Assert.True(authContext.HasSucceeded);
} }