Make CT required in IProfileService, flow through implementations, callers, and tests

This commit is contained in:
Damian Hickey 2026-02-20 22:27:29 +01:00
parent ea9ba0c05f
commit a5e42b70ab
18 changed files with 38 additions and 30 deletions

View file

@ -9,10 +9,10 @@ namespace Duende.IdentityServer.Hosts.Shared.Customization;
public class HostProfileService(TestUserStore users, ILogger<TestUserProfileService> logger) : TestUserProfileService(users, logger)
{
public override async Task GetProfileDataAsync(ProfileDataRequestContext context)
public override async Task GetProfileDataAsync(ProfileDataRequestContext context, CT ct)
{
ArgumentNullException.ThrowIfNull(context);
await base.GetProfileDataAsync(context);
await base.GetProfileDataAsync(context, ct);
var transaction = context.RequestedResources.ParsedScopes.FirstOrDefault(x => x.ParsedName == "transaction");
if (transaction?.ParsedParameter != null)

View file

@ -65,8 +65,9 @@ public class ProfileService<TUser> : IProfileService
/// This method is called whenever claims about the user are requested (e.g. during token creation or via the userinfo endpoint)
/// </summary>
/// <param name="context">The context.</param>
/// <param name="ct">A token that can be used to request cancellation of the asynchronous operation.</param>
/// <returns></returns>
public virtual async Task GetProfileDataAsync(ProfileDataRequestContext context)
public virtual async Task GetProfileDataAsync(ProfileDataRequestContext context, CT ct)
{
var sub = context.Subject?.GetSubjectId();
if (sub == null)
@ -125,8 +126,9 @@ public class ProfileService<TUser> : IProfileService
/// (e.g. during token issuance or validation).
/// </summary>
/// <param name="context">The context.</param>
/// <param name="ct">A token that can be used to request cancellation of the asynchronous operation.</param>
/// <returns></returns>
public virtual async Task IsActiveAsync(IsActiveContext context)
public virtual async Task IsActiveAsync(IsActiveContext context, CT ct)
{
var sub = context.Subject?.GetSubjectId();
if (sub == null)

View file

@ -208,7 +208,7 @@ public class AuthorizeInteractionResponseGenerator : IAuthorizeInteractionRespon
if (isAuthenticated)
{
var isActiveCtx = new IsActiveContext(request.Subject, request.Client, IdentityServerConstants.ProfileIsActiveCallers.AuthorizeEndpoint);
await Profile.IsActiveAsync(isActiveCtx);
await Profile.IsActiveAsync(isActiveCtx, ct);
isActive = isActiveCtx.IsActive;
}

View file

@ -75,7 +75,7 @@ public class UserInfoResponseGenerator : IUserInfoResponseGenerator
requestedClaimTypes);
context.RequestedResources = validatedResources;
await Profile.GetProfileDataAsync(context);
await Profile.GetProfileDataAsync(context, default);
var profileClaims = context.IssuedClaims;
// construct outgoing claims

View file

@ -76,7 +76,7 @@ public class DefaultClaimsService : IClaimsService
ValidatedRequest = request
};
await Profile.GetProfileDataAsync(context);
await Profile.GetProfileDataAsync(context, ct);
var claims = FilterProtocolClaims(context.IssuedClaims);
if (claims != null)
@ -189,7 +189,7 @@ public class DefaultClaimsService : IClaimsService
ValidatedRequest = request
};
await Profile.GetProfileDataAsync(context);
await Profile.GetProfileDataAsync(context, ct);
var claims = FilterProtocolClaims(context.IssuedClaims);
if (claims != null)

View file

@ -29,8 +29,9 @@ public class DefaultProfileService : IProfileService
/// This method is called whenever claims about the user are requested (e.g. during token creation or via the userinfo endpoint)
/// </summary>
/// <param name="context">The context.</param>
/// <param name="ct">A token that can be used to request cancellation of the asynchronous operation.</param>
/// <returns></returns>
public virtual Task GetProfileDataAsync(ProfileDataRequestContext context)
public virtual Task GetProfileDataAsync(ProfileDataRequestContext context, CT ct)
{
using var activity = Tracing.ServiceActivitySource.StartActivity("DefaultProfileService.GetProfileData");
@ -46,8 +47,9 @@ public class DefaultProfileService : IProfileService
/// (e.g. during token issuance or validation).
/// </summary>
/// <param name="context">The context.</param>
/// <param name="ct">A token that can be used to request cancellation of the asynchronous operation.</param>
/// <returns></returns>
public virtual Task IsActiveAsync(IsActiveContext context)
public virtual Task IsActiveAsync(IsActiveContext context, CT ct)
{
using var activity = Tracing.ServiceActivitySource.StartActivity("DefaultProfileService.IsActive");

View file

@ -136,7 +136,7 @@ public class DefaultRefreshTokenService : IRefreshTokenService
client,
IdentityServerConstants.ProfileIsActiveCallers.RefreshTokenValidation);
await Profile.IsActiveAsync(isActiveCtx);
await Profile.IsActiveAsync(isActiveCtx, ct);
if (isActiveCtx.IsActive == false)
{

View file

@ -17,14 +17,16 @@ public interface IProfileService
/// This method is called whenever claims about the user are requested (e.g. during token creation or via the userinfo endpoint)
/// </summary>
/// <param name="context">The context.</param>
/// <param name="ct">A token that can be used to request cancellation of the asynchronous operation.</param>
/// <returns></returns>
Task GetProfileDataAsync(ProfileDataRequestContext context);
Task GetProfileDataAsync(ProfileDataRequestContext context, CT ct);
/// <summary>
/// This method gets called whenever identity server needs to determine if the user is valid or active (e.g. if the user's account has been deactivated since they logged in).
/// (e.g. during token issuance or validation).
/// </summary>
/// <param name="context">The context.</param>
/// <param name="ct">A token that can be used to request cancellation of the asynchronous operation.</param>
/// <returns></returns>
Task IsActiveAsync(IsActiveContext context);
Task IsActiveAsync(IsActiveContext context, CT ct);
}

View file

@ -40,8 +40,9 @@ public class TestUserProfileService : IProfileService
/// This method is called whenever claims about the user are requested (e.g. during token creation or via the userinfo endpoint)
/// </summary>
/// <param name="context">The context.</param>
/// <param name="ct">A token that can be used to request cancellation of the asynchronous operation.</param>
/// <returns></returns>
public virtual Task GetProfileDataAsync(ProfileDataRequestContext context)
public virtual Task GetProfileDataAsync(ProfileDataRequestContext context, CT ct)
{
context.LogProfileRequest(Logger);
@ -64,8 +65,9 @@ public class TestUserProfileService : IProfileService
/// (e.g. during token issuance or validation).
/// </summary>
/// <param name="context">The context.</param>
/// <param name="ct">A token that can be used to request cancellation of the asynchronous operation.</param>
/// <returns></returns>
public virtual Task IsActiveAsync(IsActiveContext context)
public virtual Task IsActiveAsync(IsActiveContext context, CT ct)
{
Logger.LogDebug("IsActive called from: {caller}", context.Caller);

View file

@ -92,7 +92,7 @@ internal class BackchannelAuthenticationRequestIdValidator : IBackchannelAuthent
// make sure user is enabled
var isActiveCtx = new IsActiveContext(request.Subject, context.Request.Client, IdentityServerConstants.ProfileIsActiveCallers.BackchannelAuthenticationRequestIdValidation);
await _profile.IsActiveAsync(isActiveCtx);
await _profile.IsActiveAsync(isActiveCtx, ct);
if (isActiveCtx.IsActive == false)
{

View file

@ -103,7 +103,7 @@ internal class DeviceCodeValidator : IDeviceCodeValidator
// make sure user is enabled
var isActiveCtx = new IsActiveContext(deviceCode.Subject, context.Request.Client, IdentityServerConstants.ProfileIsActiveCallers.DeviceCodeValidation);
await _profile.IsActiveAsync(isActiveCtx);
await _profile.IsActiveAsync(isActiveCtx, ct);
if (isActiveCtx.IsActive == false)
{

View file

@ -513,7 +513,7 @@ internal class TokenRequestValidator : ITokenRequestValidator
// make sure user is enabled
/////////////////////////////////////////////
var isActiveCtx = new IsActiveContext(_validatedRequest.AuthorizationCode.Subject, _validatedRequest.Client, IdentityServerConstants.ProfileIsActiveCallers.AuthorizationCodeValidation);
await _profile.IsActiveAsync(isActiveCtx);
await _profile.IsActiveAsync(isActiveCtx, _ct);
if (isActiveCtx.IsActive == false)
{
@ -663,7 +663,7 @@ internal class TokenRequestValidator : ITokenRequestValidator
// make sure user is enabled
/////////////////////////////////////////////
var isActiveCtx = new IsActiveContext(resourceOwnerContext.Result.Subject, _validatedRequest.Client, IdentityServerConstants.ProfileIsActiveCallers.ResourceOwnerValidation);
await _profile.IsActiveAsync(isActiveCtx);
await _profile.IsActiveAsync(isActiveCtx, _ct);
if (isActiveCtx.IsActive == false)
{
@ -1074,7 +1074,7 @@ internal class TokenRequestValidator : ITokenRequestValidator
_validatedRequest.Client,
IdentityServerConstants.ProfileIsActiveCallers.ExtensionGrantValidation);
await _profile.IsActiveAsync(isActiveCtx);
await _profile.IsActiveAsync(isActiveCtx, _ct);
if (isActiveCtx.IsActive == false)
{

View file

@ -207,7 +207,7 @@ internal class TokenValidator : ITokenValidator
var isActiveCtx = new IsActiveContext(principal, result.Client,
IdentityServerConstants.ProfileIsActiveCallers.AccessTokenValidation);
await _profile.IsActiveAsync(isActiveCtx);
await _profile.IsActiveAsync(isActiveCtx, ct);
if (isActiveCtx.IsActive == false)
{

View file

@ -115,7 +115,7 @@ internal class UserInfoRequestValidator : IUserInfoRequestValidator
// make sure user is still active
var isActiveContext = new IsActiveContext(subject, tokenResult.Client!, IdentityServerConstants.ProfileIsActiveCallers.UserInfoRequestValidation);
await _profile.IsActiveAsync(isActiveContext);
await _profile.IsActiveAsync(isActiveContext, ct);
if (isActiveContext.IsActive == false)
{

View file

@ -13,9 +13,9 @@ internal class CustomProfileService : TestUserProfileService
public CustomProfileService(TestUserStore users, ILogger<TestUserProfileService> logger) : base(users, logger)
{ }
public override async Task GetProfileDataAsync(ProfileDataRequestContext context)
public override async Task GetProfileDataAsync(ProfileDataRequestContext context, CT ct)
{
await base.GetProfileDataAsync(context);
await base.GetProfileDataAsync(context, ct);
if (context.Subject.Identity.AuthenticationType == "custom")
{

View file

@ -82,7 +82,7 @@ public class CustomProfileServiceTests
public class CustomProfileService : IProfileService
{
public Task GetProfileDataAsync(ProfileDataRequestContext context)
public Task GetProfileDataAsync(ProfileDataRequestContext context, CT ct)
{
var claims = new Claim[]
{
@ -92,7 +92,7 @@ public class CustomProfileService : IProfileService
return Task.CompletedTask;
}
public Task IsActiveAsync(IsActiveContext context)
public Task IsActiveAsync(IsActiveContext context, CT ct)
{
context.IsActive = true;
return Task.CompletedTask;

View file

@ -19,14 +19,14 @@ public class MockProfileService : IProfileService
public bool IsActiveWasCalled => ActiveContext != null;
public IsActiveContext ActiveContext { get; set; }
public Task GetProfileDataAsync(ProfileDataRequestContext context)
public Task GetProfileDataAsync(ProfileDataRequestContext context, CT ct)
{
ProfileContext = context;
context.IssuedClaims = ProfileClaims.ToList();
return Task.CompletedTask;
}
public Task IsActiveAsync(IsActiveContext context)
public Task IsActiveAsync(IsActiveContext context, CT ct)
{
ActiveContext = context;
context.IsActive = IsActive;

View file

@ -13,9 +13,9 @@ internal class TestProfileService : IProfileService
public TestProfileService(bool shouldBeActive = true) => _shouldBeActive = shouldBeActive;
public Task GetProfileDataAsync(ProfileDataRequestContext context) => Task.CompletedTask;
public Task GetProfileDataAsync(ProfileDataRequestContext context, CT ct) => Task.CompletedTask;
public Task IsActiveAsync(IsActiveContext context)
public Task IsActiveAsync(IsActiveContext context, CT ct)
{
context.IsActive = _shouldBeActive;
return Task.CompletedTask;