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

Move to api project, create new context class

This commit is contained in:
Thomas Rittson 2025-03-27 10:20:19 +10:00
parent b840e2e318
commit 3d83e4b5a7
No known key found for this signature in database
GPG Key ID: CDDDA03861C35E27
14 changed files with 93 additions and 49 deletions

View File

@ -2,7 +2,7 @@
using Microsoft.AspNetCore.Authorization;
namespace Bit.Core.AdminConsole.OrganizationFeatures.Shared.Authorization;
namespace Bit.Api.AdminConsole.Authorization;
/// <summary>
/// An attribute which requires authorization using the specified requirement.

View File

@ -1,9 +1,10 @@
#nullable enable
using Bit.Api.AdminConsole.Context;
using Bit.Core.Context;
using Microsoft.AspNetCore.Authorization;
namespace Bit.Core.AdminConsole.OrganizationFeatures.Shared.Authorization;
namespace Bit.Api.AdminConsole.Authorization;
/// <summary>
/// A requirement that implements this interface will be handled by <see cref="OrganizationRequirementHandler"/>,
@ -13,6 +14,8 @@ namespace Bit.Core.AdminConsole.OrganizationFeatures.Shared.Authorization;
/// </summary>
public interface IOrganizationRequirement : IAuthorizationRequirement
{
// TODO: avoid injecting all of ICurrentContext?
public Task<bool> AuthorizeAsync(Guid organizationId, CurrentContextOrganization? organizationClaims, ICurrentContext currentContext);
public Task<bool> AuthorizeAsync(
Guid organizationId,
CurrentContextOrganization? organizationClaims,
IProviderOrganizationContext providerOrganizationContext);
}

View File

