From 3bdfc208954fcc116baac416717b0f9c6f8f3867 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A9vin=20Chalet?= Date: Thu, 20 Apr 2023 13:44:02 +0200 Subject: [PATCH] Catch exceptions thrown during the retrieval of the remote server configuration and generalize the use of OpenIddictHelpers.IsFatal() --- .../Helpers/OpenIddictHelpers.cs | 18 ++- .../OpenIddictResources.resx | 6 + .../OpenIddictClientAspNetCoreHandlers.cs | 3 +- .../OpenIddict.Client.DataProtection.csproj | 4 + ...ClientDataProtectionHandlers.Protection.cs | 3 +- .../OpenIddictClientOwinHandlers.cs | 5 +- ...penIddictClientSystemIntegrationHelpers.cs | 3 +- ...dictClientSystemIntegrationHttpListener.cs | 2 +- .../OpenIddictClientSystemNetHttpHandlers.cs | 4 +- .../OpenIddictClientDispatcher.cs | 3 +- .../OpenIddictClientHandlers.cs | 104 ++++++++++++++---- .../Managers/OpenIddictApplicationManager.cs | 2 +- .../OpenIddictAuthorizationManager.cs | 2 +- .../Managers/OpenIddictTokenManager.cs | 6 +- ...enIddictEntityFrameworkApplicationStore.cs | 3 +- ...IddictEntityFrameworkAuthorizationStore.cs | 6 +- .../OpenIddictEntityFrameworkTokenStore.cs | 5 +- ...dictEntityFrameworkCoreApplicationStore.cs | 3 +- ...ctEntityFrameworkCoreAuthorizationStore.cs | 6 +- ...OpenIddictEntityFrameworkCoreTokenStore.cs | 5 +- .../OpenIddict.Quartz.csproj | 4 + src/OpenIddict.Quartz/OpenIddictQuartzJob.cs | 29 ++--- .../OpenIddictServerAspNetCoreHandlers.cs | 3 +- .../OpenIddict.Server.DataProtection.csproj | 4 + ...ServerDataProtectionHandlers.Protection.cs | 3 +- .../OpenIddictServerOwinHandlers.cs | 5 +- .../OpenIddictServerDispatcher.cs | 3 +- ...penIddict.Validation.DataProtection.csproj | 4 + ...dationDataProtectionHandlers.Protection.cs | 3 +- .../OpenIddict.Validation.Owin.csproj | 4 + .../OpenIddictValidationOwinHandlers.cs | 3 +- ...enIddictValidationSystemNetHttpHandlers.cs | 4 +- .../OpenIddictValidationDispatcher.cs | 3 +- .../OpenIddictValidationHandlers.cs | 26 ++++- .../OpenIddictValidationIntegrationTests.cs | 57 +++++++--- 35 files changed, 252 insertions(+), 96 deletions(-) diff --git a/shared/OpenIddict.Extensions/Helpers/OpenIddictHelpers.cs b/shared/OpenIddict.Extensions/Helpers/OpenIddictHelpers.cs index ca70d965..846cab31 100644 --- a/shared/OpenIddict.Extensions/Helpers/OpenIddictHelpers.cs +++ b/shared/OpenIddict.Extensions/Helpers/OpenIddictHelpers.cs @@ -1,4 +1,5 @@ -using System.Data; +using System.Collections.ObjectModel; +using System.Data; using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.Runtime.CompilerServices; @@ -144,11 +145,24 @@ internal static class OpenIddictHelpers ThreadAbortException => true, OutOfMemoryException and not InsufficientMemoryException => true, - AggregateException { InnerExceptions: var exceptions } => exceptions.Any(IsFatal), + AggregateException { InnerExceptions: var exceptions } => IsAnyFatal(exceptions), Exception { InnerException: Exception inner } => IsFatal(inner), _ => false }; + + static bool IsAnyFatal(ReadOnlyCollection exceptions) + { + for (var index = 0; index < exceptions.Count; index++) + { + if (IsFatal(exceptions[index])) + { + return true; + } + } + + return false; + } } #if !SUPPORTS_TOHASHSET_LINQ_EXTENSION diff --git a/src/OpenIddict.Abstractions/OpenIddictResources.resx b/src/OpenIddict.Abstractions/OpenIddictResources.resx index d84a1222..215d01de 100644 --- a/src/OpenIddict.Abstractions/OpenIddictResources.resx +++ b/src/OpenIddict.Abstractions/OpenIddictResources.resx @@ -2026,6 +2026,9 @@ To apply post-logout redirection responses, create a class implementing 'IOpenId The '{0}' parameter returned in the device authorization response is not valid absolute URI. + + The remote authorization server is currently unavailable or returned an invalid configuration. + The '{0}' parameter shouldn't be null or empty at this point. @@ -2704,6 +2707,9 @@ This may indicate that the hashed entry is corrupted or malformed. The device authorization response returned by {Uri} was successfully extracted: {Response}. + + An error occurred while retrieving the configuration of the remote authorization server. + https://documentation.openiddict.com/errors/{0} diff --git a/src/OpenIddict.Client.AspNetCore/OpenIddictClientAspNetCoreHandlers.cs b/src/OpenIddict.Client.AspNetCore/OpenIddictClientAspNetCoreHandlers.cs index ec56dcf2..4ecf99c6 100644 --- a/src/OpenIddict.Client.AspNetCore/OpenIddictClientAspNetCoreHandlers.cs +++ b/src/OpenIddict.Client.AspNetCore/OpenIddictClientAspNetCoreHandlers.cs @@ -18,6 +18,7 @@ using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using Microsoft.IdentityModel.Tokens; using Microsoft.Net.Http.Headers; +using OpenIddict.Extensions; using Properties = OpenIddict.Client.AspNetCore.OpenIddictClientAspNetCoreConstants.Properties; #if SUPPORTS_JSON_NODES @@ -469,7 +470,7 @@ public static partial class OpenIddictClientAspNetCoreHandlers context.RequestForgeryProtection = Encoding.UTF8.GetString(payload, index: 5, length); } - catch + catch (Exception exception) when (!OpenIddictHelpers.IsFatal(exception)) { context.Reject( error: Errors.InvalidRequest, diff --git a/src/OpenIddict.Client.DataProtection/OpenIddict.Client.DataProtection.csproj b/src/OpenIddict.Client.DataProtection/OpenIddict.Client.DataProtection.csproj index 19da7821..6ededad8 100644 --- a/src/OpenIddict.Client.DataProtection/OpenIddict.Client.DataProtection.csproj +++ b/src/OpenIddict.Client.DataProtection/OpenIddict.Client.DataProtection.csproj @@ -32,6 +32,10 @@ + + + + diff --git a/src/OpenIddict.Client.DataProtection/OpenIddictClientDataProtectionHandlers.Protection.cs b/src/OpenIddict.Client.DataProtection/OpenIddictClientDataProtectionHandlers.Protection.cs index 9bb5b160..c89dc395 100644 --- a/src/OpenIddict.Client.DataProtection/OpenIddictClientDataProtectionHandlers.Protection.cs +++ b/src/OpenIddict.Client.DataProtection/OpenIddictClientDataProtectionHandlers.Protection.cs @@ -10,6 +10,7 @@ using Microsoft.AspNetCore.DataProtection; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using Microsoft.IdentityModel.Tokens; +using OpenIddict.Extensions; using static OpenIddict.Client.DataProtection.OpenIddictClientDataProtectionConstants.Purposes; using static OpenIddict.Client.OpenIddictClientHandlers.Protection; using Schemes = OpenIddict.Client.DataProtection.OpenIddictClientDataProtectionConstants.Purposes.Schemes; @@ -124,7 +125,7 @@ public static partial class OpenIddictClientDataProtectionHandlers return _options.CurrentValue.Formatter.ReadToken(reader)?.SetTokenType(type); } - catch (Exception exception) + catch (Exception exception) when (!OpenIddictHelpers.IsFatal(exception)) { context.Logger.LogTrace(exception, SR.GetResourceString(SR.ID6153), context.Token); diff --git a/src/OpenIddict.Client.Owin/OpenIddictClientOwinHandlers.cs b/src/OpenIddict.Client.Owin/OpenIddictClientOwinHandlers.cs index 1008f28a..6f91ddeb 100644 --- a/src/OpenIddict.Client.Owin/OpenIddictClientOwinHandlers.cs +++ b/src/OpenIddict.Client.Owin/OpenIddictClientOwinHandlers.cs @@ -15,6 +15,7 @@ using System.Text.Json; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using Microsoft.IdentityModel.Tokens; +using OpenIddict.Extensions; using Owin; using static OpenIddict.Client.Owin.OpenIddictClientOwinConstants; using Properties = OpenIddict.Client.Owin.OpenIddictClientOwinConstants.Properties; @@ -472,7 +473,7 @@ public static partial class OpenIddictClientOwinHandlers context.RequestForgeryProtection = Encoding.UTF8.GetString(payload, index: 5, length); } - catch + catch (Exception exception) when (!OpenIddictHelpers.IsFatal(exception)) { context.Reject( error: Errors.InvalidRequest, @@ -1237,7 +1238,7 @@ public static partial class OpenIddictClientOwinHandlers context.Response.SuppressFormsAuthenticationRedirect = true; } - catch + catch (Exception exception) when (!OpenIddictHelpers.IsFatal(exception)) { } } diff --git a/src/OpenIddict.Client.SystemIntegration/OpenIddictClientSystemIntegrationHelpers.cs b/src/OpenIddict.Client.SystemIntegration/OpenIddictClientSystemIntegrationHelpers.cs index ccd7d799..dab9e004 100644 --- a/src/OpenIddict.Client.SystemIntegration/OpenIddictClientSystemIntegrationHelpers.cs +++ b/src/OpenIddict.Client.SystemIntegration/OpenIddictClientSystemIntegrationHelpers.cs @@ -11,6 +11,7 @@ using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Runtime.Versioning; using System.Security.Principal; +using OpenIddict.Extensions; #if SUPPORTS_WINDOWS_RUNTIME using Windows.ApplicationModel.Activation; @@ -163,7 +164,7 @@ public static class OpenIddictClientSystemIntegrationHelpers ProtocolActivatedEventArgs args ? args.Uri : null; } - catch + catch (Exception exception) when (!OpenIddictHelpers.IsFatal(exception)) { return null; } diff --git a/src/OpenIddict.Client.SystemIntegration/OpenIddictClientSystemIntegrationHttpListener.cs b/src/OpenIddict.Client.SystemIntegration/OpenIddictClientSystemIntegrationHttpListener.cs index 7ae60ff5..29c54c90 100644 --- a/src/OpenIddict.Client.SystemIntegration/OpenIddictClientSystemIntegrationHttpListener.cs +++ b/src/OpenIddict.Client.SystemIntegration/OpenIddictClientSystemIntegrationHttpListener.cs @@ -195,7 +195,7 @@ public sealed class OpenIddictClientSystemIntegrationHttpListener : BackgroundSe exceptions.Push(new InvalidOperationException(SR.FormatID0384(port), exception)); } - catch (Exception exception) + catch (Exception exception) when (!OpenIddictHelpers.IsFatal(exception)) { listener.Close(); diff --git a/src/OpenIddict.Client.SystemNetHttp/OpenIddictClientSystemNetHttpHandlers.cs b/src/OpenIddict.Client.SystemNetHttp/OpenIddictClientSystemNetHttpHandlers.cs index c25d7cff..5b4fcf33 100644 --- a/src/OpenIddict.Client.SystemNetHttp/OpenIddictClientSystemNetHttpHandlers.cs +++ b/src/OpenIddict.Client.SystemNetHttp/OpenIddictClientSystemNetHttpHandlers.cs @@ -448,7 +448,7 @@ public static partial class OpenIddictClientSystemNetHttpHandlers // If an exception is thrown at this stage, this likely means a persistent network error occurred. // In this case, log the error details and return a generic error to stop processing the event. - catch (Exception exception) + catch (Exception exception) when (!OpenIddictHelpers.IsFatal(exception)) { context.Logger.LogError(exception, SR.GetResourceString(SR.ID6182)); @@ -698,7 +698,7 @@ public static partial class OpenIddictClientSystemNetHttpHandlers // If an exception is thrown at this stage, this likely means the returned response was not a valid // JSON response or was not correctly formatted as a JSON object. This typically happens when // a server error occurs while the JSON response is being generated and returned to the client. - catch (Exception exception) + catch (Exception exception) when (!OpenIddictHelpers.IsFatal(exception)) { context.Logger.LogError(exception, SR.GetResourceString(SR.ID6183), await response.Content.ReadAsStringAsync()); diff --git a/src/OpenIddict.Client/OpenIddictClientDispatcher.cs b/src/OpenIddict.Client/OpenIddictClientDispatcher.cs index 4b3809d5..e6257c8a 100644 --- a/src/OpenIddict.Client/OpenIddictClientDispatcher.cs +++ b/src/OpenIddict.Client/OpenIddictClientDispatcher.cs @@ -7,6 +7,7 @@ using System.ComponentModel; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; +using OpenIddict.Extensions; namespace OpenIddict.Client; @@ -48,7 +49,7 @@ public sealed class OpenIddictClientDispatcher : IOpenIddictClientDispatcher await handler.HandleAsync(context); } - catch (Exception exception) when (_logger.IsEnabled(LogLevel.Debug)) + catch (Exception exception) when (!OpenIddictHelpers.IsFatal(exception) && _logger.IsEnabled(LogLevel.Debug)) { _logger.LogDebug(exception, SR.GetResourceString(SR.ID6132), handler.GetType().FullName, typeof(TContext).FullName); diff --git a/src/OpenIddict.Client/OpenIddictClientHandlers.cs b/src/OpenIddict.Client/OpenIddictClientHandlers.cs index 2c8af8aa..81617fda 100644 --- a/src/OpenIddict.Client/OpenIddictClientHandlers.cs +++ b/src/OpenIddict.Client/OpenIddictClientHandlers.cs @@ -386,11 +386,27 @@ public static partial class OpenIddictClientHandlers // be used to authenticate users. In this case, throw an exception to abort the flow. context.Registration ??= await _service.GetClientRegistrationAsync(context.Issuer, context.CancellationToken); - // Resolve and attach the server configuration to the context if none has been set already. - context.Configuration ??= await context.Registration.ConfigurationManager - .GetConfigurationAsync(context.CancellationToken) - .WaitAsync(context.CancellationToken) ?? - throw new InvalidOperationException(SR.GetResourceString(SR.ID0140)); + try + { + // Resolve and attach the server configuration to the context if none has been set already. + context.Configuration ??= await context.Registration.ConfigurationManager + .GetConfigurationAsync(context.CancellationToken) + .WaitAsync(context.CancellationToken) ?? + throw new InvalidOperationException(SR.GetResourceString(SR.ID0140)); + } + + catch (Exception exception) when (!OpenIddictHelpers.IsFatal(exception) && + exception is not OperationCanceledException) + { + context.Logger.LogError(exception, SR.GetResourceString(SR.ID6219)); + + context.Reject( + error: Errors.ServerError, + description: SR.GetResourceString(SR.ID2170), + uri: SR.FormatID8000(SR.ID2170)); + + return; + } // Ensure the selected grant type, if explicitly set, is listed as supported in the configuration. if (!string.IsNullOrEmpty(context.GrantType) && @@ -982,11 +998,27 @@ public static partial class OpenIddictClientHandlers throw new InvalidOperationException(SR.GetResourceString(SR.ID0349)); } - // Resolve and attach the server configuration to the context. - context.Configuration = await context.Registration.ConfigurationManager - .GetConfigurationAsync(context.CancellationToken) - .WaitAsync(context.CancellationToken) ?? - throw new InvalidOperationException(SR.GetResourceString(SR.ID0140)); + try + { + // Resolve and attach the server configuration to the context. + context.Configuration = await context.Registration.ConfigurationManager + .GetConfigurationAsync(context.CancellationToken) + .WaitAsync(context.CancellationToken) ?? + throw new InvalidOperationException(SR.GetResourceString(SR.ID0140)); + } + + catch (Exception exception) when (!OpenIddictHelpers.IsFatal(exception) && + exception is not OperationCanceledException) + { + context.Logger.LogError(exception, SR.GetResourceString(SR.ID6219)); + + context.Reject( + error: Errors.ServerError, + description: SR.GetResourceString(SR.ID2170), + uri: SR.FormatID8000(SR.ID2170)); + + return; + } } } @@ -3944,11 +3976,27 @@ public static partial class OpenIddictClientHandlers // be used to authenticate users. In this case, throw an exception to abort the flow. context.Registration ??= await _service.GetClientRegistrationAsync(context.Issuer, context.CancellationToken); - // Resolve and attach the server configuration to the context if none has been set already. - context.Configuration ??= await context.Registration.ConfigurationManager - .GetConfigurationAsync(context.CancellationToken) - .WaitAsync(context.CancellationToken) ?? - throw new InvalidOperationException(SR.GetResourceString(SR.ID0140)); + try + { + // Resolve and attach the server configuration to the context if none has been set already. + context.Configuration ??= await context.Registration.ConfigurationManager + .GetConfigurationAsync(context.CancellationToken) + .WaitAsync(context.CancellationToken) ?? + throw new InvalidOperationException(SR.GetResourceString(SR.ID0140)); + } + + catch (Exception exception) when (!OpenIddictHelpers.IsFatal(exception) && + exception is not OperationCanceledException) + { + context.Logger.LogError(exception, SR.GetResourceString(SR.ID6219)); + + context.Reject( + error: Errors.ServerError, + description: SR.GetResourceString(SR.ID2170), + uri: SR.FormatID8000(SR.ID2170)); + + return; + } } } @@ -5532,11 +5580,27 @@ public static partial class OpenIddictClientHandlers // be used to authenticate users. In this case, throw an exception to abort the flow. context.Registration ??= await _service.GetClientRegistrationAsync(context.Issuer, context.CancellationToken); - // Resolve and attach the server configuration to the context if none has been set already. - context.Configuration ??= await context.Registration.ConfigurationManager - .GetConfigurationAsync(context.CancellationToken) - .WaitAsync(context.CancellationToken) ?? - throw new InvalidOperationException(SR.GetResourceString(SR.ID0140)); + try + { + // Resolve and attach the server configuration to the context if none has been set already. + context.Configuration ??= await context.Registration.ConfigurationManager + .GetConfigurationAsync(context.CancellationToken) + .WaitAsync(context.CancellationToken) ?? + throw new InvalidOperationException(SR.GetResourceString(SR.ID0140)); + } + + catch (Exception exception) when (!OpenIddictHelpers.IsFatal(exception) && + exception is not OperationCanceledException) + { + context.Logger.LogError(exception, SR.GetResourceString(SR.ID6219)); + + context.Reject( + error: Errors.ServerError, + description: SR.GetResourceString(SR.ID2170), + uri: SR.FormatID8000(SR.ID2170)); + + return; + } } } diff --git a/src/OpenIddict.Core/Managers/OpenIddictApplicationManager.cs b/src/OpenIddict.Core/Managers/OpenIddictApplicationManager.cs index de7375f3..880dc297 100644 --- a/src/OpenIddict.Core/Managers/OpenIddictApplicationManager.cs +++ b/src/OpenIddict.Core/Managers/OpenIddictApplicationManager.cs @@ -1452,7 +1452,7 @@ public class OpenIddictApplicationManager : IOpenIddictApplication return new(VerifyHashedSecret(comparand, secret)); } - catch (Exception exception) + catch (Exception exception) when (!OpenIddictHelpers.IsFatal(exception)) { Logger.LogWarning(exception, SR.GetResourceString(SR.ID6163)); diff --git a/src/OpenIddict.Core/Managers/OpenIddictAuthorizationManager.cs b/src/OpenIddict.Core/Managers/OpenIddictAuthorizationManager.cs index e6b9c5a1..d2e8719c 100644 --- a/src/OpenIddict.Core/Managers/OpenIddictAuthorizationManager.cs +++ b/src/OpenIddict.Core/Managers/OpenIddictAuthorizationManager.cs @@ -1063,7 +1063,7 @@ public class OpenIddictAuthorizationManager : IOpenIddictAuthori return false; } - catch (Exception exception) + catch (Exception exception) when (!OpenIddictHelpers.IsFatal(exception)) { Logger.LogWarning(exception, SR.GetResourceString(SR.ID6166), await Store.GetIdAsync(authorization, cancellationToken)); diff --git a/src/OpenIddict.Core/Managers/OpenIddictTokenManager.cs b/src/OpenIddict.Core/Managers/OpenIddictTokenManager.cs index 4140b7c8..3c4b3b27 100644 --- a/src/OpenIddict.Core/Managers/OpenIddictTokenManager.cs +++ b/src/OpenIddict.Core/Managers/OpenIddictTokenManager.cs @@ -1094,7 +1094,7 @@ public class OpenIddictTokenManager : IOpenIddictTokenManager where TTok return false; } - catch (Exception exception) + catch (Exception exception) when (!OpenIddictHelpers.IsFatal(exception)) { Logger.LogWarning(exception, SR.GetResourceString(SR.ID6170), await Store.GetIdAsync(token, cancellationToken)); @@ -1133,7 +1133,7 @@ public class OpenIddictTokenManager : IOpenIddictTokenManager where TTok return false; } - catch (Exception exception) + catch (Exception exception) when (!OpenIddictHelpers.IsFatal(exception)) { Logger.LogWarning(exception, SR.GetResourceString(SR.ID6173), await Store.GetIdAsync(token, cancellationToken)); @@ -1172,7 +1172,7 @@ public class OpenIddictTokenManager : IOpenIddictTokenManager where TTok return false; } - catch (Exception exception) + catch (Exception exception) when (!OpenIddictHelpers.IsFatal(exception)) { Logger.LogWarning(exception, SR.GetResourceString(SR.ID6176), await Store.GetIdAsync(token, cancellationToken)); diff --git a/src/OpenIddict.EntityFramework/Stores/OpenIddictEntityFrameworkApplicationStore.cs b/src/OpenIddict.EntityFramework/Stores/OpenIddictEntityFrameworkApplicationStore.cs index 508264d0..35dda0d9 100644 --- a/src/OpenIddict.EntityFramework/Stores/OpenIddictEntityFrameworkApplicationStore.cs +++ b/src/OpenIddict.EntityFramework/Stores/OpenIddictEntityFrameworkApplicationStore.cs @@ -17,6 +17,7 @@ using System.Text.Json; using Microsoft.Extensions.Caching.Memory; using Microsoft.Extensions.Options; using OpenIddict.EntityFramework.Models; +using OpenIddict.Extensions; using static OpenIddict.Abstractions.OpenIddictExceptions; namespace OpenIddict.EntityFramework; @@ -138,7 +139,7 @@ public class OpenIddictEntityFrameworkApplicationStore(capacity: 1); exceptions.Add(exception); diff --git a/src/OpenIddict.EntityFramework/Stores/OpenIddictEntityFrameworkTokenStore.cs b/src/OpenIddict.EntityFramework/Stores/OpenIddictEntityFrameworkTokenStore.cs index 0c4d8e0d..395b76e6 100644 --- a/src/OpenIddict.EntityFramework/Stores/OpenIddictEntityFrameworkTokenStore.cs +++ b/src/OpenIddict.EntityFramework/Stores/OpenIddictEntityFrameworkTokenStore.cs @@ -14,6 +14,7 @@ using System.Text.Json; using Microsoft.Extensions.Caching.Memory; using Microsoft.Extensions.Options; using OpenIddict.EntityFramework.Models; +using OpenIddict.Extensions; using static OpenIddict.Abstractions.OpenIddictExceptions; namespace OpenIddict.EntityFramework; @@ -591,7 +592,7 @@ public class OpenIddictEntityFrameworkTokenStore(capacity: 1); exceptions.Add(exception); diff --git a/src/OpenIddict.EntityFrameworkCore/Stores/OpenIddictEntityFrameworkCoreApplicationStore.cs b/src/OpenIddict.EntityFrameworkCore/Stores/OpenIddictEntityFrameworkCoreApplicationStore.cs index 4dbfd6d5..0ab032ed 100644 --- a/src/OpenIddict.EntityFrameworkCore/Stores/OpenIddictEntityFrameworkCoreApplicationStore.cs +++ b/src/OpenIddict.EntityFrameworkCore/Stores/OpenIddictEntityFrameworkCoreApplicationStore.cs @@ -16,6 +16,7 @@ using System.Text.Json; using Microsoft.Extensions.Caching.Memory; using Microsoft.Extensions.Options; using OpenIddict.EntityFrameworkCore.Models; +using OpenIddict.Extensions; using static OpenIddict.Abstractions.OpenIddictExceptions; namespace OpenIddict.EntityFrameworkCore; @@ -165,7 +166,7 @@ public class OpenIddictEntityFrameworkCoreApplicationStore(capacity: 1); exceptions.Add(exception); diff --git a/src/OpenIddict.EntityFrameworkCore/Stores/OpenIddictEntityFrameworkCoreTokenStore.cs b/src/OpenIddict.EntityFrameworkCore/Stores/OpenIddictEntityFrameworkCoreTokenStore.cs index cd33881d..31db2cc2 100644 --- a/src/OpenIddict.EntityFrameworkCore/Stores/OpenIddictEntityFrameworkCoreTokenStore.cs +++ b/src/OpenIddict.EntityFrameworkCore/Stores/OpenIddictEntityFrameworkCoreTokenStore.cs @@ -13,6 +13,7 @@ using System.Text.Json; using Microsoft.Extensions.Caching.Memory; using Microsoft.Extensions.Options; using OpenIddict.EntityFrameworkCore.Models; +using OpenIddict.Extensions; using static OpenIddict.Abstractions.OpenIddictExceptions; namespace OpenIddict.EntityFrameworkCore; @@ -648,7 +649,7 @@ public class OpenIddictEntityFrameworkCoreTokenStore(capacity: 1); exceptions.Add(exception); diff --git a/src/OpenIddict.Quartz/OpenIddict.Quartz.csproj b/src/OpenIddict.Quartz/OpenIddict.Quartz.csproj index 08477ab6..5f7d974d 100644 --- a/src/OpenIddict.Quartz/OpenIddict.Quartz.csproj +++ b/src/OpenIddict.Quartz/OpenIddict.Quartz.csproj @@ -24,6 +24,10 @@ + + + + diff --git a/src/OpenIddict.Quartz/OpenIddictQuartzJob.cs b/src/OpenIddict.Quartz/OpenIddictQuartzJob.cs index 500bd050..bb56dd1b 100644 --- a/src/OpenIddict.Quartz/OpenIddictQuartzJob.cs +++ b/src/OpenIddict.Quartz/OpenIddictQuartzJob.cs @@ -7,6 +7,7 @@ using System.ComponentModel; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Options; +using OpenIddict.Extensions; namespace OpenIddict.Quartz; @@ -86,16 +87,10 @@ public sealed class OpenIddictQuartzJob : IJob await manager.PruneAsync(threshold, context.CancellationToken); } - // OutOfMemoryExceptions are treated as fatal errors and are always re-thrown as-is. - catch (OutOfMemoryException) - { - throw; - } - // OperationCanceledExceptions are typically thrown when the host is about to shut down. // To allow the host to shut down as fast as possible, this exception type is special-cased // to prevent further processing in this job and inform Quartz.NET it shouldn't be refired. - catch (OperationCanceledException exception) when (exception.CancellationToken == context.CancellationToken) + catch (OperationCanceledException exception) when (context.CancellationToken.IsCancellationRequested) { throw new JobExecutionException(exception) { @@ -105,15 +100,15 @@ public sealed class OpenIddictQuartzJob : IJob // AggregateExceptions are generally thrown by the manager itself when one or multiple exception(s) // occurred while trying to prune the entities. In this case, add the inner exceptions to the collection. - catch (AggregateException exception) + catch (AggregateException exception) when (!OpenIddictHelpers.IsFatal(exception)) { exceptions ??= new List(capacity: exception.InnerExceptions.Count); exceptions.AddRange(exception.InnerExceptions); } - // Other exceptions are assumed to be transient and are added to the exceptions collection + // Other non-fatal exceptions are assumed to be transient and are added to the exceptions collection // to be re-thrown later (typically, at the very end of this job, as an AggregateException). - catch (Exception exception) + catch (Exception exception) when (!OpenIddictHelpers.IsFatal(exception)) { exceptions ??= new List(capacity: 1); exceptions.Add(exception); @@ -143,16 +138,10 @@ public sealed class OpenIddictQuartzJob : IJob await manager.PruneAsync(threshold, context.CancellationToken); } - // OutOfMemoryExceptions are treated as fatal errors and are always re-thrown as-is. - catch (OutOfMemoryException) - { - throw; - } - // OperationCanceledExceptions are typically thrown when the host is about to shut down. // To allow the host to shut down as fast as possible, this exception type is special-cased // to prevent further processing in this job and inform Quartz.NET it shouldn't be refired. - catch (OperationCanceledException exception) when (exception.CancellationToken == context.CancellationToken) + catch (OperationCanceledException exception) when (context.CancellationToken.IsCancellationRequested) { throw new JobExecutionException(exception) { @@ -162,15 +151,15 @@ public sealed class OpenIddictQuartzJob : IJob // AggregateExceptions are generally thrown by the manager itself when one or multiple exception(s) // occurred while trying to prune the entities. In this case, add the inner exceptions to the collection. - catch (AggregateException exception) + catch (AggregateException exception) when (!OpenIddictHelpers.IsFatal(exception)) { exceptions ??= new List(capacity: exception.InnerExceptions.Count); exceptions.AddRange(exception.InnerExceptions); } - // Other exceptions are assumed to be transient and are added to the exceptions collection + // Other non-fatal exceptions are assumed to be transient and are added to the exceptions collection // to be re-thrown later (typically, at the very end of this job, as an AggregateException). - catch (Exception exception) + catch (Exception exception) when (!OpenIddictHelpers.IsFatal(exception)) { exceptions ??= new List(capacity: 1); exceptions.Add(exception); diff --git a/src/OpenIddict.Server.AspNetCore/OpenIddictServerAspNetCoreHandlers.cs b/src/OpenIddict.Server.AspNetCore/OpenIddictServerAspNetCoreHandlers.cs index 80968f56..49a25148 100644 --- a/src/OpenIddict.Server.AspNetCore/OpenIddictServerAspNetCoreHandlers.cs +++ b/src/OpenIddict.Server.AspNetCore/OpenIddictServerAspNetCoreHandlers.cs @@ -16,6 +16,7 @@ using Microsoft.AspNetCore.Http.Extensions; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using Microsoft.Net.Http.Headers; +using OpenIddict.Extensions; using Properties = OpenIddict.Server.AspNetCore.OpenIddictServerAspNetCoreConstants.Properties; #if SUPPORTS_JSON_NODES @@ -718,7 +719,7 @@ public static partial class OpenIddictServerAspNetCoreHandlers return default; } - catch + catch (Exception exception) when (!OpenIddictHelpers.IsFatal(exception)) { context.Reject( error: Errors.InvalidRequest, diff --git a/src/OpenIddict.Server.DataProtection/OpenIddict.Server.DataProtection.csproj b/src/OpenIddict.Server.DataProtection/OpenIddict.Server.DataProtection.csproj index 96c94c93..7d18d21d 100644 --- a/src/OpenIddict.Server.DataProtection/OpenIddict.Server.DataProtection.csproj +++ b/src/OpenIddict.Server.DataProtection/OpenIddict.Server.DataProtection.csproj @@ -32,6 +32,10 @@ + + + + diff --git a/src/OpenIddict.Server.DataProtection/OpenIddictServerDataProtectionHandlers.Protection.cs b/src/OpenIddict.Server.DataProtection/OpenIddictServerDataProtectionHandlers.Protection.cs index 9d85865f..7cbc9818 100644 --- a/src/OpenIddict.Server.DataProtection/OpenIddictServerDataProtectionHandlers.Protection.cs +++ b/src/OpenIddict.Server.DataProtection/OpenIddictServerDataProtectionHandlers.Protection.cs @@ -10,6 +10,7 @@ using Microsoft.AspNetCore.DataProtection; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using Microsoft.IdentityModel.Tokens; +using OpenIddict.Extensions; using static OpenIddict.Server.DataProtection.OpenIddictServerDataProtectionConstants.Purposes; using static OpenIddict.Server.OpenIddictServerHandlers.Protection; using Schemes = OpenIddict.Server.DataProtection.OpenIddictServerDataProtectionConstants.Purposes.Schemes; @@ -229,7 +230,7 @@ public static partial class OpenIddictServerDataProtectionHandlers return _options.CurrentValue.Formatter.ReadToken(reader)?.SetTokenType(type); } - catch (Exception exception) + catch (Exception exception) when (!OpenIddictHelpers.IsFatal(exception)) { context.Logger.LogTrace(exception, SR.GetResourceString(SR.ID6153), context.Token); diff --git a/src/OpenIddict.Server.Owin/OpenIddictServerOwinHandlers.cs b/src/OpenIddict.Server.Owin/OpenIddictServerOwinHandlers.cs index 5e667f3c..bad0b98a 100644 --- a/src/OpenIddict.Server.Owin/OpenIddictServerOwinHandlers.cs +++ b/src/OpenIddict.Server.Owin/OpenIddictServerOwinHandlers.cs @@ -13,6 +13,7 @@ using System.Text.Encodings.Web; using System.Text.Json; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; +using OpenIddict.Extensions; using Owin; using static OpenIddict.Server.Owin.OpenIddictServerOwinConstants; using Properties = OpenIddict.Server.Owin.OpenIddictServerOwinConstants.Properties; @@ -771,7 +772,7 @@ public static partial class OpenIddictServerOwinHandlers return default; } - catch + catch (Exception exception) when (!OpenIddictHelpers.IsFatal(exception)) { context.Reject( error: Errors.InvalidRequest, @@ -1046,7 +1047,7 @@ public static partial class OpenIddictServerOwinHandlers context.Response.SuppressFormsAuthenticationRedirect = true; } - catch + catch (Exception exception) when (!OpenIddictHelpers.IsFatal(exception)) { } } diff --git a/src/OpenIddict.Server/OpenIddictServerDispatcher.cs b/src/OpenIddict.Server/OpenIddictServerDispatcher.cs index aafa17ea..436f6f0f 100644 --- a/src/OpenIddict.Server/OpenIddictServerDispatcher.cs +++ b/src/OpenIddict.Server/OpenIddictServerDispatcher.cs @@ -7,6 +7,7 @@ using System.ComponentModel; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; +using OpenIddict.Extensions; namespace OpenIddict.Server; @@ -48,7 +49,7 @@ public sealed class OpenIddictServerDispatcher : IOpenIddictServerDispatcher await handler.HandleAsync(context); } - catch (Exception exception) when (_logger.IsEnabled(LogLevel.Debug)) + catch (Exception exception) when (!OpenIddictHelpers.IsFatal(exception) && _logger.IsEnabled(LogLevel.Debug)) { _logger.LogDebug(exception, SR.GetResourceString(SR.ID6132), handler.GetType().FullName, typeof(TContext).FullName); diff --git a/src/OpenIddict.Validation.DataProtection/OpenIddict.Validation.DataProtection.csproj b/src/OpenIddict.Validation.DataProtection/OpenIddict.Validation.DataProtection.csproj index 9dc42aae..29a5d588 100644 --- a/src/OpenIddict.Validation.DataProtection/OpenIddict.Validation.DataProtection.csproj +++ b/src/OpenIddict.Validation.DataProtection/OpenIddict.Validation.DataProtection.csproj @@ -32,6 +32,10 @@ + + + + diff --git a/src/OpenIddict.Validation.DataProtection/OpenIddictValidationDataProtectionHandlers.Protection.cs b/src/OpenIddict.Validation.DataProtection/OpenIddictValidationDataProtectionHandlers.Protection.cs index 9fbb13cf..5b27fcb7 100644 --- a/src/OpenIddict.Validation.DataProtection/OpenIddictValidationDataProtectionHandlers.Protection.cs +++ b/src/OpenIddict.Validation.DataProtection/OpenIddictValidationDataProtectionHandlers.Protection.cs @@ -10,6 +10,7 @@ using Microsoft.AspNetCore.DataProtection; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using Microsoft.IdentityModel.Tokens; +using OpenIddict.Extensions; using static OpenIddict.Validation.DataProtection.OpenIddictValidationDataProtectionConstants.Purposes; using static OpenIddict.Validation.OpenIddictValidationHandlers.Protection; using Schemes = OpenIddict.Validation.DataProtection.OpenIddictValidationDataProtectionConstants.Purposes.Schemes; @@ -118,7 +119,7 @@ public static partial class OpenIddictValidationDataProtectionHandlers return _options.CurrentValue.Formatter.ReadToken(reader)?.SetTokenType(type); } - catch (Exception exception) + catch (Exception exception) when (!OpenIddictHelpers.IsFatal(exception)) { context.Logger.LogTrace(exception, SR.GetResourceString(SR.ID6153), context.Token); diff --git a/src/OpenIddict.Validation.Owin/OpenIddict.Validation.Owin.csproj b/src/OpenIddict.Validation.Owin/OpenIddict.Validation.Owin.csproj index 0339bc5e..1d50e23c 100644 --- a/src/OpenIddict.Validation.Owin/OpenIddict.Validation.Owin.csproj +++ b/src/OpenIddict.Validation.Owin/OpenIddict.Validation.Owin.csproj @@ -18,6 +18,10 @@ + + + + diff --git a/src/OpenIddict.Validation.Owin/OpenIddictValidationOwinHandlers.cs b/src/OpenIddict.Validation.Owin/OpenIddictValidationOwinHandlers.cs index 65d4e25b..c7ef20ad 100644 --- a/src/OpenIddict.Validation.Owin/OpenIddictValidationOwinHandlers.cs +++ b/src/OpenIddict.Validation.Owin/OpenIddictValidationOwinHandlers.cs @@ -12,6 +12,7 @@ using System.Text; using System.Text.Json; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; +using OpenIddict.Extensions; using Owin; using static OpenIddict.Validation.Owin.OpenIddictValidationOwinConstants; using Properties = OpenIddict.Validation.Owin.OpenIddictValidationOwinConstants.Properties; @@ -605,7 +606,7 @@ public static partial class OpenIddictValidationOwinHandlers context.Response.SuppressFormsAuthenticationRedirect = true; } - catch + catch (Exception exception) when (!OpenIddictHelpers.IsFatal(exception)) { } } diff --git a/src/OpenIddict.Validation.SystemNetHttp/OpenIddictValidationSystemNetHttpHandlers.cs b/src/OpenIddict.Validation.SystemNetHttp/OpenIddictValidationSystemNetHttpHandlers.cs index ddefc6b4..2f69364e 100644 --- a/src/OpenIddict.Validation.SystemNetHttp/OpenIddictValidationSystemNetHttpHandlers.cs +++ b/src/OpenIddict.Validation.SystemNetHttp/OpenIddictValidationSystemNetHttpHandlers.cs @@ -446,7 +446,7 @@ public static partial class OpenIddictValidationSystemNetHttpHandlers // If an exception is thrown at this stage, this likely means a persistent network error occurred. // In this case, log the error details and return a generic error to stop processing the event. - catch (Exception exception) + catch (Exception exception) when (!OpenIddictHelpers.IsFatal(exception)) { context.Logger.LogError(exception, SR.GetResourceString(SR.ID6182)); @@ -696,7 +696,7 @@ public static partial class OpenIddictValidationSystemNetHttpHandlers // If an exception is thrown at this stage, this likely means the returned response was not a valid // JSON response or was not correctly formatted as a JSON object. This typically happens when // a server error occurs while the JSON response is being generated and returned to the client. - catch (Exception exception) + catch (Exception exception) when (!OpenIddictHelpers.IsFatal(exception)) { context.Logger.LogError(exception, SR.GetResourceString(SR.ID6183), await response.Content.ReadAsStringAsync()); diff --git a/src/OpenIddict.Validation/OpenIddictValidationDispatcher.cs b/src/OpenIddict.Validation/OpenIddictValidationDispatcher.cs index be57f51c..e5fa2366 100644 --- a/src/OpenIddict.Validation/OpenIddictValidationDispatcher.cs +++ b/src/OpenIddict.Validation/OpenIddictValidationDispatcher.cs @@ -7,6 +7,7 @@ using System.ComponentModel; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; +using OpenIddict.Extensions; namespace OpenIddict.Validation; @@ -48,7 +49,7 @@ public sealed class OpenIddictValidationDispatcher : IOpenIddictValidationDispat await handler.HandleAsync(context); } - catch (Exception exception) when (_logger.IsEnabled(LogLevel.Debug)) + catch (Exception exception) when (!OpenIddictHelpers.IsFatal(exception) && _logger.IsEnabled(LogLevel.Debug)) { _logger.LogDebug(exception, SR.GetResourceString(SR.ID6132), handler.GetType().FullName, typeof(TContext).FullName); diff --git a/src/OpenIddict.Validation/OpenIddictValidationHandlers.cs b/src/OpenIddict.Validation/OpenIddictValidationHandlers.cs index 4e01c056..5a79e798 100644 --- a/src/OpenIddict.Validation/OpenIddictValidationHandlers.cs +++ b/src/OpenIddict.Validation/OpenIddictValidationHandlers.cs @@ -6,6 +6,7 @@ using System.Collections.Immutable; using System.ComponentModel; +using Microsoft.Extensions.Logging; using OpenIddict.Extensions; namespace OpenIddict.Validation; @@ -144,10 +145,27 @@ public static partial class OpenIddictValidationHandlers throw new ArgumentNullException(nameof(context)); } - context.Configuration ??= await context.Options.ConfigurationManager - .GetConfigurationAsync(context.CancellationToken) - .WaitAsync(context.CancellationToken) ?? - throw new InvalidOperationException(SR.GetResourceString(SR.ID0140)); + try + { + // Resolve and attach the server configuration to the context if none has been set already. + context.Configuration ??= await context.Options.ConfigurationManager + .GetConfigurationAsync(context.CancellationToken) + .WaitAsync(context.CancellationToken) ?? + throw new InvalidOperationException(SR.GetResourceString(SR.ID0140)); + } + + catch (Exception exception) when (!OpenIddictHelpers.IsFatal(exception) && + exception is not OperationCanceledException) + { + context.Logger.LogError(exception, SR.GetResourceString(SR.ID6219)); + + context.Reject( + error: Errors.ServerError, + description: SR.GetResourceString(SR.ID2170), + uri: SR.FormatID8000(SR.ID2170)); + + return; + } } } diff --git a/test/OpenIddict.Validation.IntegrationTests/OpenIddictValidationIntegrationTests.cs b/test/OpenIddict.Validation.IntegrationTests/OpenIddictValidationIntegrationTests.cs index 0e1cc800..b3783983 100644 --- a/test/OpenIddict.Validation.IntegrationTests/OpenIddictValidationIntegrationTests.cs +++ b/test/OpenIddict.Validation.IntegrationTests/OpenIddictValidationIntegrationTests.cs @@ -67,22 +67,25 @@ public abstract partial class OpenIddictValidationIntegrationTests public async Task ProcessAuthentication_RejectsDemandWhenAccessTokenIsMissing() { // Arrange - await using var server = await CreateServerAsync(options => - { - options.AddEventHandler(builder => - { - builder.UseInlineHandler(context => - { - // Assert - Assert.True(context.IsRejected); - Assert.Equal(Errors.MissingToken, context.Error); - Assert.Equal(SR.GetResourceString(SR.ID2000), context.ErrorDescription); + await using var server = await CreateServerAsync(); + await using var client = await server.CreateClientAsync(); - return default; - }); + // Act + var response = await client.PostAsync("/authenticate", new OpenIddictRequest()); - builder.SetOrder(ValidateRequiredTokens.Descriptor.Order + 1); - }); + // Assert + Assert.Equal(0, response.Count); + } + + [Fact] + public async Task ProcessAuthentication_RejectsDemandWithoutResolvingServerConfigurationWhenNoTokenWasResolved() + { + // Arrange + var manager = Mock.Of>(); + + await using var server = await CreateServerAsync(options => + { + options.Configure(options => options.ConfigurationManager = manager); }); await using var client = await server.CreateClientAsync(); @@ -92,6 +95,32 @@ public abstract partial class OpenIddictValidationIntegrationTests // Assert Assert.Equal(0, response.Count); + Mock.Get(manager).Verify(manager => manager.GetConfigurationAsync(It.IsAny()), Times.Never()); + } + + [Fact] + public async Task ProcessAuthentication_RejectsDemandWhenConfigurationCannotBeResolved() + { + // Arrange + var manager = Mock.Of>(manager => + manager.GetConfigurationAsync(It.IsAny()) == Task.FromException(new Exception())); + + await using var server = await CreateServerAsync(options => + { + options.Configure(options => options.ConfigurationManager = manager); + }); + + await using var client = await server.CreateClientAsync(); + + // Act + var response = await client.PostAsync("/authenticate", new OpenIddictRequest + { + AccessToken = "SlAV32hkKG" + }); + + // Assert + Assert.Equal(Errors.ServerError, response.Error); + Assert.Equal(SR.GetResourceString(SR.ID2170), response.ErrorDescription); } [Fact]