fix: use tenant-agnostic application lookup for api keys
This commit is contained in:
parent
f3ee587742
commit
1d1c68282c
|
|
@ -15,15 +15,17 @@ public static class ApplicationProviderExtensions
|
|||
/// </summary>
|
||||
/// <param name="applicationProvider">The user provider.</param>
|
||||
/// <param name="clientId">The client ID to search by.</param>
|
||||
/// <param name="tenantAgnostic">Whether to bypass tenant scoping when resolving the application.</param>
|
||||
/// <param name="cancellationToken">The cancellation token.</param>
|
||||
/// <returns>The application with the specified client ID.</returns>
|
||||
public static async Task<Application?> FindByClientIdAsync(this IApplicationProvider applicationProvider, string clientId, CancellationToken cancellationToken = default)
|
||||
public static async Task<Application?> FindByClientIdAsync(this IApplicationProvider applicationProvider, string clientId, bool tenantAgnostic = false, CancellationToken cancellationToken = default)
|
||||
{
|
||||
var filter = new ApplicationFilter
|
||||
{
|
||||
ClientId = clientId
|
||||
ClientId = clientId,
|
||||
TenantAgnostic = tenantAgnostic
|
||||
};
|
||||
|
||||
return await applicationProvider.FindAsync(filter, cancellationToken);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -21,6 +21,11 @@ public class ApplicationFilter
|
|||
/// Gets or sets the application name to filter for.
|
||||
/// </summary>
|
||||
public string? Name { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether to ignore tenant scoping when querying the application store.
|
||||
/// </summary>
|
||||
public bool TenantAgnostic { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Applies the filter to the specified queryable.
|
||||
|
|
@ -36,4 +41,4 @@ public class ApplicationFilter
|
|||
|
||||
return queryable;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -50,7 +50,7 @@ public class DefaultApplicationCredentialsValidator : IApplicationCredentialsVal
|
|||
return null;
|
||||
|
||||
var clientId = _apiKeyParser.Parse(apiKey);
|
||||
var application = await _applicationProvider.FindByClientIdAsync(clientId, cancellationToken);
|
||||
var application = await _applicationProvider.FindByClientIdAsync(clientId, tenantAgnostic: true, cancellationToken);
|
||||
|
||||
if(application == null)
|
||||
return null;
|
||||
|
|
|
|||
|
|
@ -34,8 +34,8 @@ public class EFCoreApplicationStore : IApplicationStore
|
|||
/// <inheritdoc />
|
||||
public async Task<Application?> FindAsync(ApplicationFilter filter, CancellationToken cancellationToken = default)
|
||||
{
|
||||
return await _applicationStore.FindAsync(query => Filter(query, filter), cancellationToken);
|
||||
return await _applicationStore.FindAsync(query => Filter(query, filter), filter.TenantAgnostic, cancellationToken);
|
||||
}
|
||||
|
||||
private static IQueryable<Application> Filter(IQueryable<Application> query, ApplicationFilter filter) => filter.Apply(query);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -265,6 +265,34 @@ public class DefaultSecretHasherTests
|
|||
Assert.Equal(encodedLegacyHash, application.HashedApiKey);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ValidateAsync_UsesTenantAgnosticLookupForApplications()
|
||||
{
|
||||
var apiKeyGenerator = new DefaultApiKeyGeneratorAndParser();
|
||||
var apiKey = apiKeyGenerator.Generate("client-1");
|
||||
var hasher = new DefaultSecretHasher();
|
||||
var hashedApiKey = hasher.HashSecret(apiKey);
|
||||
var application = new Application
|
||||
{
|
||||
Id = "app-1",
|
||||
ClientId = "client-1",
|
||||
Name = "Client 1",
|
||||
HashedApiKey = hashedApiKey.EncodeSecret(),
|
||||
HashedApiKeySalt = hashedApiKey.EncodeSalt(),
|
||||
HashedClientSecret = "",
|
||||
HashedClientSecretSalt = ""
|
||||
};
|
||||
var applicationProvider = new RecordingApplicationProvider(application);
|
||||
var validator = new DefaultApplicationCredentialsValidator(apiKeyGenerator, applicationProvider, hasher);
|
||||
|
||||
var validatedApplication = await validator.ValidateAsync(apiKey);
|
||||
|
||||
Assert.Same(application, validatedApplication);
|
||||
Assert.NotNull(applicationProvider.LastFilter);
|
||||
Assert.True(applicationProvider.LastFilter!.TenantAgnostic);
|
||||
Assert.Equal("client-1", applicationProvider.LastFilter.ClientId);
|
||||
}
|
||||
|
||||
private static HashedSecret CreateLegacyHash(string secret)
|
||||
{
|
||||
var salt = RandomNumberGenerator.GetBytes(32);
|
||||
|
|
@ -370,6 +398,19 @@ public class DefaultSecretHasherTests
|
|||
}
|
||||
}
|
||||
|
||||
private sealed class RecordingApplicationProvider(Application application) : IApplicationProvider
|
||||
{
|
||||
private readonly Application _application = application;
|
||||
public ApplicationFilter? LastFilter { get; private set; }
|
||||
|
||||
public Task<Application?> FindAsync(ApplicationFilter filter, CancellationToken cancellationToken = default)
|
||||
{
|
||||
LastFilter = filter;
|
||||
var application = filter.Apply(new[] { _application }.AsQueryable()).FirstOrDefault();
|
||||
return Task.FromResult(application);
|
||||
}
|
||||
}
|
||||
|
||||
private sealed class CultureScope : IDisposable
|
||||
{
|
||||
private readonly CultureInfo _currentCulture = CultureInfo.CurrentCulture;
|
||||
|
|
|
|||
Loading…
Reference in a new issue