@ -1,10 +1,10 @@
#nullable enable
using Bit.Api.AdminConsole.Context;
using Bit.Core.Context;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
namespace Bit.Core.AdminConsole.OrganizationFeatures.Shared.Authorization;
namespace Bit.Api.AdminConsole.Authorization;
/// <summary>
/// Handles any requirement that implements <see cref="IOrganizationRequirement"/>.
@ -13,7 +13,10 @@ namespace Bit.Core.AdminConsole.OrganizationFeatures.Shared.Authorization;
/// </summary>
/// <param name="currentContext"></param>
/// <param name="httpContextAccessor"></param>
public class OrganizationRequirementHandler(ICurrentContext currentContext, IHttpContextAccessor httpContextAccessor)
public class OrganizationRequirementHandler(
ICurrentContext currentContext,
IProviderOrganizationContext providerOrganizationContext,
IHttpContextAccessor httpContextAccessor)
: AuthorizationHandler<IOrganizationRequirement>
{
protected override async Task HandleRequirementAsync(AuthorizationHandlerContext context, IOrganizationRequirement requirement)
@ -24,9 +27,9 @@ public class OrganizationRequirementHandler(ICurrentContext currentContext, IHtt
throw new Exception("No organizationId found in route. IOrganizationRequirement cannot be used on this endpoint.");
}
var organization = currentContext.GetOrganization(organizationId.Value);
var organizationClaims = currentContext.GetOrganization(organizationId.Value);
var authorized = await requirement.AuthorizeAsync(organizationId.Value, organization, currentContext);
var authorized = await requirement.AuthorizeAsync(organizationId.Value, organizationClaims, providerOrganizationContext);
if (authorized)
{

View File

@ -1,9 +1,6 @@
#nullable enable
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Routing;
namespace Bit.Core.AdminConsole.OrganizationFeatures.Shared.Authorization;
namespace Bit.Api.AdminConsole.Authorization;
public static class OrganizationRequirementHelpers
{

View File

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

View File

@ -0,0 +1,18 @@
#nullable enable
using Bit.Api.AdminConsole.Context;
using Bit.Core.Context;
namespace Bit.Api.AdminConsole.Authorization.Requirements;
/// <summary>
/// Requires that the user is a member of the organization or a provider for the organization.
/// </summary>
public class MemberOrProviderRequirement : IOrganizationRequirement
{
public async Task<bool> AuthorizeAsync(
Guid organizationId,
CurrentContextOrganization? organizationClaims,
IProviderOrganizationContext providerOrganizationContext)
=> organizationClaims is not null || await providerOrganizationContext.ProviderUserForOrgAsync(organizationId);
}

View File

@ -0,0 +1,12 @@
namespace Bit.Api.AdminConsole.Context;
/// <summary>
/// Current context for the relationship between ProviderUsers and Organizations that they manage.
/// </summary>
public interface IProviderOrganizationContext
{
/// <summary>
/// Returns true if the current user is a ProviderUser for the specified organization, false otherwise.
/// </summary>
Task<bool> ProviderUserForOrgAsync(Guid orgId);
}

View File

@ -0,0 +1,19 @@
using Bit.Core.Context;
namespace Bit.Api.AdminConsole.Context;
public class ProviderOrganizationContext(ICurrentContext currentContext) : IProviderOrganizationContext
{
/// <inheritdoc/>
public async Task<bool> ProviderUserForOrgAsync(Guid orgId)
{
// If the user doesn't have any ProviderUser claims (in relation to the provider), they can't have a provider
// relationship to any organization.
if (currentContext.Providers.Count == 0)
{
return false;
}
return await currentContext.ProviderUserForOrgAsync(orgId);
}
}

View File

@ -1,4 +1,6 @@
using Bit.Api.AdminConsole.Models.Request.Organizations;
using Bit.Api.AdminConsole.Authorization;
using Bit.Api.AdminConsole.Authorization.Requirements;
using Bit.Api.AdminConsole.Models.Request.Organizations;
using Bit.Api.AdminConsole.Models.Response.Organizations;
using Bit.Api.Models.Request.Organizations;
using Bit.Api.Models.Response;
@ -6,11 +8,9 @@ using Bit.Api.Vault.AuthorizationHandlers.Collections;
using Bit.Core;
using Bit.Core.AdminConsole.Enums;
using Bit.Core.AdminConsole.Models.Data.Organizations.Policies;
using Bit.Core.AdminConsole.OrganizationFeatures;
using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.Interfaces;
using Bit.Core.AdminConsole.OrganizationFeatures.Policies;
using Bit.Core.AdminConsole.OrganizationFeatures.Policies.PolicyRequirements;
using Bit.Core.AdminConsole.OrganizationFeatures.Shared.Authorization;
using Bit.Core.AdminConsole.Repositories;
using Bit.Core.Auth.Enums;
using Bit.Core.Auth.Repositories;

View File

@ -7,6 +7,7 @@ using Stripe;
using Bit.Core.Utilities;
using Duende.IdentityModel;
using System.Globalization;
using Bit.Api.AdminConsole.Context;
using Bit.Api.AdminConsole.Models.Request.Organizations;
using Bit.Api.Auth.Models.Request;
using Bit.Api.KeyManagement.Validators;
@ -84,6 +85,7 @@ public class Startup
// Context
services.AddScoped<ICurrentContext, CurrentContext>();
services.AddScoped<IProviderOrganizationContext, ProviderOrganizationContext>();
services.TryAddSingleton<IHttpContextAccessor, HttpContextAccessor>();
// Caching

View File

@ -1,7 +1,7 @@
using Bit.Api.Tools.Authorization;
using Bit.Api.AdminConsole.Authorization;
using Bit.Api.Tools.Authorization;
using Bit.Api.Vault.AuthorizationHandlers.Collections;
using Bit.Core.AdminConsole.OrganizationFeatures.Groups.Authorization;
using Bit.Core.AdminConsole.OrganizationFeatures.Shared.Authorization;
using Bit.Core.IdentityServer;
using Bit.Core.Settings;
using Bit.Core.Utilities;

View File

@ -1,16 +0,0 @@
#nullable enable
using Bit.Core.AdminConsole.OrganizationFeatures.Shared.Authorization;
using Bit.Core.Context;
using Bit.Core.Enums;
namespace Bit.Core.AdminConsole.OrganizationFeatures;
public class ManageUsersRequirement : IOrganizationRequirement
{
public async Task<bool> AuthorizeAsync(Guid organizationId, CurrentContextOrganization? organizationClaims, ICurrentContext currentContext)
=> organizationClaims is
{ Type: OrganizationUserType.Owner or OrganizationUserType.Admin } or
{ Permissions.ManageUsers: true }
|| await currentContext.ProviderUserForOrgAsync(organizationId);
}

View File

@ -1,15 +0,0 @@
#nullable enable
using Bit.Core.AdminConsole.OrganizationFeatures.Shared.Authorization;
using Bit.Core.Context;
namespace Bit.Core.AdminConsole.OrganizationFeatures;
/// <summary>
/// Requires that the user is a member of the organization or a provider for the organization.
/// </summary>
public class MemberOrProviderRequirement : IOrganizationRequirement
{
public async Task<bool> AuthorizeAsync(Guid organizationId, CurrentContextOrganization? organizationClaims, ICurrentContext currentContext)
=> organizationClaims is not null || await currentContext.ProviderUserForOrgAsync(organizationId);
}

View File

@ -22,6 +22,7 @@ public interface ICurrentContext
string IpAddress { get; set; }
string CountryName { get; set; }
List<CurrentContextOrganization> Organizations { get; set; }
List<CurrentContextProvider> Providers { get; set; }
Guid? InstallationId { get; set; }
Guid? OrganizationId { get; set; }
IdentityClientType IdentityClientType { get; set; }
@ -59,6 +60,7 @@ public interface ICurrentContext
Task<bool> EditSubscription(Guid orgId);
Task<bool> EditPaymentMethods(Guid orgId);
Task<bool> ViewBillingHistory(Guid orgId);
[Obsolete("Use IProviderOrganizationContext.ProviderUserForOrgAsync instead.")]
Task<bool> ProviderUserForOrgAsync(Guid orgId);
bool ProviderProviderAdmin(Guid providerId);
bool ProviderUser(Guid providerId);