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
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<bool> AuthorizeAsync(
Guid organizationId,
CurrentContextOrganization? organizationClaims,
IProviderOrganizationContext providerOrganizationContext);
Func<Task<bool>> isProviderUserForOrg);
}

View File

@ -8,7 +8,7 @@ using Bit.Core.Models.Data;
namespace Bit.Api.AdminConsole.Authorization;
public static class ClaimsExtensions
public static class OrganizationClaimsExtensions
{
/// <summary>
/// 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
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.
/// </summary>
public class OrganizationRequirementHandler(
IHttpContextAccessor httpContextAccessor)
IHttpContextAccessor httpContextAccessor,
IProviderUserRepository providerUserRepository,
IUserService userService)
: AuthorizationHandler<IOrganizationRequirement>
{
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<bool> IsProviderUserForOrg() => httpContextAccessor.HttpContext.IsProviderUserForOrgAsync(providerUserRepository, userId.Value, organizationId);
var authorized = await requirement.AuthorizeAsync(organizationClaims, IsProviderUserForOrg);
if (authorized)
{

View File

@ -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(

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
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<bool> AuthorizeAsync(
Guid organizationId,
CurrentContextOrganization? organizationClaims,
IProviderOrganizationContext providerOrganizationContext)
Func<Task<bool>> isProviderUserForOrg)
=> organizationClaims is
{ Type: OrganizationUserType.Owner or OrganizationUserType.Admin } or
{ Permissions.ManageUsers: true }
|| await providerOrganizationContext.ProviderUserForOrgAsync(organizationId);
|| await isProviderUserForOrg();
}

View File

@ -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<bool> AuthorizeAsync(
Guid organizationId,
CurrentContextOrganization? organizationClaims,
IProviderOrganizationContext providerOrganizationContext)
=> organizationClaims is not null || await providerOrganizationContext.ProviderUserForOrgAsync(organizationId);
Func<Task<bool>> isProviderUserForOrg)
=> organizationClaims is not null || await isProviderUserForOrg();
}

View File

@ -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<IOrganizationRequirement>();
testRequirement
.AuthorizeAsync(organizationId, null, Arg.Any<IProviderOrganizationContext>())
.AuthorizeAsync(organizationId, null, Arg.Any<Func<Task<bool>>>())
.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<IProviderOrganizationContext>());
await testRequirement.Received(1).AuthorizeAsync(organizationId, null, Arg.Any<Func<Task<bool>>>());
Assert.False(authContext.HasSucceeded);
}
@ -71,7 +70,7 @@ public class OrganizationRequirementHandlerTests
// Arrange requirement
var testRequirement = Substitute.For<IOrganizationRequirement>();
testRequirement
.AuthorizeAsync(organizationId, null, Arg.Any<IProviderOrganizationContext>())
.AuthorizeAsync(organizationId, null, Arg.Any<Func<Task<bool>>>())
.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<IProviderOrganizationContext>());
await testRequirement.Received(1).AuthorizeAsync(organizationId, null, Arg.Any<Func<Task<bool>>>());
Assert.True(authContext.HasSucceeded);
}