diff --git a/src/OpenIddict.Abstractions/OpenIddictResources.resx b/src/OpenIddict.Abstractions/OpenIddictResources.resx index 0f7f2535..608b68dd 100644 --- a/src/OpenIddict.Abstractions/OpenIddictResources.resx +++ b/src/OpenIddict.Abstractions/OpenIddictResources.resx @@ -1626,6 +1626,12 @@ To register the server services, use 'services.AddOpenIddict().AddClient()'. The '{0}' parameter must not include a '{1}' component. + + An error occurred while communicating with the remote HTTP server. + + + An invalid JSON response was returned by the remote HTTP server. + The '{0}' parameter shouldn't be null or empty at this point. @@ -2174,6 +2180,12 @@ This may indicate that the hashed entry is corrupted or malformed. The authorization request was rejected because the '{Parameter}' contained a forbidden parameter: {Name}. + + A network error occured while communicating with the remote HTTP server. + + + An invalid JSON response was returned by the remote HTTP server: {Payload}. + https://documentation.openiddict.com/errors/{0} diff --git a/src/OpenIddict.Client.SystemNetHttp/OpenIddictClientSystemNetHttpHandlers.Userinfo.cs b/src/OpenIddict.Client.SystemNetHttp/OpenIddictClientSystemNetHttpHandlers.Userinfo.cs index aef74189..0e2d5e0f 100644 --- a/src/OpenIddict.Client.SystemNetHttp/OpenIddictClientSystemNetHttpHandlers.Userinfo.cs +++ b/src/OpenIddict.Client.SystemNetHttp/OpenIddictClientSystemNetHttpHandlers.Userinfo.cs @@ -7,6 +7,8 @@ using System.Collections.Immutable; using System.Diagnostics; using System.Net.Http.Headers; +using System.Text.Json; +using Microsoft.Extensions.Logging; using static OpenIddict.Client.SystemNetHttp.OpenIddictClientSystemNetHttpConstants; namespace OpenIddict.Client.SystemNetHttp; @@ -115,13 +117,45 @@ public static partial class OpenIddictClientSystemNetHttpHandlers { context.Response = new OpenIddictResponse(); context.UserinfoToken = await response.Content.ReadAsStringAsync(); + + return; + } + + try + { + try + { + // Note: ReadFromJsonAsync() automatically validates the content encoding and transparently + // transcodes the response stream if a non-UTF-8 response is returned by the remote server. + context.Response = await response.Content.ReadFromJsonAsync(); + } + + // Initial versions of System.Net.Http.Json were known to eagerly validate the media type returned + // as part of the HTTP Content-Type header and throw a NotSupportedException. If such an exception + // is caught, try to extract the response using the less efficient string-based deserialization, + // that will also take care of handling non-UTF-8 encodings but won't validate the media type. + catch (NotSupportedException) + { + context.Response = JsonSerializer.Deserialize( + await response.Content.ReadAsStringAsync()); + } } - else + // 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 and a default error page is returned by the remote HTTP server. + // In this case, log the error details and return a generic error to stop processing the event. + catch (Exception exception) { - // Note: ReadFromJsonAsync() automatically validates the content type and the content encoding - // and transcode the response stream if a non-UTF-8 response is returned by the remote server. - context.Response = await response.Content.ReadFromJsonAsync(); + context.Logger.LogError(exception, SR.GetResourceString(SR.ID6183), + await response.Content.ReadAsStringAsync()); + + context.Reject( + error: Errors.ServerError, + description: SR.GetResourceString(SR.ID2137), + uri: SR.FormatID8000(SR.ID2137)); + + return; } } } diff --git a/src/OpenIddict.Client.SystemNetHttp/OpenIddictClientSystemNetHttpHandlers.cs b/src/OpenIddict.Client.SystemNetHttp/OpenIddictClientSystemNetHttpHandlers.cs index a1fe9d4a..2cca67c0 100644 --- a/src/OpenIddict.Client.SystemNetHttp/OpenIddictClientSystemNetHttpHandlers.cs +++ b/src/OpenIddict.Client.SystemNetHttp/OpenIddictClientSystemNetHttpHandlers.cs @@ -10,6 +10,8 @@ using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.Net.Http.Headers; using System.Text; +using System.Text.Json; +using Microsoft.Extensions.Logging; namespace OpenIddict.Client.SystemNetHttp; @@ -254,11 +256,30 @@ public static partial class OpenIddictClientSystemNetHttpHandlers // If supported, import the HTTP version from the client instance. request.Version = client.DefaultRequestVersion; #endif - var response = await client.SendAsync(request, HttpCompletionOption.ResponseContentRead) ?? - throw new InvalidOperationException(SR.GetResourceString(SR.ID0175)); + HttpResponseMessage response; + + try + { + response = await client.SendAsync(request, HttpCompletionOption.ResponseContentRead); + } + + // 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) + { + context.Logger.LogError(exception, SR.GetResourceString(SR.ID6182)); + + context.Reject( + error: Errors.ServerError, + description: SR.GetResourceString(SR.ID2136), + uri: SR.FormatID8000(SR.ID2136)); + + return; + } // Store the HttpResponseMessage in the transaction properties. - context.Transaction.SetProperty(typeof(HttpResponseMessage).FullName!, response); + context.Transaction.SetProperty(typeof(HttpResponseMessage).FullName!, response ?? + throw new InvalidOperationException(SR.GetResourceString(SR.ID0175))); } } @@ -332,9 +353,42 @@ public static partial class OpenIddictClientSystemNetHttpHandlers // The status code is deliberately not validated to ensure even errored responses // (typically in the 4xx range) can be deserialized and handled by the event handlers. - // Note: ReadFromJsonAsync() automatically validates the content type and the content encoding - // and transcode the response stream if a non-UTF-8 response is returned by the remote server. - context.Transaction.Response = await response.Content.ReadFromJsonAsync(); + try + { + try + { + // Note: ReadFromJsonAsync() automatically validates the content encoding and transparently + // transcodes the response stream if a non-UTF-8 response is returned by the remote server. + context.Transaction.Response = await response.Content.ReadFromJsonAsync(); + } + + // Initial versions of System.Net.Http.Json were known to eagerly validate the media type returned + // as part of the HTTP Content-Type header and throw a NotSupportedException. If such an exception + // is caught, try to extract the response using the less efficient string-based deserialization, + // that will also take care of handling non-UTF-8 encodings but won't validate the media type. + catch (NotSupportedException) + { + context.Transaction.Response = JsonSerializer.Deserialize( + await response.Content.ReadAsStringAsync()); + } + } + + // 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 and a default error page is returned by the remote HTTP server. + // In this case, log the error details and return a generic error to stop processing the event. + catch (Exception exception) + { + context.Logger.LogError(exception, SR.GetResourceString(SR.ID6183), + await response.Content.ReadAsStringAsync()); + + context.Reject( + error: Errors.ServerError, + description: SR.GetResourceString(SR.ID2137), + uri: SR.FormatID8000(SR.ID2137)); + + return; + } } } diff --git a/src/OpenIddict.Validation.SystemNetHttp/OpenIddictValidationSystemNetHttpHandlers.cs b/src/OpenIddict.Validation.SystemNetHttp/OpenIddictValidationSystemNetHttpHandlers.cs index df7367a5..22db0ff8 100644 --- a/src/OpenIddict.Validation.SystemNetHttp/OpenIddictValidationSystemNetHttpHandlers.cs +++ b/src/OpenIddict.Validation.SystemNetHttp/OpenIddictValidationSystemNetHttpHandlers.cs @@ -10,6 +10,8 @@ using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.Net.Http.Headers; using System.Text; +using System.Text.Json; +using Microsoft.Extensions.Logging; namespace OpenIddict.Validation.SystemNetHttp; @@ -253,11 +255,30 @@ public static partial class OpenIddictValidationSystemNetHttpHandlers // If supported, import the HTTP version from the client instance. request.Version = client.DefaultRequestVersion; #endif - var response = await client.SendAsync(request, HttpCompletionOption.ResponseContentRead) ?? - throw new InvalidOperationException(SR.GetResourceString(SR.ID0175)); + HttpResponseMessage response; + + try + { + response = await client.SendAsync(request, HttpCompletionOption.ResponseContentRead); + } + + // 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) + { + context.Logger.LogError(exception, SR.GetResourceString(SR.ID6182)); + + context.Reject( + error: Errors.ServerError, + description: SR.GetResourceString(SR.ID2136), + uri: SR.FormatID8000(SR.ID2136)); + + return; + } // Store the HttpResponseMessage in the transaction properties. - context.Transaction.SetProperty(typeof(HttpResponseMessage).FullName!, response); + context.Transaction.SetProperty(typeof(HttpResponseMessage).FullName!, response ?? + throw new InvalidOperationException(SR.GetResourceString(SR.ID0175))); } } @@ -331,9 +352,42 @@ public static partial class OpenIddictValidationSystemNetHttpHandlers // The status code is deliberately not validated to ensure even errored responses // (typically in the 4xx range) can be deserialized and handled by the event handlers. - // Note: ReadFromJsonAsync() automatically validates the content type and the content encoding - // and transcode the response stream if a non-UTF-8 response is returned by the remote server. - context.Transaction.Response = await response.Content.ReadFromJsonAsync(); + try + { + try + { + // Note: ReadFromJsonAsync() automatically validates the content encoding and transparently + // transcodes the response stream if a non-UTF-8 response is returned by the remote server. + context.Transaction.Response = await response.Content.ReadFromJsonAsync(); + } + + // Initial versions of System.Net.Http.Json were known to eagerly validate the media type returned + // as part of the HTTP Content-Type header and throw a NotSupportedException. If such an exception + // is caught, try to extract the response using the less efficient string-based deserialization, + // that will also take care of handling non-UTF-8 encodings but won't validate the media type. + catch (NotSupportedException) + { + context.Transaction.Response = JsonSerializer.Deserialize( + await response.Content.ReadAsStringAsync()); + } + } + + // 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 and a default error page is returned by the remote HTTP server. + // In this case, log the error details and return a generic error to stop processing the event. + catch (Exception exception) + { + context.Logger.LogError(exception, SR.GetResourceString(SR.ID6183), + await response.Content.ReadAsStringAsync()); + + context.Reject( + error: Errors.ServerError, + description: SR.GetResourceString(SR.ID2137), + uri: SR.FormatID8000(SR.ID2137)); + + return; + } } }