2025-09-11 07:37:45 +10:00
using Bit.Core.AdminConsole.Enums.Provider ;
2025-04-15 14:36:00 +10:00
using Bit.Core.AdminConsole.Models.Data.Provider ;
using Bit.Core.AdminConsole.Repositories ;
namespace Bit.Api.AdminConsole.Authorization ;
public static class HttpContextExtensions
{
public const string NoOrgIdError =
2025-05-02 07:00:48 +10:00
"A route decorated with with '[Authorize<Requirement>]' must include a route value named 'orgId' or 'organizationId' either through the [Controller] attribute or through a '[Http*]' attribute." ;
2025-04-15 14:36:00 +10:00
/// <summary>
/// Returns the result of the callback, caching it in HttpContext.Features for the lifetime of the request.
/// Subsequent calls will retrieve the cached value.
/// Results are stored by type and therefore must be of a unique type.
/// </summary>
public static async Task < T > WithFeaturesCacheAsync < T > ( this HttpContext httpContext , Func < Task < T > > callback )
{
var cachedResult = httpContext . Features . Get < T > ( ) ;
if ( cachedResult ! = null )
{
return cachedResult ;
}
var result = await callback ( ) ;
httpContext . Features . Set ( result ) ;
return result ;
}
/// <summary>
/// Returns true if the user is a ProviderUser for a Provider which manages the specified organization, otherwise false.
/// </summary>
/// <remarks>
/// This data is fetched from the database and cached as a HttpContext Feature for the lifetime of the request.
/// </remarks>
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 ) ;
}
/// <summary>
/// Returns the ProviderUserOrganizations for a user. These are the organizations the ProviderUser manages via their Provider, if any.
/// </summary>
/// <remarks>
/// This data is fetched from the database and cached as a HttpContext Feature for the lifetime of the request.
/// </remarks>
private static async Task < IEnumerable < ProviderUserOrganizationDetails > > GetProviderUserOrganizationsAsync (
this HttpContext httpContext ,
IProviderUserRepository providerUserRepository ,
Guid userId )
= > await httpContext . WithFeaturesCacheAsync ( ( ) = >
providerUserRepository . GetManyOrganizationDetailsByUserAsync ( userId , ProviderUserStatusType . Confirmed ) ) ;
/// <summary>
2025-05-02 07:00:48 +10:00
/// Parses the {orgId} or {organizationId} route parameter into a Guid, or throws if neither are present or are not valid guids.
2025-04-15 14:36:00 +10:00
/// </summary>
/// <param name="httpContext"></param>
/// <returns></returns>
/// <exception cref="InvalidOperationException"></exception>
public static Guid GetOrganizationId ( this HttpContext httpContext )
{
2025-05-02 07:00:48 +10:00
var routeValues = httpContext . GetRouteData ( ) . Values ;
routeValues . TryGetValue ( "orgId" , out var orgIdParam ) ;
if ( orgIdParam ! = null & & Guid . TryParse ( orgIdParam . ToString ( ) , out var orgId ) )
{
return orgId ;
}
routeValues . TryGetValue ( "organizationId" , out var organizationIdParam ) ;
if ( organizationIdParam ! = null & & Guid . TryParse ( organizationIdParam . ToString ( ) , out var organizationId ) )
2025-04-15 14:36:00 +10:00
{
2025-05-02 07:00:48 +10:00
return organizationId ;
2025-04-15 14:36:00 +10:00
}
2025-05-02 07:00:48 +10:00
throw new InvalidOperationException ( NoOrgIdError ) ;
2025-04-15 14:36:00 +10:00
}
}