From f54d124340b158ec75dd426d597038fc25f2fcf4 Mon Sep 17 00:00:00 2001 From: Damian Hickey Date: Fri, 20 Feb 2026 23:26:10 +0100 Subject: [PATCH] Make CT required in IIdentityServerInteractionService and IBackchannelAuthenticationInteractionService.CompleteLoginRequestAsync, flow through implementations, callers, and tests --- .../Pages/Account/Login/Index.cshtml.cs | 6 +- .../Pages/Account/Logout/Index.cshtml.cs | 4 +- .../Pages/ExternalLogin/Callback.cshtml.cs | 2 +- .../Main/Pages/Account/Create/Index.cshtml.cs | 4 +- .../Main/Pages/Account/Login/Index.cshtml.cs | 6 +- .../Main/Pages/Account/Logout/Index.cshtml.cs | 4 +- .../Pages/Account/Logout/LoggedOut.cshtml.cs | 2 +- .../UI/Main/Pages/Ciba/Consent.cshtml.cs | 2 +- .../UI/Main/Pages/Consent/Index.cshtml.cs | 6 +- .../Pages/ExternalLogin/Callback.cshtml.cs | 2 +- .../UI/Main/Pages/Grants/Index.cshtml.cs | 4 +- .../UI/Main/Pages/Home/Error/Index.cshtml.cs | 2 +- ...channelAuthenticationInteractionService.cs | 10 +-- ...DefaultIdentityServerInteractionService.cs | 61 +++++++++++-------- ...channelAuthenticationInteractionService.cs | 4 +- .../IIdentityServerInteractionService.cs | 27 +++++--- .../Common/IdentityServerPipeline.cs | 14 ++--- .../Endpoints/Token/CibaTokenEndpointTests.cs | 12 ++-- .../Hosting/DynamicProvidersTests.cs | 2 +- ...elAuthenticationInteractionServiceTests.cs | 16 ++--- ...ltIdentityServerInteractionServiceTests.cs | 19 +++--- .../Validation/IsLocalUrlTests.cs | 2 +- 22 files changed, 117 insertions(+), 94 deletions(-) diff --git a/identity-server/hosts/UI/AspNetIdentity/Pages/Account/Login/Index.cshtml.cs b/identity-server/hosts/UI/AspNetIdentity/Pages/Account/Login/Index.cshtml.cs index 1903cd414..8cdf84436 100644 --- a/identity-server/hosts/UI/AspNetIdentity/Pages/Account/Login/Index.cshtml.cs +++ b/identity-server/hosts/UI/AspNetIdentity/Pages/Account/Login/Index.cshtml.cs @@ -64,7 +64,7 @@ public class Index : PageModel public async Task OnPost() { // check if we are in the context of an authorization request - var context = await _interaction.GetAuthorizationContextAsync(Input.ReturnUrl); + var context = await _interaction.GetAuthorizationContextAsync(Input.ReturnUrl, HttpContext.RequestAborted); // the user clicked the "cancel" button if (Input.Button != "login") @@ -77,7 +77,7 @@ public class Index : PageModel // if the user cancels, send a result back into IdentityServer as if they // denied the consent (even if this client does not require consent). // this will send back an access denied OIDC error response to the client. - await _interaction.DenyAuthorizationAsync(context, AuthorizationError.AccessDenied); + await _interaction.DenyAuthorizationAsync(context, AuthorizationError.AccessDenied, HttpContext.RequestAborted); // we can trust model.ReturnUrl since GetAuthorizationContextAsync returned non-null if (context.IsNativeClient()) @@ -158,7 +158,7 @@ public class Index : PageModel ReturnUrl = returnUrl }; - var context = await _interaction.GetAuthorizationContextAsync(returnUrl); + var context = await _interaction.GetAuthorizationContextAsync(returnUrl, HttpContext.RequestAborted); if (context?.IdP != null) { var scheme = await _schemeProvider.GetSchemeAsync(context.IdP); diff --git a/identity-server/hosts/UI/AspNetIdentity/Pages/Account/Logout/Index.cshtml.cs b/identity-server/hosts/UI/AspNetIdentity/Pages/Account/Logout/Index.cshtml.cs index 3c754d2c2..01f147b4b 100644 --- a/identity-server/hosts/UI/AspNetIdentity/Pages/Account/Logout/Index.cshtml.cs +++ b/identity-server/hosts/UI/AspNetIdentity/Pages/Account/Logout/Index.cshtml.cs @@ -47,7 +47,7 @@ public class Index : PageModel } else { - var context = await _interaction.GetLogoutContextAsync(LogoutId); + var context = await _interaction.GetLogoutContextAsync(LogoutId, HttpContext.RequestAborted); if (context?.ShowSignoutPrompt == false) { // it's safe to automatically sign-out @@ -72,7 +72,7 @@ public class Index : PageModel // if there's no current logout context, we need to create one // this captures necessary info from the current logged in user // this can still return null if there is no context needed - LogoutId ??= await _interaction.CreateLogoutContextAsync(); + LogoutId ??= await _interaction.CreateLogoutContextAsync(HttpContext.RequestAborted); // delete local authentication cookie await _signInManager.SignOutAsync(); diff --git a/identity-server/hosts/UI/AspNetIdentity/Pages/ExternalLogin/Callback.cshtml.cs b/identity-server/hosts/UI/AspNetIdentity/Pages/ExternalLogin/Callback.cshtml.cs index c97589f9b..532bbe71c 100644 --- a/identity-server/hosts/UI/AspNetIdentity/Pages/ExternalLogin/Callback.cshtml.cs +++ b/identity-server/hosts/UI/AspNetIdentity/Pages/ExternalLogin/Callback.cshtml.cs @@ -100,7 +100,7 @@ public class Callback : PageModel var returnUrl = result.Properties.Items["returnUrl"] ?? "~/"; // check if external login is in the context of an OIDC request - var context = await _interaction.GetAuthorizationContextAsync(returnUrl); + var context = await _interaction.GetAuthorizationContextAsync(returnUrl, HttpContext.RequestAborted); await _events.RaiseAsync(new UserLoginSuccessEvent(provider, providerUserId, user.Id, user.UserName, true, context?.Client.ClientId), HttpContext.RequestAborted); Duende.IdentityServer.UI.Pages.Telemetry.Metrics.UserLogin(context?.Client.ClientId, provider!); diff --git a/identity-server/hosts/UI/Main/Pages/Account/Create/Index.cshtml.cs b/identity-server/hosts/UI/Main/Pages/Account/Create/Index.cshtml.cs index bb3def5cf..02dfebe02 100644 --- a/identity-server/hosts/UI/Main/Pages/Account/Create/Index.cshtml.cs +++ b/identity-server/hosts/UI/Main/Pages/Account/Create/Index.cshtml.cs @@ -39,7 +39,7 @@ public class Index : PageModel public async Task OnPost() { // check if we are in the context of an authorization request - var context = await _interaction.GetAuthorizationContextAsync(Input.ReturnUrl); + var context = await _interaction.GetAuthorizationContextAsync(Input.ReturnUrl, HttpContext.RequestAborted); // the user clicked the "cancel" button if (Input.Button != "create") @@ -49,7 +49,7 @@ public class Index : PageModel // if the user cancels, send a result back into IdentityServer as if they // denied the consent (even if this client does not require consent). // this will send back an access denied OIDC error response to the client. - await _interaction.DenyAuthorizationAsync(context, AuthorizationError.AccessDenied); + await _interaction.DenyAuthorizationAsync(context, AuthorizationError.AccessDenied, HttpContext.RequestAborted); // we can trust model.ReturnUrl since GetAuthorizationContextAsync returned non-null if (context.IsNativeClient()) diff --git a/identity-server/hosts/UI/Main/Pages/Account/Login/Index.cshtml.cs b/identity-server/hosts/UI/Main/Pages/Account/Login/Index.cshtml.cs index 4f418045c..6b79a843e 100644 --- a/identity-server/hosts/UI/Main/Pages/Account/Login/Index.cshtml.cs +++ b/identity-server/hosts/UI/Main/Pages/Account/Login/Index.cshtml.cs @@ -60,7 +60,7 @@ public class Index : PageModel public async Task OnPost() { // check if we are in the context of an authorization request - var context = await _interaction.GetAuthorizationContextAsync(Input.ReturnUrl); + var context = await _interaction.GetAuthorizationContextAsync(Input.ReturnUrl, HttpContext.RequestAborted); // the user clicked the "cancel" button if (Input.Button != "login") @@ -73,7 +73,7 @@ public class Index : PageModel // if the user cancels, send a result back into IdentityServer as if they // denied the consent (even if this client does not require consent). // this will send back an access denied OIDC error response to the client. - await _interaction.DenyAuthorizationAsync(context, AuthorizationError.AccessDenied); + await _interaction.DenyAuthorizationAsync(context, AuthorizationError.AccessDenied, HttpContext.RequestAborted); // we can trust model.ReturnUrl since GetAuthorizationContextAsync returned non-null if (context.IsNativeClient()) @@ -168,7 +168,7 @@ public class Index : PageModel ReturnUrl = returnUrl }; - var context = await _interaction.GetAuthorizationContextAsync(returnUrl); + var context = await _interaction.GetAuthorizationContextAsync(returnUrl, HttpContext.RequestAborted); if (context?.IdP != null) { var scheme = await _schemeProvider.GetSchemeAsync(context.IdP); diff --git a/identity-server/hosts/UI/Main/Pages/Account/Logout/Index.cshtml.cs b/identity-server/hosts/UI/Main/Pages/Account/Logout/Index.cshtml.cs index d9e93afe9..69ffbb118 100644 --- a/identity-server/hosts/UI/Main/Pages/Account/Logout/Index.cshtml.cs +++ b/identity-server/hosts/UI/Main/Pages/Account/Logout/Index.cshtml.cs @@ -41,7 +41,7 @@ public class Index : PageModel } else { - var context = await _interaction.GetLogoutContextAsync(LogoutId); + var context = await _interaction.GetLogoutContextAsync(LogoutId, HttpContext.RequestAborted); if (context?.ShowSignoutPrompt == false) { // it's safe to automatically sign-out @@ -66,7 +66,7 @@ public class Index : PageModel // if there's no current logout context, we need to create one // this captures necessary info from the current logged in user // this can still return null if there is no context needed - LogoutId ??= await _interaction.CreateLogoutContextAsync(); + LogoutId ??= await _interaction.CreateLogoutContextAsync(HttpContext.RequestAborted); // delete local authentication cookie await HttpContext.SignOutAsync(); diff --git a/identity-server/hosts/UI/Main/Pages/Account/Logout/LoggedOut.cshtml.cs b/identity-server/hosts/UI/Main/Pages/Account/Logout/LoggedOut.cshtml.cs index 8b88394d8..deee46f99 100644 --- a/identity-server/hosts/UI/Main/Pages/Account/Logout/LoggedOut.cshtml.cs +++ b/identity-server/hosts/UI/Main/Pages/Account/Logout/LoggedOut.cshtml.cs @@ -20,7 +20,7 @@ public class LoggedOut : PageModel public async Task OnGet(string? logoutId) { // get context information (client name, post logout redirect URI and iframe for federated signout) - var logout = await _interactionService.GetLogoutContextAsync(logoutId); + var logout = await _interactionService.GetLogoutContextAsync(logoutId, HttpContext.RequestAborted); View = new LoggedOutViewModel { diff --git a/identity-server/hosts/UI/Main/Pages/Ciba/Consent.cshtml.cs b/identity-server/hosts/UI/Main/Pages/Ciba/Consent.cshtml.cs index ed7a604e6..a065336ac 100644 --- a/identity-server/hosts/UI/Main/Pages/Ciba/Consent.cshtml.cs +++ b/identity-server/hosts/UI/Main/Pages/Ciba/Consent.cshtml.cs @@ -108,7 +108,7 @@ public class Consent : PageModel if (result != null) { // communicate outcome of consent back to identityserver - await _interaction.CompleteLoginRequestAsync(result); + await _interaction.CompleteLoginRequestAsync(result, HttpContext.RequestAborted); return RedirectToPage("/Ciba/All"); } diff --git a/identity-server/hosts/UI/Main/Pages/Consent/Index.cshtml.cs b/identity-server/hosts/UI/Main/Pages/Consent/Index.cshtml.cs index 6f6ee345f..9691ca3d9 100644 --- a/identity-server/hosts/UI/Main/Pages/Consent/Index.cshtml.cs +++ b/identity-server/hosts/UI/Main/Pages/Consent/Index.cshtml.cs @@ -54,7 +54,7 @@ public class Index : PageModel public async Task OnPost() { // validate return url is still valid - var request = await _interaction.GetAuthorizationContextAsync(Input.ReturnUrl); + var request = await _interaction.GetAuthorizationContextAsync(Input.ReturnUrl, HttpContext.RequestAborted); if (request == null) { return RedirectToPage("/Home/Error/Index"); @@ -111,7 +111,7 @@ public class Index : PageModel ArgumentNullException.ThrowIfNull(Input.ReturnUrl, nameof(Input.ReturnUrl)); // communicate outcome of consent back to identityserver - await _interaction.GrantConsentAsync(request, grantedConsent); + await _interaction.GrantConsentAsync(request, grantedConsent, HttpContext.RequestAborted); // redirect back to authorization endpoint if (request.IsNativeClient() == true) @@ -136,7 +136,7 @@ public class Index : PageModel { ArgumentNullException.ThrowIfNull(returnUrl); - var request = await _interaction.GetAuthorizationContextAsync(returnUrl); + var request = await _interaction.GetAuthorizationContextAsync(returnUrl, HttpContext.RequestAborted); if (request != null) { View = CreateConsentViewModel(request); diff --git a/identity-server/hosts/UI/Main/Pages/ExternalLogin/Callback.cshtml.cs b/identity-server/hosts/UI/Main/Pages/ExternalLogin/Callback.cshtml.cs index e65ee8138..1495be559 100644 --- a/identity-server/hosts/UI/Main/Pages/ExternalLogin/Callback.cshtml.cs +++ b/identity-server/hosts/UI/Main/Pages/ExternalLogin/Callback.cshtml.cs @@ -103,7 +103,7 @@ public class Callback : PageModel var returnUrl = result.Properties.Items["returnUrl"] ?? "~/"; // check if external login is in the context of an OIDC request - var context = await _interaction.GetAuthorizationContextAsync(returnUrl); + var context = await _interaction.GetAuthorizationContextAsync(returnUrl, HttpContext.RequestAborted); await _events.RaiseAsync(new UserLoginSuccessEvent(provider, providerUserId, user.SubjectId, user.Username, true, context?.Client.ClientId), HttpContext.RequestAborted); Telemetry.Metrics.UserLogin(context?.Client.ClientId, provider!); diff --git a/identity-server/hosts/UI/Main/Pages/Grants/Index.cshtml.cs b/identity-server/hosts/UI/Main/Pages/Grants/Index.cshtml.cs index a3a75161b..ddc704220 100644 --- a/identity-server/hosts/UI/Main/Pages/Grants/Index.cshtml.cs +++ b/identity-server/hosts/UI/Main/Pages/Grants/Index.cshtml.cs @@ -35,7 +35,7 @@ public class Index : PageModel public async Task OnGet() { - var grants = await _interaction.GetAllUserGrantsAsync(); + var grants = await _interaction.GetAllUserGrantsAsync(HttpContext.RequestAborted); var list = new List(); foreach (var grant in grants) @@ -73,7 +73,7 @@ public class Index : PageModel public async Task OnPost() { - await _interaction.RevokeUserConsentAsync(ClientId); + await _interaction.RevokeUserConsentAsync(ClientId, HttpContext.RequestAborted); await _events.RaiseAsync(new GrantsRevokedEvent(User.GetSubjectId(), ClientId), HttpContext.RequestAborted); Telemetry.Metrics.GrantsRevoked(ClientId); diff --git a/identity-server/hosts/UI/Main/Pages/Home/Error/Index.cshtml.cs b/identity-server/hosts/UI/Main/Pages/Home/Error/Index.cshtml.cs index 592ab5216..21324614f 100644 --- a/identity-server/hosts/UI/Main/Pages/Home/Error/Index.cshtml.cs +++ b/identity-server/hosts/UI/Main/Pages/Home/Error/Index.cshtml.cs @@ -25,7 +25,7 @@ public class Index : PageModel public async Task OnGet(string? errorId) { // retrieve error details from identityserver - var message = await _interaction.GetErrorContextAsync(errorId); + var message = await _interaction.GetErrorContextAsync(errorId, HttpContext.RequestAborted); if (message != null) { View.Error = message; diff --git a/identity-server/src/IdentityServer/Services/Default/DefaultBackchannelAuthenticationInteractionService.cs b/identity-server/src/IdentityServer/Services/Default/DefaultBackchannelAuthenticationInteractionService.cs index 1f25cc7a3..7822414fd 100644 --- a/identity-server/src/IdentityServer/Services/Default/DefaultBackchannelAuthenticationInteractionService.cs +++ b/identity-server/src/IdentityServer/Services/Default/DefaultBackchannelAuthenticationInteractionService.cs @@ -117,19 +117,19 @@ public class DefaultBackchannelAuthenticationInteractionService : IBackchannelAu } /// - public async Task CompleteLoginRequestAsync(CompleteBackchannelLoginRequest completionRequest) + public async Task CompleteLoginRequestAsync(CompleteBackchannelLoginRequest completionRequest, CT ct) { using var activity = Tracing.ServiceActivitySource.StartActivity("DefaultBackchannelAuthenticationInteractionService.CompleteLoginRequest"); ArgumentNullException.ThrowIfNull(completionRequest); - var request = await _requestStore.GetByInternalIdAsync(completionRequest.InternalId, default); + var request = await _requestStore.GetByInternalIdAsync(completionRequest.InternalId, ct); if (request == null) { throw new InvalidOperationException("Invalid backchannel authentication request id."); } - var subject = completionRequest.Subject ?? await _session.GetUserAsync(default); + var subject = completionRequest.Subject ?? await _session.GetUserAsync(ct); if (subject == null) { throw new InvalidOperationException("Invalid subject."); @@ -141,7 +141,7 @@ public class DefaultBackchannelAuthenticationInteractionService : IBackchannelAu } var sid = (completionRequest.Subject == null) ? - await _session.GetSessionIdAsync(default) : + await _session.GetSessionIdAsync(ct) : completionRequest.SessionId; if (completionRequest.ScopesValuesConsented != null) @@ -170,7 +170,7 @@ public class DefaultBackchannelAuthenticationInteractionService : IBackchannelAu request.AuthorizedScopes = completionRequest.ScopesValuesConsented; request.Description = completionRequest.Description; - await _requestStore.UpdateByInternalIdAsync(completionRequest.InternalId, request, default); + await _requestStore.UpdateByInternalIdAsync(completionRequest.InternalId, request, ct); _logger.LogDebug("Successful update for backchannel authentication request id {id}", completionRequest.InternalId); } diff --git a/identity-server/src/IdentityServer/Services/Default/DefaultIdentityServerInteractionService.cs b/identity-server/src/IdentityServer/Services/Default/DefaultIdentityServerInteractionService.cs index aeb454f17..74069bbd0 100644 --- a/identity-server/src/IdentityServer/Services/Default/DefaultIdentityServerInteractionService.cs +++ b/identity-server/src/IdentityServer/Services/Default/DefaultIdentityServerInteractionService.cs @@ -44,11 +44,12 @@ internal class DefaultIdentityServerInteractionService : IIdentityServerInteract _logger = logger; } - public async Task GetAuthorizationContextAsync(string returnUrl) + /// + public async Task GetAuthorizationContextAsync(string returnUrl, CT ct) { using var activity = Tracing.ServiceActivitySource.StartActivity("DefaultIdentityServerInteractionService.GetAuthorizationContext"); - var result = await _returnUrlParser.ParseAsync(returnUrl, default); + var result = await _returnUrlParser.ParseAsync(returnUrl, ct); if (result != null) { @@ -62,33 +63,35 @@ internal class DefaultIdentityServerInteractionService : IIdentityServerInteract return result; } - public async Task GetLogoutContextAsync(string logoutId) + /// + public async Task GetLogoutContextAsync(string logoutId, CT ct) { using var activity = Tracing.ServiceActivitySource.StartActivity("DefaultIdentityServerInteractionService.GetLogoutContext"); - var msg = await _logoutMessageStore.ReadAsync(logoutId, default); + var msg = await _logoutMessageStore.ReadAsync(logoutId, ct); var iframeUrl = await _context.HttpContext.GetIdentityServerSignoutFrameCallbackUrlAsync(msg?.Data); return new LogoutRequest(iframeUrl, msg?.Data); } - public async Task CreateLogoutContextAsync() + /// + public async Task CreateLogoutContextAsync(CT ct) { using var activity = Tracing.ServiceActivitySource.StartActivity("DefaultIdentityServerInteractionService.CreateLogoutContext"); - var user = await _userSession.GetUserAsync(default); + var user = await _userSession.GetUserAsync(ct); if (user != null) { - var clientIds = await _userSession.GetClientListAsync(default); + var clientIds = await _userSession.GetClientListAsync(ct); if (clientIds.Any()) { - var sid = await _userSession.GetSessionIdAsync(default); + var sid = await _userSession.GetSessionIdAsync(ct); var msg = new Message(new LogoutMessage { SubjectId = user.GetSubjectId(), SessionId = sid, ClientIds = clientIds }, _timeProvider.GetUtcNow().UtcDateTime); - var id = await _logoutMessageStore.WriteAsync(msg, default); + var id = await _logoutMessageStore.WriteAsync(msg, ct); return id; } } @@ -96,13 +99,14 @@ internal class DefaultIdentityServerInteractionService : IIdentityServerInteract return null; } - public async Task GetErrorContextAsync(string errorId) + /// + public async Task GetErrorContextAsync(string errorId, CT ct) { using var activity = Tracing.ServiceActivitySource.StartActivity("DefaultIdentityServerInteractionService.GetErrorContext"); if (errorId != null) { - var result = await _errorMessageStore.ReadAsync(errorId, default); + var result = await _errorMessageStore.ReadAsync(errorId, ct); var data = result?.Data; if (data != null) { @@ -120,13 +124,14 @@ internal class DefaultIdentityServerInteractionService : IIdentityServerInteract return null; } - public async Task GrantConsentAsync(AuthorizationRequest request, ConsentResponse consent, string subject = null) + /// + public async Task GrantConsentAsync(AuthorizationRequest request, ConsentResponse consent, CT ct, string subject = null) { using var activity = Tracing.ServiceActivitySource.StartActivity("DefaultIdentityServerInteractionService.GrantConsent"); if (subject == null) { - var user = await _userSession.GetUserAsync(default); + var user = await _userSession.GetUserAsync(ct); subject = user?.GetSubjectId(); } @@ -136,10 +141,11 @@ internal class DefaultIdentityServerInteractionService : IIdentityServerInteract } var consentRequest = new ConsentRequest(request, subject); - await _consentMessageStore.WriteAsync(consentRequest.Id, new Message(consent, _timeProvider.GetUtcNow().UtcDateTime), default); + await _consentMessageStore.WriteAsync(consentRequest.Id, new Message(consent, _timeProvider.GetUtcNow().UtcDateTime), ct); } - public Task DenyAuthorizationAsync(AuthorizationRequest request, AuthorizationError error, string errorDescription = null) + /// + public Task DenyAuthorizationAsync(AuthorizationRequest request, AuthorizationError error, CT ct, string errorDescription = null) { using var activity = Tracing.ServiceActivitySource.StartActivity("DefaultIdentityServerInteractionService.DenyAuthorization"); @@ -148,7 +154,7 @@ internal class DefaultIdentityServerInteractionService : IIdentityServerInteract Error = error, ErrorDescription = errorDescription }; - return GrantConsentAsync(request, response); + return GrantConsentAsync(request, response, ct); } public bool IsValidReturnUrl(string returnUrl) @@ -169,42 +175,45 @@ internal class DefaultIdentityServerInteractionService : IIdentityServerInteract return result; } - public async Task> GetAllUserGrantsAsync() + /// + public async Task> GetAllUserGrantsAsync(CT ct) { using var activity = Tracing.ServiceActivitySource.StartActivity("DefaultIdentityServerInteractionService.GetAllUserGrants"); - var user = await _userSession.GetUserAsync(default); + var user = await _userSession.GetUserAsync(ct); if (user != null) { var subject = user.GetSubjectId(); - return await _grants.GetAllGrantsAsync(subject, default); + return await _grants.GetAllGrantsAsync(subject, ct); } return Enumerable.Empty(); } - public async Task RevokeUserConsentAsync(string clientId) + /// + public async Task RevokeUserConsentAsync(string clientId, CT ct) { using var activity = Tracing.ServiceActivitySource.StartActivity("DefaultIdentityServerInteractionService.RevokeUserConsent"); - var user = await _userSession.GetUserAsync(default); + var user = await _userSession.GetUserAsync(ct); if (user != null) { var subject = user.GetSubjectId(); - await _grants.RemoveAllGrantsAsync(subject, clientId); + await _grants.RemoveAllGrantsAsync(subject, clientId, ct: ct); } } - public async Task RevokeTokensForCurrentSessionAsync() + /// + public async Task RevokeTokensForCurrentSessionAsync(CT ct) { using var activity = Tracing.ServiceActivitySource.StartActivity("DefaultIdentityServerInteractionService.RevokeTokensForCurrentSession"); - var user = await _userSession.GetUserAsync(default); + var user = await _userSession.GetUserAsync(ct); if (user != null) { var subject = user.GetSubjectId(); - var sessionId = await _userSession.GetSessionIdAsync(default); - await _grants.RemoveAllGrantsAsync(subject, sessionId: sessionId); + var sessionId = await _userSession.GetSessionIdAsync(ct); + await _grants.RemoveAllGrantsAsync(subject, sessionId: sessionId, ct: ct); } } } diff --git a/identity-server/src/IdentityServer/Services/IBackchannelAuthenticationInteractionService.cs b/identity-server/src/IdentityServer/Services/IBackchannelAuthenticationInteractionService.cs index 2d9f2828b..f9dae2e0d 100644 --- a/identity-server/src/IdentityServer/Services/IBackchannelAuthenticationInteractionService.cs +++ b/identity-server/src/IdentityServer/Services/IBackchannelAuthenticationInteractionService.cs @@ -27,7 +27,9 @@ public interface IBackchannelAuthenticationInteractionService /// /// Completes the login request with the provided response for the current user or the subject passed. /// - Task CompleteLoginRequestAsync(CompleteBackchannelLoginRequest completionRequest); + /// The completion request. + /// The to monitor for cancellation requests. + Task CompleteLoginRequestAsync(CompleteBackchannelLoginRequest completionRequest, CT ct); } /// diff --git a/identity-server/src/IdentityServer/Services/IIdentityServerInteractionService.cs b/identity-server/src/IdentityServer/Services/IIdentityServerInteractionService.cs index 12b3fc2da..10bf344c0 100644 --- a/identity-server/src/IdentityServer/Services/IIdentityServerInteractionService.cs +++ b/identity-server/src/IdentityServer/Services/IIdentityServerInteractionService.cs @@ -17,7 +17,8 @@ public interface IIdentityServerInteractionService /// Gets the authorization context. /// /// The return URL. - Task GetAuthorizationContextAsync(string? returnUrl); + /// The to monitor for cancellation requests. + Task GetAuthorizationContextAsync(string? returnUrl, CT ct); /// /// Indicates if the returnUrl is a valid URL for redirect after login or consent. @@ -29,27 +30,31 @@ public interface IIdentityServerInteractionService /// Gets the error context. /// /// The error identifier. - Task GetErrorContextAsync(string? errorId); + /// The to monitor for cancellation requests. + Task GetErrorContextAsync(string? errorId, CT ct); /// /// Gets the logout context. /// /// The logout identifier. - Task GetLogoutContextAsync(string? logoutId); + /// The to monitor for cancellation requests. + Task GetLogoutContextAsync(string? logoutId, CT ct); /// /// Used to create a logoutId if there is not one presently. /// + /// The to monitor for cancellation requests. /// - Task CreateLogoutContextAsync(); + Task CreateLogoutContextAsync(CT ct); /// /// Informs IdentityServer of the user's consent. /// /// The request. /// The consent. + /// The to monitor for cancellation requests. /// The subject. - Task GrantConsentAsync(AuthorizationRequest request, ConsentResponse consent, string? subject = null); + Task GrantConsentAsync(AuthorizationRequest request, ConsentResponse consent, CT ct, string? subject = null); /// /// Triggers error back to the client for the authorization request. @@ -57,22 +62,26 @@ public interface IIdentityServerInteractionService /// /// The request. /// + /// The to monitor for cancellation requests. /// - Task DenyAuthorizationAsync(AuthorizationRequest request, AuthorizationError error, string? errorDescription = null); + Task DenyAuthorizationAsync(AuthorizationRequest request, AuthorizationError error, CT ct, string? errorDescription = null); /// /// Returns a collection representing all of the user's consents and grants. /// - Task> GetAllUserGrantsAsync(); + /// The to monitor for cancellation requests. + Task> GetAllUserGrantsAsync(CT ct); /// /// Revokes all a user's consents and grants for a given client, or for all clients if clientId is null. /// /// The client identifier. - Task RevokeUserConsentAsync(string? clientId); + /// The to monitor for cancellation requests. + Task RevokeUserConsentAsync(string? clientId, CT ct); /// /// Revokes all of a user's consents and grants for clients the user has signed into during their current session. /// - Task RevokeTokensForCurrentSessionAsync(); + /// The to monitor for cancellation requests. + Task RevokeTokensForCurrentSessionAsync(CT ct); } diff --git a/identity-server/test/IdentityServer.IntegrationTests/Common/IdentityServerPipeline.cs b/identity-server/test/IdentityServer.IntegrationTests/Common/IdentityServerPipeline.cs index 7931a6ec9..7519eaad9 100644 --- a/identity-server/test/IdentityServer.IntegrationTests/Common/IdentityServerPipeline.cs +++ b/identity-server/test/IdentityServer.IntegrationTests/Common/IdentityServerPipeline.cs @@ -251,7 +251,7 @@ public class IdentityServerPipeline CreateAccountWasCalled = true; var interaction = ctx.RequestServices.GetRequiredService(); CreateAccountReturnUrl = ctx.Request.Query[Options.UserInteraction.CreateAccountReturnUrlParameter].FirstOrDefault(); - CreateAccountRequest = await interaction.GetAuthorizationContextAsync(CreateAccountReturnUrl); + CreateAccountRequest = await interaction.GetAuthorizationContextAsync(CreateAccountReturnUrl, ctx.RequestAborted); await IssueLoginCookie(ctx); } @@ -259,7 +259,7 @@ public class IdentityServerPipeline { var interaction = ctx.RequestServices.GetRequiredService(); LoginReturnUrl = ctx.Request.Query[Options.UserInteraction.LoginReturnUrlParameter].FirstOrDefault(); - LoginRequest = await interaction.GetAuthorizationContextAsync(LoginReturnUrl); + LoginRequest = await interaction.GetAuthorizationContextAsync(LoginReturnUrl, ctx.RequestAborted); } private async Task IssueLoginCookie(HttpContext ctx) @@ -290,7 +290,7 @@ public class IdentityServerPipeline private async Task ReadLogoutRequest(HttpContext ctx) { var interaction = ctx.RequestServices.GetRequiredService(); - LogoutRequest = await interaction.GetLogoutContextAsync(ctx.Request.Query["logoutId"].FirstOrDefault()); + LogoutRequest = await interaction.GetLogoutContextAsync(ctx.Request.Query["logoutId"].FirstOrDefault(), ctx.RequestAborted); } public bool ConsentWasCalled { get; set; } @@ -306,14 +306,14 @@ public class IdentityServerPipeline private async Task ReadConsentMessage(HttpContext ctx) { var interaction = ctx.RequestServices.GetRequiredService(); - ConsentRequest = await interaction.GetAuthorizationContextAsync(ctx.Request.Query["returnUrl"].FirstOrDefault()); + ConsentRequest = await interaction.GetAuthorizationContextAsync(ctx.Request.Query["returnUrl"].FirstOrDefault(), ctx.RequestAborted); } private async Task CreateConsentResponse(HttpContext ctx) { if (ConsentRequest != null && ConsentResponse != null) { var interaction = ctx.RequestServices.GetRequiredService(); - await interaction.GrantConsentAsync(ConsentRequest, ConsentResponse); + await interaction.GrantConsentAsync(ConsentRequest, ConsentResponse, ctx.RequestAborted); ConsentResponse = null; var url = ctx.Request.Query[Options.UserInteraction.ConsentReturnUrlParameter].FirstOrDefault(); @@ -331,7 +331,7 @@ public class IdentityServerPipeline { CustomWasCalled = true; var interaction = ctx.RequestServices.GetRequiredService(); - CustomRequest = await interaction.GetAuthorizationContextAsync(ctx.Request.Query[Options.UserInteraction.ConsentReturnUrlParameter].FirstOrDefault()); + CustomRequest = await interaction.GetAuthorizationContextAsync(ctx.Request.Query[Options.UserInteraction.ConsentReturnUrlParameter].FirstOrDefault(), ctx.RequestAborted); } public bool ErrorWasCalled { get; set; } @@ -347,7 +347,7 @@ public class IdentityServerPipeline private async Task ReadErrorMessage(HttpContext ctx) { var interaction = ctx.RequestServices.GetRequiredService(); - ErrorMessage = await interaction.GetErrorContextAsync(ctx.Request.Query["errorId"].FirstOrDefault()); + ErrorMessage = await interaction.GetErrorContextAsync(ctx.Request.Query["errorId"].FirstOrDefault(), ctx.RequestAborted); } /* helpers */ diff --git a/identity-server/test/IdentityServer.IntegrationTests/Endpoints/Token/CibaTokenEndpointTests.cs b/identity-server/test/IdentityServer.IntegrationTests/Endpoints/Token/CibaTokenEndpointTests.cs index 5a6581610..4270118c6 100644 --- a/identity-server/test/IdentityServer.IntegrationTests/Endpoints/Token/CibaTokenEndpointTests.cs +++ b/identity-server/test/IdentityServer.IntegrationTests/Endpoints/Token/CibaTokenEndpointTests.cs @@ -157,7 +157,7 @@ public class CibaTokenEndpointTests IdentityProvider = IdentityServerConstants.LocalIdentityProvider, } .CreatePrincipal() - }); + }, _ct); // token request @@ -264,7 +264,7 @@ public class CibaTokenEndpointTests IdentityProvider = IdentityServerConstants.LocalIdentityProvider, } .CreatePrincipal() - }); + }, _ct); // token request @@ -327,7 +327,7 @@ public class CibaTokenEndpointTests IdentityProvider = IdentityServerConstants.LocalIdentityProvider, } .CreatePrincipal() - }); + }, _ct); // token request @@ -390,7 +390,7 @@ public class CibaTokenEndpointTests IdentityProvider = IdentityServerConstants.LocalIdentityProvider, } .CreatePrincipal() - }); + }, _ct); // token request @@ -453,7 +453,7 @@ public class CibaTokenEndpointTests IdentityProvider = IdentityServerConstants.LocalIdentityProvider, } .CreatePrincipal() - }); + }, _ct); // token request @@ -522,7 +522,7 @@ public class CibaTokenEndpointTests IdentityProvider = IdentityServerConstants.LocalIdentityProvider, } .CreatePrincipal() - }); + }, _ct); // token request diff --git a/identity-server/test/IdentityServer.IntegrationTests/Hosting/DynamicProvidersTests.cs b/identity-server/test/IdentityServer.IntegrationTests/Hosting/DynamicProvidersTests.cs index dc376789c..f50302d85 100644 --- a/identity-server/test/IdentityServer.IntegrationTests/Hosting/DynamicProvidersTests.cs +++ b/identity-server/test/IdentityServer.IntegrationTests/Hosting/DynamicProvidersTests.cs @@ -86,7 +86,7 @@ public class DynamicProvidersTests app.MapGet("/account/logout", async ctx => { var isis = ctx.RequestServices.GetRequiredService(); - var logoutCtx = await isis.GetLogoutContextAsync(ctx.Request.Query["logoutId"]); + var logoutCtx = await isis.GetLogoutContextAsync(ctx.Request.Query["logoutId"], ctx.RequestAborted); Idp1FrontChannelLogoutUri = logoutCtx.SignOutIFrameUrl; await ctx.SignOutAsync(); }); diff --git a/identity-server/test/IdentityServer.UnitTests/Services/Default/DefaultBackchannelAuthenticationInteractionServiceTests.cs b/identity-server/test/IdentityServer.UnitTests/Services/Default/DefaultBackchannelAuthenticationInteractionServiceTests.cs index 2ac108f85..5c1796c86 100644 --- a/identity-server/test/IdentityServer.UnitTests/Services/Default/DefaultBackchannelAuthenticationInteractionServiceTests.cs +++ b/identity-server/test/IdentityServer.UnitTests/Services/Default/DefaultBackchannelAuthenticationInteractionServiceTests.cs @@ -108,7 +108,7 @@ public class DefaultBackchannelAuthenticationInteractionServiceTests AdditionalClaims = { new Claim("foo", "bar") }, AuthenticationMethods = { "phone", "pin" } }.CreatePrincipal() - }); + }, _ct); var item = _mockStore.Items[requestId]; item.IsComplete.ShouldBeTrue(); @@ -136,7 +136,7 @@ public class DefaultBackchannelAuthenticationInteractionServiceTests }; var requestId = await _mockStore.CreateRequestAsync(req, _ct); - var f = async () => await _subject.CompleteLoginRequestAsync(null); + var f = async () => await _subject.CompleteLoginRequestAsync(null, _ct); await f.ShouldThrowAsync(); } @@ -165,7 +165,7 @@ public class DefaultBackchannelAuthenticationInteractionServiceTests AdditionalClaims = { new Claim("foo", "bar") }, AuthenticationMethods = { "phone", "pin" } }.CreatePrincipal() - }); + }, _ct); var exception = await f.ShouldThrowAsync(); exception.Message.ShouldBe("More scopes consented than originally requested."); } @@ -195,7 +195,7 @@ public class DefaultBackchannelAuthenticationInteractionServiceTests AdditionalClaims = { new Claim("foo", "bar") }, AuthenticationMethods = { "phone", "pin" } }.CreatePrincipal() - }); + }, _ct); var exception = await f.ShouldThrowAsync(); exception.Message.ShouldBe("User's subject id: 'invalid' does not match subject id for backchannel authentication request: '123'."); } @@ -224,7 +224,7 @@ public class DefaultBackchannelAuthenticationInteractionServiceTests // AdditionalClaims = { new Claim("foo", "bar") }, // AuthenticationMethods = { "phone", "pin" } //}.CreatePrincipal() - }); + }, _ct); var exception = await f.ShouldThrowAsync(); exception.Message.ShouldBe("Invalid subject."); } @@ -253,7 +253,7 @@ public class DefaultBackchannelAuthenticationInteractionServiceTests AdditionalClaims = { new Claim("foo", "bar") }, AuthenticationMethods = { "phone", "pin" } }.CreatePrincipal() - }); + }, _ct); var exception = await f.ShouldThrowAsync(); exception.Message.ShouldBe("Invalid backchannel authentication request id."); } @@ -286,7 +286,7 @@ public class DefaultBackchannelAuthenticationInteractionServiceTests ScopesValuesConsented = new string[] { "scope1", "scope2" }, SessionId = "ignored", //Subject = - }); + }, _ct); var item = _mockStore.Items[requestId]; item.SessionId.ShouldBe("session id"); @@ -324,7 +324,7 @@ public class DefaultBackchannelAuthenticationInteractionServiceTests AdditionalClaims = { new Claim("foo", "bar") }, AuthenticationMethods = { "phone", "pin" } }.CreatePrincipal() - }); + }, _ct); var item = _mockStore.Items[requestId]; item.Subject.HasClaim("idp", "local").ShouldBeTrue(); diff --git a/identity-server/test/IdentityServer.UnitTests/Services/Default/DefaultIdentityServerInteractionServiceTests.cs b/identity-server/test/IdentityServer.UnitTests/Services/Default/DefaultIdentityServerInteractionServiceTests.cs index 57a2d3add..ce8b04568 100644 --- a/identity-server/test/IdentityServer.UnitTests/Services/Default/DefaultIdentityServerInteractionServiceTests.cs +++ b/identity-server/test/IdentityServer.UnitTests/Services/Default/DefaultIdentityServerInteractionServiceTests.cs @@ -16,6 +16,8 @@ namespace UnitTests.Services.Default; public class DefaultIdentityServerInteractionServiceTests { + private readonly CT _ct = TestContext.Current.CancellationToken; + private DefaultIdentityServerInteractionService _subject; private IdentityServerOptions _options = new IdentityServerOptions(); @@ -62,7 +64,7 @@ public class DefaultIdentityServerInteractionServiceTests _mockUserSession.SessionId = null; _mockLogoutMessageStore.Messages.Add("id", new Message(new LogoutMessage() { SessionId = "session" })); - var context = await _subject.GetLogoutContextAsync("id"); + var context = await _subject.GetLogoutContextAsync("id", _ct); context.SignOutIFrameUrl.ShouldBeNull(); } @@ -77,7 +79,7 @@ public class DefaultIdentityServerInteractionServiceTests _mockUserSession.SessionId = "session"; _mockUserSession.User = new IdentityServerUser("123").CreatePrincipal(); - var context = await _subject.GetLogoutContextAsync(null); + var context = await _subject.GetLogoutContextAsync(null, _ct); context.SignOutIFrameUrl.ShouldBeNull(); } @@ -94,7 +96,7 @@ public class DefaultIdentityServerInteractionServiceTests _mockUserSession.SessionId = "session"; _mockUserSession.User = new IdentityServerUser("123").CreatePrincipal(); - var context = await _subject.GetLogoutContextAsync(null); + var context = await _subject.GetLogoutContextAsync(null, _ct); context.SignOutIFrameUrl.ShouldNotBeNull(); } @@ -105,7 +107,7 @@ public class DefaultIdentityServerInteractionServiceTests _mockUserSession.SessionId = null; _mockLogoutMessageStore.Messages.Add("id", new Message(new LogoutMessage())); - var context = await _subject.GetLogoutContextAsync("id"); + var context = await _subject.GetLogoutContextAsync("id", _ct); context.SignOutIFrameUrl.ShouldBeNull(); } @@ -113,7 +115,7 @@ public class DefaultIdentityServerInteractionServiceTests [Fact] public async Task CreateLogoutContextAsync_without_session_should_not_create_session() { - var context = await _subject.CreateLogoutContextAsync(); + var context = await _subject.CreateLogoutContextAsync(_ct); context.ShouldBeNull(); _mockLogoutMessageStore.Messages.ShouldBeEmpty(); @@ -126,7 +128,7 @@ public class DefaultIdentityServerInteractionServiceTests _mockUserSession.User = new IdentityServerUser("123").CreatePrincipal(); _mockUserSession.SessionId = "session"; - var context = await _subject.CreateLogoutContextAsync(); + var context = await _subject.CreateLogoutContextAsync(_ct); context.ShouldNotBeNull(); _mockLogoutMessageStore.Messages.ShouldNotBeEmpty(); @@ -138,6 +140,7 @@ public class DefaultIdentityServerInteractionServiceTests var act = () => _subject.GrantConsentAsync( new AuthorizationRequest(), new ConsentResponse() { ScopesValuesConsented = new[] { "openid" } }, + _ct, null); var exception = await act.ShouldThrowAsync(); @@ -152,7 +155,7 @@ public class DefaultIdentityServerInteractionServiceTests Client = new Client { ClientId = "client" }, ValidatedResources = _resourceValidationResult }; - await _subject.GrantConsentAsync(req, new ConsentResponse { Error = AuthorizationError.AccessDenied }, null); + await _subject.GrantConsentAsync(req, new ConsentResponse { Error = AuthorizationError.AccessDenied }, _ct, null); } [Fact] @@ -165,7 +168,7 @@ public class DefaultIdentityServerInteractionServiceTests Client = new Client { ClientId = "client" }, ValidatedResources = _resourceValidationResult }; - await _subject.GrantConsentAsync(req, new ConsentResponse(), null); + await _subject.GrantConsentAsync(req, new ConsentResponse(), _ct, null); _mockConsentStore.Messages.ShouldNotBeEmpty(); var consentRequest = new ConsentRequest(req, "bob"); diff --git a/identity-server/test/IdentityServer.UnitTests/Validation/IsLocalUrlTests.cs b/identity-server/test/IdentityServer.UnitTests/Validation/IsLocalUrlTests.cs index 1ac1540cb..02d3f2d99 100644 --- a/identity-server/test/IdentityServer.UnitTests/Validation/IsLocalUrlTests.cs +++ b/identity-server/test/IdentityServer.UnitTests/Validation/IsLocalUrlTests.cs @@ -67,7 +67,7 @@ public class IsLocalUrlTests { var interactionService = new DefaultIdentityServerInteractionService(null, null, null, null, null, null, null, GetReturnUrlParser(), new LoggerFactory().CreateLogger()); - var actual = await interactionService.GetAuthorizationContextAsync(returnUrl); + var actual = await interactionService.GetAuthorizationContextAsync(returnUrl, _ct); if (expected) { actual.ShouldNotBeNull();