From 9512da8a22488d58badbb7b483e93469a48f02ff Mon Sep 17 00:00:00 2001 From: BrentSchmaltz Date: Fri, 11 Aug 2023 10:05:12 -0700 Subject: [PATCH] First cut at OpenIdConnectConfiguration and SignedHttpRequest (#2214) * First cut at OpenIdConnectConfiguration and SignedHttpRequest * addressed review comments --------- Co-authored-by: Brent Schmaltz --- .../JwtTokenUtilities.cs | 1 + .../OpenIdConnectConfiguration.cs | 324 +++++-- .../OpenIdConnectConfigurationRetriever.cs | 3 +- .../OpenIdConnectConfigurationSerializer.cs | 610 ++++++++++++ .../OpenIdConnectMessage.cs | 40 +- .../OpenIdConnectParameterNames.cs | 51 + .../OpenIdProviderMetadataNames.cs | 58 +- .../SignedHttpRequestDescriptor.cs | 27 +- .../SignedHttpRequestHandler.cs | 367 ++++---- .../SignedHttpRequestValidationContext.cs | 27 +- .../SignedHttpRequestValidationResult.cs | 27 +- .../AuthenticationProtocolMessage.cs | 4 +- .../BaseConfiguration.cs | 2 +- .../Json/EncodedJsonWebKeyParameterNames.cs | 271 ------ .../Json/JsonSerializerPrimitives.cs | 159 +++- .../Json/JsonWebKeySerializer.cs | 51 +- .../Json/JsonWebKeySetSerializer.cs | 2 +- .../JsonWebKeyParameterNames.cs | 53 +- .../Json/OpenIdConnectConfiguration6x.cs | 671 ++++++++++++++ .../Json/OpenIdConnectMessage6x.cs | 587 ++++++++++++ ...Model.Protocols.OpenIdConnect.Tests.csproj | 1 + ...penIdConnectConfigurationRetrieverTests.cs | 2 +- .../OpenIdConnectConfigurationTests.cs | 10 +- .../OpenIdConnectMessageTests.cs | 51 +- .../PopKeyResolvingTests.cs | 28 +- .../SignedHttpRequestCreationTests.cs | 873 +++++++----------- .../SignedHttpRequestHandlerPublic.cs | 55 -- .../SignedHttpRequestValidationTests.cs | 34 +- .../Json/AuthenticationProtocolMessage6x.cs | 278 ++++++ 29 files changed, 3304 insertions(+), 1363 deletions(-) create mode 100644 src/Microsoft.IdentityModel.Protocols.OpenIdConnect/Json/OpenIdConnectConfigurationSerializer.cs delete mode 100644 src/Microsoft.IdentityModel.Tokens/Json/EncodedJsonWebKeyParameterNames.cs create mode 100644 test/Microsoft.IdentityModel.Protocols.OpenIdConnect.Tests/Json/OpenIdConnectConfiguration6x.cs create mode 100644 test/Microsoft.IdentityModel.Protocols.OpenIdConnect.Tests/Json/OpenIdConnectMessage6x.cs create mode 100644 test/Microsoft.IdentityModel.Protocols.Tests/Json/AuthenticationProtocolMessage6x.cs diff --git a/src/Microsoft.IdentityModel.JsonWebTokens/JwtTokenUtilities.cs b/src/Microsoft.IdentityModel.JsonWebTokens/JwtTokenUtilities.cs index e8b8f76940..e3daea25e8 100644 --- a/src/Microsoft.IdentityModel.JsonWebTokens/JwtTokenUtilities.cs +++ b/src/Microsoft.IdentityModel.JsonWebTokens/JwtTokenUtilities.cs @@ -85,6 +85,7 @@ public static string CreateEncodedSignature(string input, SigningCredentials sig /// or is null. public static string CreateEncodedSignature(string input, SigningCredentials signingCredentials, bool cacheProvider) { + // TODO create overload that takes a Span for the input if (input == null) throw LogHelper.LogArgumentNullException(nameof(input)); diff --git a/src/Microsoft.IdentityModel.Protocols.OpenIdConnect/Configuration/OpenIdConnectConfiguration.cs b/src/Microsoft.IdentityModel.Protocols.OpenIdConnect/Configuration/OpenIdConnectConfiguration.cs index 753c5dbd26..667494c70a 100644 --- a/src/Microsoft.IdentityModel.Protocols.OpenIdConnect/Configuration/OpenIdConnectConfiguration.cs +++ b/src/Microsoft.IdentityModel.Protocols.OpenIdConnect/Configuration/OpenIdConnectConfiguration.cs @@ -5,20 +5,47 @@ using System.Collections.Generic; using System.Collections.ObjectModel; using System.ComponentModel; +using System.Text.Json.Serialization; +using System.Threading; using Microsoft.IdentityModel.Abstractions; using Microsoft.IdentityModel.Logging; using Microsoft.IdentityModel.Tokens; -using Newtonsoft.Json; namespace Microsoft.IdentityModel.Protocols.OpenIdConnect { /// /// Contains OpenIdConnect configuration that can be populated from a json string. /// - [JsonObject] public class OpenIdConnectConfiguration : BaseConfiguration { - private const string _className = "Microsoft.IdentityModel.Protocols.OpenIdConnect.OpenIdConnectConfiguration"; + internal const string ClassName = "Microsoft.IdentityModel.Protocols.OpenIdConnect.OpenIdConnectConfiguration"; + + // these are used to lazy create + private Dictionary _additionalData; + private ICollection _acrValuesSupported; + private ICollection _claimsSupported; + private ICollection _claimsLocalesSupported; + private ICollection _claimTypesSupported; + private ICollection _displayValuesSupported; + private ICollection _grantTypesSupported; + private ICollection _idTokenEncryptionAlgValuesSupported; + private ICollection _idTokenEncryptionEncValuesSupported; + private ICollection _idTokenSigningAlgValuesSupported; + private ICollection _introspectionEndpointAuthMethodsSupported; + private ICollection _introspectionEndpointAuthSigningAlgValuesSupported; + private ICollection _requestObjectEncryptionAlgValuesSupported; + private ICollection _requestObjectEncryptionEncValuesSupported; + private ICollection _requestObjectSigningAlgValuesSupported; + private ICollection _responseModesSupported; + private ICollection _responseTypesSupported; + private ICollection _scopesSupported; + private ICollection _subjectTypesSupported; + private ICollection _tokenEndpointAuthMethodsSupported; + private ICollection _tokenEndpointAuthSigningAlgValuesSupported; + private ICollection _uILocalesSupported; + private ICollection _userInfoEndpointEncryptionAlgValuesSupported; + private ICollection _userInfoEndpointEncryptionEncValuesSupported; + private ICollection _userInfoEndpointSigningAlgValuesSupported; /// /// Deserializes the json string into an object. @@ -49,8 +76,10 @@ public static string Write(OpenIdConnectConfiguration configuration) if (configuration == null) throw LogHelper.LogArgumentNullException(nameof(configuration)); - LogHelper.LogVerbose(LogMessages.IDX21809); - return JsonConvert.SerializeObject(configuration); + if (LogHelper.IsEnabled(EventLogLevel.Verbose)) + LogHelper.LogVerbose(LogMessages.IDX21809); + + return OpenIdConnectConfigurationSerializer.Write(configuration); } /// @@ -67,19 +96,19 @@ public OpenIdConnectConfiguration() /// If 'json' is null or empty. public OpenIdConnectConfiguration(string json) { - if(string.IsNullOrEmpty(json)) + if (string.IsNullOrEmpty(json)) throw LogHelper.LogArgumentNullException(nameof(json)); try { if (LogHelper.IsEnabled(EventLogLevel.Verbose)) - LogHelper.LogVerbose(LogMessages.IDX21806, json, LogHelper.MarkAsNonPII(_className)); + LogHelper.LogVerbose(LogMessages.IDX21806, json, LogHelper.MarkAsNonPII(ClassName)); - JsonConvert.PopulateObject(json, this); + OpenIdConnectConfigurationSerializer.Read(json, this); } catch (Exception ex) { - throw LogHelper.LogExceptionMessage(new ArgumentException(LogHelper.FormatInvariant(LogMessages.IDX21815, json, LogHelper.MarkAsNonPII(_className)), ex)); + throw LogHelper.LogExceptionMessage(new ArgumentException(LogHelper.FormatInvariant(LogMessages.IDX21815, json, LogHelper.MarkAsNonPII(ClassName)), ex)); } } @@ -87,222 +116,330 @@ public OpenIdConnectConfiguration(string json) /// When deserializing from JSON any properties that are not defined will be placed here. /// [JsonExtensionData] - public virtual IDictionary AdditionalData { get; } = new Dictionary(); + public IDictionary AdditionalData => + _additionalData ?? + Interlocked.CompareExchange(ref _additionalData, new Dictionary(StringComparer.Ordinal), null) ?? + _additionalData; /// /// Gets the collection of 'acr_values_supported' /// - [JsonProperty(DefaultValueHandling = DefaultValueHandling.Ignore, NullValueHandling = NullValueHandling.Ignore, PropertyName = OpenIdProviderMetadataNames.AcrValuesSupported, Required = Required.Default)] - public ICollection AcrValuesSupported { get; } = new Collection(); + [JsonPropertyName(OpenIdProviderMetadataNames.AcrValuesSupported)] + public ICollection AcrValuesSupported => + _acrValuesSupported ?? + Interlocked.CompareExchange(ref _acrValuesSupported, new Collection(), null) ?? + _acrValuesSupported; /// /// Gets or sets the 'authorization_endpoint'. /// - [JsonProperty(DefaultValueHandling = DefaultValueHandling.Ignore, NullValueHandling = NullValueHandling.Ignore, PropertyName = OpenIdProviderMetadataNames.AuthorizationEndpoint, Required = Required.Default)] + [JsonPropertyName(OpenIdProviderMetadataNames.AuthorizationEndpoint)] +#if NET6_0_OR_GREATER + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] +#endif public string AuthorizationEndpoint { get; set; } /// /// Gets or sets the 'check_session_iframe'. /// - [JsonProperty(DefaultValueHandling = DefaultValueHandling.Ignore, NullValueHandling = NullValueHandling.Ignore, PropertyName = OpenIdProviderMetadataNames.CheckSessionIframe, Required = Required.Default)] + [JsonPropertyName(OpenIdProviderMetadataNames.CheckSessionIframe)] +#if NET6_0_OR_GREATER + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] +#endif public string CheckSessionIframe { get; set; } /// /// Gets the collection of 'claims_supported' /// - [JsonProperty(DefaultValueHandling = DefaultValueHandling.Ignore, NullValueHandling = NullValueHandling.Ignore, PropertyName = OpenIdProviderMetadataNames.ClaimsSupported, Required = Required.Default)] - public ICollection ClaimsSupported { get; } = new Collection(); + [JsonPropertyName(OpenIdProviderMetadataNames.ClaimsSupported)] + public ICollection ClaimsSupported => + _claimsSupported ?? + Interlocked.CompareExchange(ref _claimsSupported, new Collection(), null) ?? + _claimsSupported; /// /// Gets the collection of 'claims_locales_supported' /// - [JsonProperty(DefaultValueHandling = DefaultValueHandling.Ignore, NullValueHandling = NullValueHandling.Ignore, PropertyName = OpenIdProviderMetadataNames.ClaimsLocalesSupported, Required = Required.Default)] - public ICollection ClaimsLocalesSupported { get; } = new Collection(); + [JsonPropertyName(OpenIdProviderMetadataNames.ClaimsLocalesSupported)] + public ICollection ClaimsLocalesSupported => + _claimsLocalesSupported ?? + Interlocked.CompareExchange(ref _claimsLocalesSupported, new Collection(), null) ?? + _claimsLocalesSupported; /// /// Gets or sets the 'claims_parameter_supported' /// - [JsonProperty(DefaultValueHandling = DefaultValueHandling.Ignore, NullValueHandling = NullValueHandling.Ignore, PropertyName = OpenIdProviderMetadataNames.ClaimsParameterSupported, Required = Required.Default)] + [JsonPropertyName(OpenIdProviderMetadataNames.ClaimsParameterSupported)] +#if NET6_0_OR_GREATER + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] +#endif public bool ClaimsParameterSupported { get; set; } /// /// Gets the collection of 'claim_types_supported' /// - [JsonProperty(DefaultValueHandling = DefaultValueHandling.Ignore, NullValueHandling = NullValueHandling.Ignore, PropertyName = OpenIdProviderMetadataNames.ClaimTypesSupported, Required = Required.Default)] - public ICollection ClaimTypesSupported { get; } = new Collection(); + [JsonPropertyName(OpenIdProviderMetadataNames.ClaimsSupported)] + public ICollection ClaimTypesSupported => + _claimTypesSupported ?? + Interlocked.CompareExchange(ref _claimTypesSupported, new Collection(), null) ?? + _claimTypesSupported; /// /// Gets the collection of 'display_values_supported' /// - [JsonProperty(DefaultValueHandling = DefaultValueHandling.Ignore, NullValueHandling = NullValueHandling.Ignore, PropertyName = OpenIdProviderMetadataNames.DisplayValuesSupported, Required = Required.Default)] - public ICollection DisplayValuesSupported { get; } = new Collection(); + [JsonPropertyName(OpenIdProviderMetadataNames.DisplayValuesSupported)] + public ICollection DisplayValuesSupported => + _displayValuesSupported ?? + Interlocked.CompareExchange(ref _displayValuesSupported, new Collection(), null) ?? + _displayValuesSupported; /// /// Gets or sets the 'end_session_endpoint'. /// - [JsonProperty(DefaultValueHandling = DefaultValueHandling.Ignore, NullValueHandling = NullValueHandling.Ignore, PropertyName = OpenIdProviderMetadataNames.EndSessionEndpoint, Required = Required.Default)] + [JsonPropertyName(OpenIdProviderMetadataNames.EndSessionEndpoint)] +#if NET6_0_OR_GREATER + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] +#endif public string EndSessionEndpoint { get; set; } /// /// Gets or sets the 'frontchannel_logout_session_supported'. /// - [JsonProperty(DefaultValueHandling = DefaultValueHandling.Ignore, NullValueHandling = NullValueHandling.Ignore, PropertyName = OpenIdProviderMetadataNames.FrontchannelLogoutSessionSupported, Required = Required.Default)] + /// Would be breaking to change, in 6x it was string, spec says bool. + /// TODO - add another property, obsolete and drop in 8x? + /// see: https://openid.net/specs/openid-connect-frontchannel-1_0.html + /// + [JsonPropertyName(OpenIdProviderMetadataNames.FrontchannelLogoutSessionSupported)] public string FrontchannelLogoutSessionSupported { get; set; } /// /// Gets or sets the 'frontchannel_logout_supported'. /// - [JsonProperty(DefaultValueHandling = DefaultValueHandling.Ignore, NullValueHandling = NullValueHandling.Ignore, PropertyName = OpenIdProviderMetadataNames.FrontchannelLogoutSupported, Required = Required.Default)] + /// Would be breaking to change, in 6x it was string, spec says bool. + /// TODO - add another property, obsolete and drop in 8x? + /// see: https://openid.net/specs/openid-connect-frontchannel-1_0.html + /// + [JsonPropertyName(OpenIdProviderMetadataNames.FrontchannelLogoutSupported)] public string FrontchannelLogoutSupported { get; set; } /// /// Gets the collection of 'grant_types_supported' /// - [JsonProperty(DefaultValueHandling = DefaultValueHandling.Ignore, NullValueHandling = NullValueHandling.Ignore, PropertyName = OpenIdProviderMetadataNames.GrantTypesSupported, Required = Required.Default)] - public ICollection GrantTypesSupported { get; } = new Collection(); + [JsonPropertyName(OpenIdProviderMetadataNames.GrantTypesSupported)] + public ICollection GrantTypesSupported => + _grantTypesSupported ?? + Interlocked.CompareExchange(ref _grantTypesSupported, new Collection(), null) ?? + _grantTypesSupported; /// /// Boolean value specifying whether the OP supports HTTP-based logout. Default is false. /// - [JsonProperty(DefaultValueHandling = DefaultValueHandling.Ignore, NullValueHandling = NullValueHandling.Ignore, PropertyName = OpenIdProviderMetadataNames.HttpLogoutSupported, Required = Required.Default)] + [JsonPropertyName(OpenIdProviderMetadataNames.HttpLogoutSupported)] +#if NET6_0_OR_GREATER + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] +#endif public bool HttpLogoutSupported { get; set; } /// /// Gets the collection of 'id_token_encryption_alg_values_supported'. /// - [JsonProperty(DefaultValueHandling = DefaultValueHandling.Ignore, NullValueHandling = NullValueHandling.Ignore, PropertyName = OpenIdProviderMetadataNames.IdTokenEncryptionAlgValuesSupported, Required = Required.Default)] - public ICollection IdTokenEncryptionAlgValuesSupported { get; } = new Collection(); + [JsonPropertyName(OpenIdProviderMetadataNames.IdTokenEncryptionAlgValuesSupported)] + public ICollection IdTokenEncryptionAlgValuesSupported => + _idTokenEncryptionAlgValuesSupported ?? + Interlocked.CompareExchange(ref _idTokenEncryptionAlgValuesSupported, new Collection(), null) ?? + _idTokenEncryptionAlgValuesSupported; /// /// Gets the collection of 'id_token_encryption_enc_values_supported'. /// - [JsonProperty(DefaultValueHandling = DefaultValueHandling.Ignore, NullValueHandling = NullValueHandling.Ignore, PropertyName = OpenIdProviderMetadataNames.IdTokenEncryptionEncValuesSupported, Required = Required.Default)] - public ICollection IdTokenEncryptionEncValuesSupported { get; } = new Collection(); + [JsonPropertyName(OpenIdProviderMetadataNames.IdTokenEncryptionEncValuesSupported)] + public ICollection IdTokenEncryptionEncValuesSupported => + _idTokenEncryptionEncValuesSupported ?? + Interlocked.CompareExchange(ref _idTokenEncryptionEncValuesSupported, new Collection(), null) ?? + _idTokenEncryptionEncValuesSupported; /// /// Gets the collection of 'id_token_signing_alg_values_supported'. /// - [JsonProperty(DefaultValueHandling = DefaultValueHandling.Ignore, NullValueHandling = NullValueHandling.Ignore, PropertyName = OpenIdProviderMetadataNames.IdTokenSigningAlgValuesSupported, Required = Required.Default)] - public ICollection IdTokenSigningAlgValuesSupported { get; } = new Collection(); + [JsonPropertyName(OpenIdProviderMetadataNames.IdTokenSigningAlgValuesSupported)] + public ICollection IdTokenSigningAlgValuesSupported => + _idTokenSigningAlgValuesSupported ?? + Interlocked.CompareExchange(ref _idTokenSigningAlgValuesSupported, new Collection(), null) ?? + _idTokenSigningAlgValuesSupported; /// /// Gets or sets the 'introspection_endpoint'. /// - [JsonProperty(DefaultValueHandling = DefaultValueHandling.Ignore, NullValueHandling = NullValueHandling.Ignore, PropertyName = OpenIdProviderMetadataNames.IntrospectionEndpoint, Required = Required.Default)] + [JsonPropertyName(OpenIdProviderMetadataNames.IntrospectionEndpoint)] +#if NET6_0_OR_GREATER + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] +#endif public string IntrospectionEndpoint { get; set; } /// /// Gets the collection of 'introspection_endpoint_auth_methods_supported'. /// - [JsonProperty(DefaultValueHandling = DefaultValueHandling.Ignore, NullValueHandling = NullValueHandling.Ignore, PropertyName = OpenIdProviderMetadataNames.IntrospectionEndpointAuthMethodsSupported, Required = Required.Default)] - public ICollection IntrospectionEndpointAuthMethodsSupported { get; } = new Collection(); + [JsonPropertyName(OpenIdProviderMetadataNames.IntrospectionEndpointAuthMethodsSupported)] + public ICollection IntrospectionEndpointAuthMethodsSupported => + _introspectionEndpointAuthMethodsSupported ?? + Interlocked.CompareExchange(ref _introspectionEndpointAuthMethodsSupported, new Collection(), null) ?? + _introspectionEndpointAuthMethodsSupported; /// /// Gets the collection of 'introspection_endpoint_auth_signing_alg_values_supported'. /// - [JsonProperty(DefaultValueHandling = DefaultValueHandling.Ignore, NullValueHandling = NullValueHandling.Ignore, PropertyName = OpenIdProviderMetadataNames.IntrospectionEndpointAuthSigningAlgValuesSupported, Required = Required.Default)] - public ICollection IntrospectionEndpointAuthSigningAlgValuesSupported { get; } = new Collection(); + [JsonPropertyName(OpenIdProviderMetadataNames.IntrospectionEndpointAuthSigningAlgValuesSupported)] + public ICollection IntrospectionEndpointAuthSigningAlgValuesSupported => + _introspectionEndpointAuthSigningAlgValuesSupported ?? + Interlocked.CompareExchange(ref _introspectionEndpointAuthSigningAlgValuesSupported, new Collection(), null) ?? + _introspectionEndpointAuthSigningAlgValuesSupported; /// /// Gets or sets the 'issuer'. /// - [JsonProperty(DefaultValueHandling = DefaultValueHandling.Ignore, NullValueHandling = NullValueHandling.Ignore, PropertyName = OpenIdProviderMetadataNames.Issuer, Required = Required.Default)] + [JsonPropertyName(OpenIdProviderMetadataNames.Issuer)] +#if NET6_0_OR_GREATER + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] +#endif public override string Issuer { get; set; } /// /// Gets or sets the 'jwks_uri' /// - [JsonProperty(DefaultValueHandling = DefaultValueHandling.Ignore, NullValueHandling = NullValueHandling.Ignore, PropertyName = OpenIdProviderMetadataNames.JwksUri, Required = Required.Default)] + [JsonPropertyName(OpenIdProviderMetadataNames.JwksUri)] +#if NET6_0_OR_GREATER + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] +#endif public string JwksUri { get; set; } /// /// Gets or sets the /// + [JsonIgnore] public JsonWebKeySet JsonWebKeySet {get; set;} /// /// Boolean value specifying whether the OP can pass a sid (session ID) query parameter to identify the RP session at the OP when the logout_uri is used. Dafault Value is false. /// - [JsonProperty(DefaultValueHandling = DefaultValueHandling.Ignore, NullValueHandling = NullValueHandling.Ignore, PropertyName = OpenIdProviderMetadataNames.LogoutSessionSupported, Required = Required.Default)] + [JsonPropertyName(OpenIdProviderMetadataNames.LogoutSessionSupported)] public bool LogoutSessionSupported { get; set; } /// /// Gets or sets the 'op_policy_uri' /// - [JsonProperty(DefaultValueHandling = DefaultValueHandling.Ignore, NullValueHandling = NullValueHandling.Ignore, PropertyName = OpenIdProviderMetadataNames.OpPolicyUri, Required = Required.Default)] + [JsonPropertyName(OpenIdProviderMetadataNames.OpPolicyUri)] +#if NET6_0_OR_GREATER + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] +#endif public string OpPolicyUri { get; set; } /// /// Gets or sets the 'op_tos_uri' /// - [JsonProperty(DefaultValueHandling = DefaultValueHandling.Ignore, NullValueHandling = NullValueHandling.Ignore, PropertyName = OpenIdProviderMetadataNames.OpTosUri, Required = Required.Default)] + [JsonPropertyName(OpenIdProviderMetadataNames.OpTosUri)] +#if NET6_0_OR_GREATER + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] +#endif public string OpTosUri { get; set; } /// /// Gets or sets the 'registration_endpoint' /// - [JsonProperty(DefaultValueHandling = DefaultValueHandling.Ignore, NullValueHandling = NullValueHandling.Ignore, PropertyName = OpenIdProviderMetadataNames.RegistrationEndpoint, Required = Required.Default)] + [JsonPropertyName(OpenIdProviderMetadataNames.RegistrationEndpoint)] +#if NET6_0_OR_GREATER + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] +#endif public string RegistrationEndpoint { get; set; } /// /// Gets the collection of 'request_object_encryption_alg_values_supported'. /// - [JsonProperty(DefaultValueHandling = DefaultValueHandling.Ignore, NullValueHandling = NullValueHandling.Ignore, PropertyName = OpenIdProviderMetadataNames.RequestObjectEncryptionAlgValuesSupported, Required = Required.Default)] - public ICollection RequestObjectEncryptionAlgValuesSupported { get; } = new Collection(); + [JsonPropertyName(OpenIdProviderMetadataNames.RequestObjectEncryptionAlgValuesSupported)] + public ICollection RequestObjectEncryptionAlgValuesSupported => + _requestObjectEncryptionAlgValuesSupported ?? + Interlocked.CompareExchange(ref _requestObjectEncryptionAlgValuesSupported, new Collection(), null) ?? + _requestObjectEncryptionAlgValuesSupported; /// /// Gets the collection of 'request_object_encryption_enc_values_supported'. /// - [JsonProperty(DefaultValueHandling = DefaultValueHandling.Ignore, NullValueHandling = NullValueHandling.Ignore, PropertyName = OpenIdProviderMetadataNames.RequestObjectEncryptionEncValuesSupported, Required = Required.Default)] - public ICollection RequestObjectEncryptionEncValuesSupported { get; } = new Collection(); + [JsonPropertyName(OpenIdProviderMetadataNames.RequestObjectEncryptionEncValuesSupported)] + public ICollection RequestObjectEncryptionEncValuesSupported => + _requestObjectEncryptionEncValuesSupported ?? + Interlocked.CompareExchange(ref _requestObjectEncryptionEncValuesSupported, new Collection(), null) ?? + _requestObjectEncryptionEncValuesSupported; /// /// Gets the collection of 'request_object_signing_alg_values_supported'. /// - [JsonProperty(DefaultValueHandling = DefaultValueHandling.Ignore, NullValueHandling = NullValueHandling.Ignore, PropertyName = OpenIdProviderMetadataNames.RequestObjectSigningAlgValuesSupported, Required = Required.Default)] - public ICollection RequestObjectSigningAlgValuesSupported { get; } = new Collection(); + [JsonPropertyName(OpenIdProviderMetadataNames.RequestObjectSigningAlgValuesSupported)] + public ICollection RequestObjectSigningAlgValuesSupported => + _requestObjectSigningAlgValuesSupported ?? + Interlocked.CompareExchange(ref _requestObjectSigningAlgValuesSupported, new Collection(), null) ?? + _requestObjectSigningAlgValuesSupported; /// /// Gets or sets the 'request_parameter_supported' /// - [JsonProperty(DefaultValueHandling = DefaultValueHandling.Ignore, NullValueHandling = NullValueHandling.Ignore, PropertyName = OpenIdProviderMetadataNames.RequestParameterSupported, Required = Required.Default)] + [JsonPropertyName(OpenIdProviderMetadataNames.RequestParameterSupported)] +#if NET6_0_OR_GREATER + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] +#endif public bool RequestParameterSupported { get; set; } /// /// Gets or sets the 'request_uri_parameter_supported' /// - [JsonProperty(DefaultValueHandling = DefaultValueHandling.Ignore, NullValueHandling = NullValueHandling.Ignore, PropertyName = OpenIdProviderMetadataNames.RequestUriParameterSupported, Required = Required.Default)] + [JsonPropertyName(OpenIdProviderMetadataNames.RequestUriParameterSupported)] +#if NET6_0_OR_GREATER + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] +#endif public bool RequestUriParameterSupported { get; set; } /// /// Gets or sets the 'require_request_uri_registration' /// - [JsonProperty(DefaultValueHandling = DefaultValueHandling.Ignore, NullValueHandling = NullValueHandling.Ignore, PropertyName = OpenIdProviderMetadataNames.RequireRequestUriRegistration, Required = Required.Default)] + [JsonPropertyName(OpenIdProviderMetadataNames.RequireRequestUriRegistration)] +#if NET6_0_OR_GREATER + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] +#endif public bool RequireRequestUriRegistration { get; set; } /// /// Gets the collection of 'response_modes_supported'. /// - [JsonProperty(DefaultValueHandling = DefaultValueHandling.Ignore, NullValueHandling = NullValueHandling.Ignore, PropertyName = OpenIdProviderMetadataNames.ResponseModesSupported, Required = Required.Default)] - public ICollection ResponseModesSupported { get; } = new Collection(); + [JsonPropertyName(OpenIdProviderMetadataNames.ResponseModesSupported)] + public ICollection ResponseModesSupported => + _responseModesSupported ?? + Interlocked.CompareExchange(ref _responseModesSupported, new Collection(), null) ?? + _responseModesSupported; /// /// Gets the collection of 'response_types_supported'. /// - [JsonProperty(DefaultValueHandling = DefaultValueHandling.Ignore, NullValueHandling = NullValueHandling.Ignore, PropertyName = OpenIdProviderMetadataNames.ResponseTypesSupported, Required = Required.Default)] - public ICollection ResponseTypesSupported { get; } = new Collection(); + [JsonPropertyName(OpenIdProviderMetadataNames.ResponseTypesSupported)] + public ICollection ResponseTypesSupported => + _responseTypesSupported ?? + Interlocked.CompareExchange(ref _responseTypesSupported, new Collection(), null) ?? + _responseTypesSupported; /// /// Gets or sets the 'service_documentation' /// - [JsonProperty(DefaultValueHandling = DefaultValueHandling.Ignore, NullValueHandling = NullValueHandling.Ignore, PropertyName = OpenIdProviderMetadataNames.ServiceDocumentation, Required = Required.Default)] + [JsonPropertyName(OpenIdProviderMetadataNames.ServiceDocumentation)] +#if NET6_0_OR_GREATER + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] +#endif public string ServiceDocumentation { get; set; } /// /// Gets the collection of 'scopes_supported' /// - [JsonProperty(DefaultValueHandling = DefaultValueHandling.Ignore, NullValueHandling = NullValueHandling.Ignore, PropertyName = OpenIdProviderMetadataNames.ScopesSupported, Required = Required.Default)] - public ICollection ScopesSupported { get; } = new Collection(); + [JsonPropertyName(OpenIdProviderMetadataNames.ScopesSupported)] + public ICollection ScopesSupported => + _scopesSupported ?? + Interlocked.CompareExchange(ref _scopesSupported, new Collection(), null) ?? + _scopesSupported; /// /// Gets the that the IdentityProvider indicates are to be used signing tokens. @@ -313,13 +450,19 @@ public OpenIdConnectConfiguration(string json) /// /// Gets the collection of 'subject_types_supported'. /// - [JsonProperty(DefaultValueHandling = DefaultValueHandling.Ignore, NullValueHandling = NullValueHandling.Ignore, PropertyName = OpenIdProviderMetadataNames.SubjectTypesSupported, Required = Required.Default)] - public ICollection SubjectTypesSupported { get; } = new Collection(); + [JsonPropertyName(OpenIdProviderMetadataNames.SubjectTypesSupported)] + public ICollection SubjectTypesSupported => + _subjectTypesSupported ?? + Interlocked.CompareExchange(ref _subjectTypesSupported, new Collection(), null) ?? + _subjectTypesSupported; /// /// Gets or sets the 'token_endpoint'. /// - [JsonProperty(DefaultValueHandling = DefaultValueHandling.Ignore, NullValueHandling = NullValueHandling.Ignore, PropertyName = OpenIdProviderMetadataNames.TokenEndpoint, Required = Required.Default)] + [JsonPropertyName(OpenIdProviderMetadataNames.TokenEndpoint)] +#if NET6_0_OR_GREATER + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] +#endif public override string TokenEndpoint { get; set; } /// @@ -331,46 +474,69 @@ public OpenIdConnectConfiguration(string json) /// /// Gets the collection of 'token_endpoint_auth_methods_supported'. /// - [JsonProperty(DefaultValueHandling = DefaultValueHandling.Ignore, NullValueHandling = NullValueHandling.Ignore, PropertyName = OpenIdProviderMetadataNames.TokenEndpointAuthMethodsSupported, Required = Required.Default)] - public ICollection TokenEndpointAuthMethodsSupported { get; } = new Collection(); + [JsonPropertyName(OpenIdProviderMetadataNames.TokenEndpointAuthMethodsSupported)] + public ICollection TokenEndpointAuthMethodsSupported => + _tokenEndpointAuthMethodsSupported ?? + Interlocked.CompareExchange(ref _tokenEndpointAuthMethodsSupported, new Collection(), null) ?? + _tokenEndpointAuthMethodsSupported; /// /// Gets the collection of 'token_endpoint_auth_signing_alg_values_supported'. /// - [JsonProperty(DefaultValueHandling = DefaultValueHandling.Ignore, NullValueHandling = NullValueHandling.Ignore, PropertyName = OpenIdProviderMetadataNames.TokenEndpointAuthSigningAlgValuesSupported, Required = Required.Default)] - public ICollection TokenEndpointAuthSigningAlgValuesSupported { get; } = new Collection(); + [JsonPropertyName(OpenIdProviderMetadataNames.TokenEndpointAuthSigningAlgValuesSupported)] + public ICollection TokenEndpointAuthSigningAlgValuesSupported => + _tokenEndpointAuthSigningAlgValuesSupported ?? + Interlocked.CompareExchange(ref _tokenEndpointAuthSigningAlgValuesSupported, new Collection(), null) ?? + _tokenEndpointAuthSigningAlgValuesSupported; /// /// Gets the collection of 'ui_locales_supported' /// - [JsonProperty(DefaultValueHandling = DefaultValueHandling.Ignore, NullValueHandling = NullValueHandling.Ignore, PropertyName = OpenIdProviderMetadataNames.UILocalesSupported, Required = Required.Default)] - public ICollection UILocalesSupported { get; } = new Collection(); + [JsonPropertyName(OpenIdProviderMetadataNames.UILocalesSupported)] + public ICollection UILocalesSupported => + _uILocalesSupported ?? + Interlocked.CompareExchange(ref _uILocalesSupported, new Collection(), null) ?? + _uILocalesSupported; /// /// Gets or sets the 'user_info_endpoint'. /// - [JsonProperty(DefaultValueHandling = DefaultValueHandling.Ignore, NullValueHandling = NullValueHandling.Ignore, PropertyName = OpenIdProviderMetadataNames.UserInfoEndpoint, Required = Required.Default)] + [JsonPropertyName(OpenIdProviderMetadataNames.UserInfoEndpoint)] +#if NET6_0_OR_GREATER + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] +#endif public string UserInfoEndpoint { get; set; } /// /// Gets the collection of 'userinfo_encryption_alg_values_supported' /// - [JsonProperty(DefaultValueHandling = DefaultValueHandling.Ignore, NullValueHandling = NullValueHandling.Ignore, PropertyName = OpenIdProviderMetadataNames.UserInfoEncryptionAlgValuesSupported, Required = Required.Default)] - public ICollection UserInfoEndpointEncryptionAlgValuesSupported { get; } = new Collection(); + [JsonPropertyName(OpenIdProviderMetadataNames.UserInfoEncryptionAlgValuesSupported)] + public ICollection UserInfoEndpointEncryptionAlgValuesSupported => + _userInfoEndpointEncryptionAlgValuesSupported ?? + Interlocked.CompareExchange(ref _userInfoEndpointEncryptionAlgValuesSupported, new Collection(), null) ?? + _userInfoEndpointEncryptionAlgValuesSupported; /// /// Gets the collection of 'userinfo_encryption_enc_values_supported' /// - [JsonProperty(DefaultValueHandling = DefaultValueHandling.Ignore, NullValueHandling = NullValueHandling.Ignore, PropertyName = OpenIdProviderMetadataNames.UserInfoEncryptionEncValuesSupported, Required = Required.Default)] - public ICollection UserInfoEndpointEncryptionEncValuesSupported { get; } = new Collection(); + [JsonPropertyName(OpenIdProviderMetadataNames.UserInfoEncryptionEncValuesSupported)] + public ICollection UserInfoEndpointEncryptionEncValuesSupported => + _userInfoEndpointEncryptionEncValuesSupported ?? + Interlocked.CompareExchange(ref _userInfoEndpointEncryptionEncValuesSupported, new Collection(), null) ?? + _userInfoEndpointEncryptionEncValuesSupported; /// /// Gets the collection of 'userinfo_signing_alg_values_supported' /// - [JsonProperty(DefaultValueHandling = DefaultValueHandling.Ignore, NullValueHandling = NullValueHandling.Ignore, PropertyName = OpenIdProviderMetadataNames.UserInfoSigningAlgValuesSupported, Required = Required.Default)] - public ICollection UserInfoEndpointSigningAlgValuesSupported { get; } = new Collection(); + [JsonPropertyName(OpenIdProviderMetadataNames.UserInfoSigningAlgValuesSupported)] + public ICollection UserInfoEndpointSigningAlgValuesSupported => + _userInfoEndpointSigningAlgValuesSupported ?? + Interlocked.CompareExchange(ref _userInfoEndpointSigningAlgValuesSupported, new Collection(), null) ?? + _userInfoEndpointSigningAlgValuesSupported; -#region shouldserialize + #region shouldserialize + // TODO - should we keep these, they were used by Newtonsoft to control serialization of collections. + // May help users to keep them hanging around. /// /// Gets a bool that determines if the 'acr_values_supported' (AcrValuesSupported) property should be serialized. /// This is used by Json.NET in order to conditionally serialize properties. diff --git a/src/Microsoft.IdentityModel.Protocols.OpenIdConnect/Configuration/OpenIdConnectConfigurationRetriever.cs b/src/Microsoft.IdentityModel.Protocols.OpenIdConnect/Configuration/OpenIdConnectConfigurationRetriever.cs index 22f7dc5421..17b354b390 100644 --- a/src/Microsoft.IdentityModel.Protocols.OpenIdConnect/Configuration/OpenIdConnectConfigurationRetriever.cs +++ b/src/Microsoft.IdentityModel.Protocols.OpenIdConnect/Configuration/OpenIdConnectConfigurationRetriever.cs @@ -7,7 +7,6 @@ using Microsoft.IdentityModel.Abstractions; using Microsoft.IdentityModel.Logging; using Microsoft.IdentityModel.Tokens; -using Newtonsoft.Json; namespace Microsoft.IdentityModel.Protocols.OpenIdConnect { @@ -68,7 +67,7 @@ public static async Task GetAsync(string address, ID if (LogHelper.IsEnabled(EventLogLevel.Verbose)) LogHelper.LogVerbose(LogMessages.IDX21811, doc); - OpenIdConnectConfiguration openIdConnectConfiguration = JsonConvert.DeserializeObject(doc); + OpenIdConnectConfiguration openIdConnectConfiguration = OpenIdConnectConfigurationSerializer.Read(doc); if (!string.IsNullOrEmpty(openIdConnectConfiguration.JwksUri)) { if (LogHelper.IsEnabled(EventLogLevel.Verbose)) diff --git a/src/Microsoft.IdentityModel.Protocols.OpenIdConnect/Json/OpenIdConnectConfigurationSerializer.cs b/src/Microsoft.IdentityModel.Protocols.OpenIdConnect/Json/OpenIdConnectConfigurationSerializer.cs new file mode 100644 index 0000000000..1eace64244 --- /dev/null +++ b/src/Microsoft.IdentityModel.Protocols.OpenIdConnect/Json/OpenIdConnectConfigurationSerializer.cs @@ -0,0 +1,610 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Collections.Generic; +using System.IO; +using System.Text; +using System.Text.Encodings.Web; +using System.Text.Json; +using Microsoft.IdentityModel.Logging; +using Utf8Bytes = Microsoft.IdentityModel.Protocols.OpenIdConnect.OpenIdProviderMetadataUtf8Bytes; +using JsonPrimitives = Microsoft.IdentityModel.Tokens.Json.JsonSerializerPrimitives; +using MetadataName = Microsoft.IdentityModel.Protocols.OpenIdConnect.OpenIdProviderMetadataNames; + +namespace Microsoft.IdentityModel.Protocols.OpenIdConnect +{ + internal static class OpenIdConnectConfigurationSerializer + { + public const string ClassName = OpenIdConnectConfiguration.ClassName; + + // This is used to perform performant case-insensitive property names. + // 6x used Newtonsoft and was case-insensitive w.r.t. property names. + // The serializer is written to use Utf8JsonReader.ValueTextEquals(...), to match property names. + // When we do not have a match, we check the uppercase name of the property against this table. + // If not found, then we assume we should put the value into AdditionalData. + // If we didn't do that, we would pay a performance penalty for those cases where there is AdditionalData + // but otherwise the JSON properties are all lower case. + public static HashSet OpenIdProviderMetadataNamesUpperCase = new HashSet + { + "ACR_VALUES_SUPPORTED", + "AUTHORIZATION_ENDPOINT", + "CHECK_SESSION_IFRAME", + "CLAIMS_LOCALES_SUPPORTED", + "CLAIMS_PARAMETER_SUPPORTED", + "CLAIMS_SUPPORTED", + "CLAIM_TYPES_SUPPORTED", + ".WELL-KNOWN/OPENID-CONFIGURATION", + "DISPLAY_VALUES_SUPPORTED", + "END_SESSION_ENDPOINT", + "FRONTCHANNEL_LOGOUT_SESSION_SUPPORTED", + "FRONTCHANNEL_LOGOUT_SUPPORTED", + "HTTP_LOGOUT_SUPPORTED", + "GRANT_TYPES_SUPPORTED", + "ID_TOKEN_ENCRYPTION_ALG_VALUES_SUPPORTED", + "ID_TOKEN_ENCRYPTION_ENC_VALUES_SUPPORTED", + "ID_TOKEN_SIGNING_ALG_VALUES_SUPPORTED", + "INTROSPECTION_ENDPOINT", + "INTROSPECTION_ENDPOINT_AUTH_METHODS_SUPPORTED", + "INTROSPECTION_ENDPOINT_AUTH_SIGNING_ALG_VALUES_SUPPORTED", + "JWKS_URI", + "ISSUER", + "LOGOUT_SESSION_SUPPORTED", + "OP_POLICY_URI", + "OP_TOS_URI", + "REGISTRATION_ENDPOINT", + "REQUEST_OBJECT_ENCRYPTION_ALG_VALUES_SUPPORTED", + "REQUEST_OBJECT_ENCRYPTION_ENC_VALUES_SUPPORTED", + "REQUEST_OBJECT_SIGNING_ALG_VALUES_SUPPORTED", + "REQUEST_PARAMETER_SUPPORTED", + "REQUEST_URI_PARAMETER_SUPPORTED", + "REQUIRE_REQUEST_URI_REGISTRATION", + "RESPONSE_MODES_SUPPORTED", + "RESPONSE_TYPES_SUPPORTED", + "SERVICE_DOCUMENTATION", + "SCOPES_SUPPORTED", + "SUBJECT_TYPES_SUPPORTED", + "TOKEN_ENDPOINT", + "TOKEN_ENDPOINT_AUTH_METHODS_SUPPORTED", + "TOKEN_ENDPOINT_AUTH_SIGNING_ALG_VALUES_SUPPORTED", + "UI_LOCALES_SUPPORTED", + "USERINFO_ENDPOINT", + "USERINFO_ENCRYPTION_ALG_VALUES_SUPPORTED", + "USERINFO_ENCRYPTION_ENC_VALUES_SUPPORTED", + "USERINFO_SIGNING_ALG_VALUES_SUPPORTED", + }; + + #region Read + public static OpenIdConnectConfiguration Read(string json) + { + return Read(json, new OpenIdConnectConfiguration()); + } + + public static OpenIdConnectConfiguration Read(string json, OpenIdConnectConfiguration config) + { + Utf8JsonReader reader = new(Encoding.UTF8.GetBytes(json).AsSpan()); + return Read(ref reader, config); + } + + /// + /// Reads config. see: https://openid.net/specs/openid-connect-discovery-1_0.html + /// + /// a pointing at a StartObject. + /// + /// A . + public static OpenIdConnectConfiguration Read(ref Utf8JsonReader reader, OpenIdConnectConfiguration config) + { + if (!JsonPrimitives.IsReaderAtTokenType(ref reader, JsonTokenType.StartObject, false)) + throw LogHelper.LogExceptionMessage( + new JsonException( + LogHelper.FormatInvariant( + Tokens.LogMessages.IDX11023, + LogHelper.MarkAsNonPII("JsonTokenType.StartObject"), + LogHelper.MarkAsNonPII(reader.TokenType), + LogHelper.MarkAsNonPII(ClassName), + LogHelper.MarkAsNonPII(reader.TokenStartIndex), + LogHelper.MarkAsNonPII(reader.CurrentDepth), + LogHelper.MarkAsNonPII(reader.BytesConsumed)))); + + while(JsonPrimitives.ReaderRead(ref reader)) + { + #region Check property name using ValueTextEquals + // the config spec, https://datatracker.ietf.org/doc/html/rfc7517#section-4, does not require that we reject JSON with + // duplicate member names, in strict mode, we could add logic to try a property once and throw if a duplicate shows up. + // 6x uses the last value. + // TODO - With collections, make sure two properties are not additive + if (reader.TokenType == JsonTokenType.PropertyName) + { + if (reader.ValueTextEquals(Utf8Bytes.AcrValuesSupported)) + JsonPrimitives.ReadStrings(ref reader, config.AcrValuesSupported, MetadataName.AcrValuesSupported, ClassName, true); + + else if (reader.ValueTextEquals(Utf8Bytes.AuthorizationEndpoint)) + config.AuthorizationEndpoint = JsonPrimitives.ReadString(ref reader, MetadataName.AuthorizationEndpoint, ClassName, true); + + else if (reader.ValueTextEquals(Utf8Bytes.CheckSessionIframe)) + config.CheckSessionIframe = JsonPrimitives.ReadString(ref reader, MetadataName.CheckSessionIframe, ClassName, true); + + else if (reader.ValueTextEquals(Utf8Bytes.ClaimsLocalesSupported)) + JsonPrimitives.ReadStrings(ref reader, config.ClaimsLocalesSupported, MetadataName.ClaimsLocalesSupported, ClassName, true); + + else if (reader.ValueTextEquals(Utf8Bytes.ClaimsParameterSupported)) + config.ClaimsParameterSupported = JsonPrimitives.ReadBoolean(ref reader, MetadataName.ClaimsParameterSupported, ClassName, true); + + else if (reader.ValueTextEquals(Utf8Bytes.ClaimsSupported)) + JsonPrimitives.ReadStrings(ref reader, config.ClaimsSupported, MetadataName.ClaimsSupported, ClassName, true); + + else if (reader.ValueTextEquals(Utf8Bytes.ClaimTypesSupported)) + JsonPrimitives.ReadStrings(ref reader, config.ClaimTypesSupported, MetadataName.ClaimTypesSupported, ClassName, true); + + else if (reader.ValueTextEquals(Utf8Bytes.DisplayValuesSupported)) + JsonPrimitives.ReadStrings(ref reader, config.DisplayValuesSupported, MetadataName.DisplayValuesSupported, ClassName, true); + + else if (reader.ValueTextEquals(Utf8Bytes.EndSessionEndpoint)) + config.EndSessionEndpoint = JsonPrimitives.ReadString(ref reader, MetadataName.EndSessionEndpoint, ClassName, true); + + // TODO these two properties are per spec 'boolean', we shipped 6x with them as string, if we change we may break folks. + // probably best to mark the property obsolete with the gentle tag, then open up another property and keep them in sync, + // remove the obsolete in 8.x + else if (reader.ValueTextEquals(Utf8Bytes.FrontchannelLogoutSessionSupported)) + { + reader.Read(); + if (reader.TokenType == JsonTokenType.True) + config.FrontchannelLogoutSessionSupported = "True"; + else if (reader.TokenType == JsonTokenType.False) + config.FrontchannelLogoutSessionSupported = "False"; + else + config.FrontchannelLogoutSessionSupported = JsonPrimitives.ReadString(ref reader, MetadataName.FrontchannelLogoutSessionSupported, ClassName, false); + } + else if (reader.ValueTextEquals(Utf8Bytes.FrontchannelLogoutSupported)) + { + reader.Read(); + if (reader.TokenType == JsonTokenType.True) + config.FrontchannelLogoutSupported = "True"; + else if (reader.TokenType == JsonTokenType.False) + config.FrontchannelLogoutSupported = "False"; + else + config.FrontchannelLogoutSupported = JsonPrimitives.ReadString(ref reader, MetadataName.FrontchannelLogoutSupported, ClassName, false); + } + else if (reader.ValueTextEquals(Utf8Bytes.GrantTypesSupported)) + JsonPrimitives.ReadStrings(ref reader, config.GrantTypesSupported, MetadataName.GrantTypesSupported, ClassName, true); + + else if (reader.ValueTextEquals(Utf8Bytes.HttpLogoutSupported)) + config.HttpLogoutSupported = JsonPrimitives.ReadBoolean(ref reader, MetadataName.HttpLogoutSupported, ClassName, true); + + else if (reader.ValueTextEquals(Utf8Bytes.IdTokenEncryptionAlgValuesSupported)) + JsonPrimitives.ReadStrings(ref reader, config.IdTokenEncryptionAlgValuesSupported, MetadataName.IdTokenEncryptionAlgValuesSupported, ClassName, true); + + else if (reader.ValueTextEquals(Utf8Bytes.IdTokenEncryptionEncValuesSupported)) + JsonPrimitives.ReadStrings(ref reader, config.IdTokenEncryptionEncValuesSupported, MetadataName.IdTokenEncryptionEncValuesSupported, ClassName, true); + + else if (reader.ValueTextEquals(Utf8Bytes.IdTokenSigningAlgValuesSupported)) + JsonPrimitives.ReadStrings(ref reader, config.IdTokenSigningAlgValuesSupported, MetadataName.IdTokenSigningAlgValuesSupported, ClassName, true); + + else if (reader.ValueTextEquals(Utf8Bytes.IntrospectionEndpoint)) + config.IntrospectionEndpoint = JsonPrimitives.ReadString(ref reader, MetadataName.IntrospectionEndpoint, ClassName, true); + + else if (reader.ValueTextEquals(Utf8Bytes.IntrospectionEndpointAuthMethodsSupported)) + JsonPrimitives.ReadStrings(ref reader, config.IntrospectionEndpointAuthMethodsSupported, MetadataName.IntrospectionEndpointAuthMethodsSupported, ClassName, true); + + else if (reader.ValueTextEquals(Utf8Bytes.IntrospectionEndpointAuthSigningAlgValuesSupported)) + JsonPrimitives.ReadStrings(ref reader, config.IntrospectionEndpointAuthSigningAlgValuesSupported, MetadataName.IntrospectionEndpointAuthSigningAlgValuesSupported, ClassName, true); + + else if (reader.ValueTextEquals(Utf8Bytes.Issuer)) + config.Issuer = JsonPrimitives.ReadString(ref reader, MetadataName.Issuer, ClassName, true); + + else if (reader.ValueTextEquals(Utf8Bytes.JwksUri)) + config.JwksUri = JsonPrimitives.ReadString(ref reader, MetadataName.JwksUri, ClassName, true); + + else if (reader.ValueTextEquals(Utf8Bytes.LogoutSessionSupported)) + config.LogoutSessionSupported = JsonPrimitives.ReadBoolean(ref reader, MetadataName.LogoutSessionSupported, ClassName, true); + + else if (reader.ValueTextEquals(Utf8Bytes.OpPolicyUri)) + config.OpPolicyUri = JsonPrimitives.ReadString(ref reader, MetadataName.OpPolicyUri, ClassName, true); + + else if (reader.ValueTextEquals(Utf8Bytes.OpTosUri)) + config.OpTosUri = JsonPrimitives.ReadString(ref reader, MetadataName.OpTosUri, ClassName, true); + + else if (reader.ValueTextEquals(Utf8Bytes.RegistrationEndpoint)) + config.RegistrationEndpoint = JsonPrimitives.ReadString(ref reader, MetadataName.RegistrationEndpoint, ClassName, true); + + else if (reader.ValueTextEquals(Utf8Bytes.RequestObjectEncryptionAlgValuesSupported)) + JsonPrimitives.ReadStrings(ref reader, config.RequestObjectEncryptionAlgValuesSupported, MetadataName.RequestObjectEncryptionAlgValuesSupported, ClassName, true); + + else if (reader.ValueTextEquals(Utf8Bytes.RequestObjectEncryptionEncValuesSupported)) + JsonPrimitives.ReadStrings(ref reader, config.RequestObjectEncryptionEncValuesSupported, MetadataName.RequestObjectEncryptionEncValuesSupported, ClassName, true); + + else if (reader.ValueTextEquals(Utf8Bytes.RequestObjectSigningAlgValuesSupported)) + JsonPrimitives.ReadStrings(ref reader, config.RequestObjectSigningAlgValuesSupported, MetadataName.RequestObjectSigningAlgValuesSupported, ClassName, true); + + else if (reader.ValueTextEquals(Utf8Bytes.RequestParameterSupported)) + config.RequestParameterSupported = JsonPrimitives.ReadBoolean(ref reader, MetadataName.RequestParameterSupported, ClassName, true); + + else if (reader.ValueTextEquals(Utf8Bytes.RequestUriParameterSupported)) + config.RequestUriParameterSupported = JsonPrimitives.ReadBoolean(ref reader, MetadataName.RequestUriParameterSupported, ClassName, true); + + else if (reader.ValueTextEquals(Utf8Bytes.RequireRequestUriRegistration)) + config.RequireRequestUriRegistration = JsonPrimitives.ReadBoolean(ref reader, MetadataName.RequireRequestUriRegistration, ClassName, true); + + else if (reader.ValueTextEquals(Utf8Bytes.ResponseModesSupported)) + JsonPrimitives.ReadStrings(ref reader, config.ResponseModesSupported, MetadataName.ResponseModesSupported, ClassName, true); + + else if (reader.ValueTextEquals(Utf8Bytes.ResponseTypesSupported)) + JsonPrimitives.ReadStrings(ref reader, config.ResponseTypesSupported, MetadataName.ResponseTypesSupported, ClassName, true); + + else if (reader.ValueTextEquals(Utf8Bytes.ScopesSupported)) + JsonPrimitives.ReadStrings(ref reader, config.ScopesSupported, MetadataName.ScopesSupported, ClassName, true); + + else if (reader.ValueTextEquals(Utf8Bytes.ServiceDocumentation)) + config.ServiceDocumentation = JsonPrimitives.ReadString(ref reader, MetadataName.ScopesSupported, ClassName, true); + + else if (reader.ValueTextEquals(Utf8Bytes.SubjectTypesSupported)) + JsonPrimitives.ReadStrings(ref reader, config.SubjectTypesSupported, MetadataName.SubjectTypesSupported, ClassName, true); + + else if (reader.ValueTextEquals(Utf8Bytes.SubjectTypesSupported)) + JsonPrimitives.ReadStrings(ref reader, config.SubjectTypesSupported, MetadataName.SubjectTypesSupported, ClassName, true); + + else if (reader.ValueTextEquals(Utf8Bytes.TokenEndpoint)) + config.TokenEndpoint = JsonPrimitives.ReadString(ref reader, MetadataName.TokenEndpoint, ClassName, true); + + else if (reader.ValueTextEquals(Utf8Bytes.TokenEndpointAuthMethodsSupported)) + JsonPrimitives.ReadStrings(ref reader, config.TokenEndpointAuthMethodsSupported, MetadataName.TokenEndpointAuthMethodsSupported, ClassName, true); + + else if (reader.ValueTextEquals(Utf8Bytes.TokenEndpointAuthSigningAlgValuesSupported)) + JsonPrimitives.ReadStrings(ref reader, config.TokenEndpointAuthSigningAlgValuesSupported, MetadataName.TokenEndpointAuthSigningAlgValuesSupported, ClassName, true); + + else if (reader.ValueTextEquals(Utf8Bytes.UILocalesSupported)) + JsonPrimitives.ReadStrings(ref reader, config.UILocalesSupported, MetadataName.UILocalesSupported, ClassName, true); + + else if (reader.ValueTextEquals(Utf8Bytes.UserInfoEncryptionAlgValuesSupported)) + JsonPrimitives.ReadStrings(ref reader, config.UserInfoEndpointEncryptionAlgValuesSupported, MetadataName.UserInfoEncryptionAlgValuesSupported, ClassName, true); + + else if (reader.ValueTextEquals(Utf8Bytes.UserInfoEncryptionEncValuesSupported)) + JsonPrimitives.ReadStrings(ref reader, config.UserInfoEndpointEncryptionEncValuesSupported, MetadataName.UserInfoEncryptionEncValuesSupported, ClassName, true); + + else if (reader.ValueTextEquals(Utf8Bytes.UserInfoEndpoint)) + config.UserInfoEndpoint = JsonPrimitives.ReadString(ref reader, MetadataName.ScopesSupported, ClassName, true); + + else if (reader.ValueTextEquals(Utf8Bytes.UserInfoSigningAlgValuesSupported)) + JsonPrimitives.ReadStrings(ref reader, config.UserInfoEndpointSigningAlgValuesSupported, MetadataName.UserInfoSigningAlgValuesSupported, ClassName, true); + #endregion + else + { + #region case-insensitive + string propertyName = JsonPrimitives.GetPropertyName(ref reader, OpenIdConnectConfiguration.ClassName, true); + + // fallback to checking property names as case insensitive + // first check to see if the upper case property value is a valid property name if not add to AdditionalData, to avoid unnecessary string compares. + if (!OpenIdProviderMetadataNamesUpperCase.Contains(propertyName.ToUpperInvariant())) + { + config.AdditionalData[propertyName] = JsonPrimitives.GetUnknownProperty(ref reader); + } + else + { + if (propertyName.Equals(MetadataName.AcrValuesSupported, StringComparison.OrdinalIgnoreCase)) + JsonPrimitives.ReadStrings(ref reader, config.AcrValuesSupported, MetadataName.AcrValuesSupported, ClassName, true); + + else if (propertyName.Equals(MetadataName.AuthorizationEndpoint, StringComparison.OrdinalIgnoreCase)) + config.AuthorizationEndpoint = JsonPrimitives.ReadString(ref reader, MetadataName.AuthorizationEndpoint, ClassName, true); + + else if (propertyName.Equals(MetadataName.CheckSessionIframe, StringComparison.OrdinalIgnoreCase)) + config.CheckSessionIframe = JsonPrimitives.ReadString(ref reader, MetadataName.CheckSessionIframe, ClassName, true); + + else if (propertyName.Equals(MetadataName.ClaimsLocalesSupported, StringComparison.OrdinalIgnoreCase)) + JsonPrimitives.ReadStrings(ref reader, config.ClaimsLocalesSupported, MetadataName.ClaimsLocalesSupported, ClassName, true); + + else if (propertyName.Equals(MetadataName.ClaimsParameterSupported, StringComparison.OrdinalIgnoreCase)) + config.ClaimsParameterSupported = JsonPrimitives.ReadBoolean(ref reader, MetadataName.ClaimsParameterSupported, ClassName, true); + + else if (propertyName.Equals(MetadataName.ClaimsSupported, StringComparison.OrdinalIgnoreCase)) + JsonPrimitives.ReadStrings(ref reader, config.ClaimsSupported, MetadataName.ClaimsSupported, ClassName, true); + + else if (propertyName.Equals(MetadataName.ClaimTypesSupported, StringComparison.OrdinalIgnoreCase)) + JsonPrimitives.ReadStrings(ref reader, config.ClaimTypesSupported, MetadataName.ClaimTypesSupported, ClassName, true); + + else if (propertyName.Equals(MetadataName.DisplayValuesSupported, StringComparison.OrdinalIgnoreCase)) + JsonPrimitives.ReadStrings(ref reader, config.DisplayValuesSupported, MetadataName.DisplayValuesSupported, ClassName, true); + + else if (propertyName.Equals(MetadataName.EndSessionEndpoint, StringComparison.OrdinalIgnoreCase)) + config.EndSessionEndpoint = JsonPrimitives.ReadString(ref reader, MetadataName.EndSessionEndpoint, ClassName, true); + + // TODO these two properties are per spec 'boolean', we shipped 6x with them as string, if we change we may break folks. + // probably best to mark the property obsolete with the gentle tag, then open up another property and keep them in sync, + // remove the obsolete in 8.x + else if (propertyName.Equals(MetadataName.FrontchannelLogoutSessionSupported, StringComparison.OrdinalIgnoreCase)) + { + reader.Read(); + if (reader.TokenType == JsonTokenType.True) + config.FrontchannelLogoutSessionSupported = "True"; + else if (reader.TokenType == JsonTokenType.False) + config.FrontchannelLogoutSessionSupported = "False"; + else + config.FrontchannelLogoutSessionSupported = JsonPrimitives.ReadString(ref reader, MetadataName.FrontchannelLogoutSessionSupported, ClassName, false); + } + else if (propertyName.Equals(MetadataName.FrontchannelLogoutSupported, StringComparison.OrdinalIgnoreCase)) + { + reader.Read(); + if (reader.TokenType == JsonTokenType.True) + config.FrontchannelLogoutSupported = "True"; + else if (reader.TokenType == JsonTokenType.False) + config.FrontchannelLogoutSupported = "False"; + else + config.FrontchannelLogoutSupported = JsonPrimitives.ReadString(ref reader, MetadataName.FrontchannelLogoutSupported, ClassName, false); + } + else if (propertyName.Equals(MetadataName.GrantTypesSupported, StringComparison.OrdinalIgnoreCase)) + JsonPrimitives.ReadStrings(ref reader, config.GrantTypesSupported, MetadataName.GrantTypesSupported, ClassName, true); + + else if (propertyName.Equals(MetadataName.HttpLogoutSupported, StringComparison.OrdinalIgnoreCase)) + config.HttpLogoutSupported = JsonPrimitives.ReadBoolean(ref reader, MetadataName.HttpLogoutSupported, ClassName, true); + + else if (propertyName.Equals(MetadataName.IdTokenEncryptionAlgValuesSupported, StringComparison.OrdinalIgnoreCase)) + JsonPrimitives.ReadStrings(ref reader, config.IdTokenEncryptionAlgValuesSupported, MetadataName.IdTokenEncryptionAlgValuesSupported, ClassName, true); + + else if (propertyName.Equals(MetadataName.IdTokenEncryptionEncValuesSupported, StringComparison.OrdinalIgnoreCase)) + JsonPrimitives.ReadStrings(ref reader, config.IdTokenEncryptionEncValuesSupported, MetadataName.IdTokenEncryptionEncValuesSupported, ClassName, true); + + else if (propertyName.Equals(MetadataName.IdTokenSigningAlgValuesSupported , StringComparison.OrdinalIgnoreCase)) + JsonPrimitives.ReadStrings(ref reader, config.IdTokenSigningAlgValuesSupported, MetadataName.IdTokenSigningAlgValuesSupported, ClassName, true); + + else if (propertyName.Equals(MetadataName. IntrospectionEndpoint, StringComparison.OrdinalIgnoreCase)) + config.IntrospectionEndpoint = JsonPrimitives.ReadString(ref reader, MetadataName.IntrospectionEndpoint, ClassName, true); + + else if (propertyName.Equals(MetadataName. IntrospectionEndpointAuthMethodsSupported, StringComparison.OrdinalIgnoreCase)) + JsonPrimitives.ReadStrings(ref reader, config.IntrospectionEndpointAuthMethodsSupported, MetadataName.IntrospectionEndpointAuthMethodsSupported, ClassName, true); + + else if (propertyName.Equals(MetadataName. IntrospectionEndpointAuthSigningAlgValuesSupported, StringComparison.OrdinalIgnoreCase)) + JsonPrimitives.ReadStrings(ref reader, config.IntrospectionEndpointAuthSigningAlgValuesSupported, MetadataName.IntrospectionEndpointAuthSigningAlgValuesSupported, ClassName, true); + + else if (propertyName.Equals(MetadataName. Issuer, StringComparison.OrdinalIgnoreCase)) + config.Issuer = JsonPrimitives.ReadString(ref reader, MetadataName.Issuer, ClassName, true); + + else if (propertyName.Equals(MetadataName. JwksUri, StringComparison.OrdinalIgnoreCase)) + config.JwksUri = JsonPrimitives.ReadString(ref reader, MetadataName.JwksUri, ClassName, true); + + else if (propertyName.Equals(MetadataName. LogoutSessionSupported, StringComparison.OrdinalIgnoreCase)) + config.LogoutSessionSupported = JsonPrimitives.ReadBoolean(ref reader, MetadataName.LogoutSessionSupported, ClassName, true); + + else if (propertyName.Equals(MetadataName. OpPolicyUri, StringComparison.OrdinalIgnoreCase)) + config.OpPolicyUri = JsonPrimitives.ReadString(ref reader, MetadataName.OpPolicyUri, ClassName, true); + + else if (propertyName.Equals(MetadataName. OpTosUri, StringComparison.OrdinalIgnoreCase)) + config.OpTosUri = JsonPrimitives.ReadString(ref reader, MetadataName.OpTosUri, ClassName, true); + + else if (propertyName.Equals(MetadataName. RegistrationEndpoint, StringComparison.OrdinalIgnoreCase)) + config.RegistrationEndpoint = JsonPrimitives.ReadString(ref reader, MetadataName.RegistrationEndpoint, ClassName, true); + + else if (propertyName.Equals(MetadataName. RequestObjectEncryptionAlgValuesSupported, StringComparison.OrdinalIgnoreCase)) + JsonPrimitives.ReadStrings(ref reader, config.RequestObjectEncryptionAlgValuesSupported, MetadataName.RequestObjectEncryptionAlgValuesSupported, ClassName, true); + + else if (propertyName.Equals(MetadataName. RequestObjectEncryptionEncValuesSupported, StringComparison.OrdinalIgnoreCase)) + JsonPrimitives.ReadStrings(ref reader, config.RequestObjectEncryptionEncValuesSupported, MetadataName.RequestObjectEncryptionEncValuesSupported, ClassName, true); + + else if (propertyName.Equals(MetadataName. RequestObjectSigningAlgValuesSupported, StringComparison.OrdinalIgnoreCase)) + JsonPrimitives.ReadStrings(ref reader, config.RequestObjectSigningAlgValuesSupported, MetadataName.RequestObjectSigningAlgValuesSupported, ClassName, true); + + else if (propertyName.Equals(MetadataName. RequestParameterSupported, StringComparison.OrdinalIgnoreCase)) + config.RequestParameterSupported = JsonPrimitives.ReadBoolean(ref reader, MetadataName.RequestParameterSupported, ClassName, true); + + else if (propertyName.Equals(MetadataName. RequestUriParameterSupported, StringComparison.OrdinalIgnoreCase)) + config.RequestUriParameterSupported = JsonPrimitives.ReadBoolean(ref reader, MetadataName.RequestUriParameterSupported, ClassName, true); + + else if (propertyName.Equals(MetadataName. RequireRequestUriRegistration, StringComparison.OrdinalIgnoreCase)) + config.RequireRequestUriRegistration = JsonPrimitives.ReadBoolean(ref reader, MetadataName.RequireRequestUriRegistration, ClassName, true); + + else if (propertyName.Equals(MetadataName. ResponseModesSupported, StringComparison.OrdinalIgnoreCase)) + JsonPrimitives.ReadStrings(ref reader, config.ResponseModesSupported, MetadataName.ResponseModesSupported, ClassName, true); + + else if (propertyName.Equals(MetadataName. ResponseTypesSupported, StringComparison.OrdinalIgnoreCase)) + JsonPrimitives.ReadStrings(ref reader, config.ResponseTypesSupported, MetadataName.ResponseTypesSupported, ClassName, true); + + else if (propertyName.Equals(MetadataName. ScopesSupported, StringComparison.OrdinalIgnoreCase)) + JsonPrimitives.ReadStrings(ref reader, config.ScopesSupported, MetadataName.ScopesSupported, ClassName, true); + + else if (propertyName.Equals(MetadataName. ServiceDocumentation, StringComparison.OrdinalIgnoreCase)) + config.ServiceDocumentation = JsonPrimitives.ReadString(ref reader, MetadataName.ScopesSupported, ClassName, true); + + else if (propertyName.Equals(MetadataName. SubjectTypesSupported, StringComparison.OrdinalIgnoreCase)) + JsonPrimitives.ReadStrings(ref reader, config.SubjectTypesSupported, MetadataName.SubjectTypesSupported, ClassName, true); + + else if (propertyName.Equals(MetadataName. TokenEndpoint, StringComparison.OrdinalIgnoreCase)) + config.TokenEndpoint = JsonPrimitives.ReadString(ref reader, MetadataName.TokenEndpoint, ClassName, true); + + else if (propertyName.Equals(MetadataName. TokenEndpointAuthMethodsSupported, StringComparison.OrdinalIgnoreCase)) + JsonPrimitives.ReadStrings(ref reader, config.TokenEndpointAuthMethodsSupported, MetadataName.TokenEndpointAuthMethodsSupported, ClassName, true); + + else if (propertyName.Equals(MetadataName. TokenEndpointAuthSigningAlgValuesSupported, StringComparison.OrdinalIgnoreCase)) + JsonPrimitives.ReadStrings(ref reader, config.TokenEndpointAuthSigningAlgValuesSupported, MetadataName.TokenEndpointAuthSigningAlgValuesSupported, ClassName, true); + + else if (propertyName.Equals(MetadataName. UILocalesSupported, StringComparison.OrdinalIgnoreCase)) + JsonPrimitives.ReadStrings(ref reader, config.UILocalesSupported, MetadataName.UILocalesSupported, ClassName, true); + + else if (propertyName.Equals(MetadataName. UserInfoEncryptionAlgValuesSupported, StringComparison.OrdinalIgnoreCase)) + JsonPrimitives.ReadStrings(ref reader, config.UserInfoEndpointEncryptionAlgValuesSupported, MetadataName.UserInfoEncryptionAlgValuesSupported, ClassName, true); + + else if (propertyName.Equals(MetadataName. UserInfoEncryptionEncValuesSupported, StringComparison.OrdinalIgnoreCase)) + JsonPrimitives.ReadStrings(ref reader, config.UserInfoEndpointEncryptionEncValuesSupported, MetadataName.UserInfoEncryptionEncValuesSupported, ClassName, true); + + else if (propertyName.Equals(MetadataName. UserInfoEndpoint, StringComparison.OrdinalIgnoreCase)) + config.UserInfoEndpoint = JsonPrimitives.ReadString(ref reader, MetadataName.ScopesSupported, ClassName, true); + + else if (propertyName.Equals(MetadataName. UserInfoSigningAlgValuesSupported, StringComparison.OrdinalIgnoreCase)) + JsonPrimitives.ReadStrings(ref reader, config.UserInfoEndpointSigningAlgValuesSupported, MetadataName.UserInfoSigningAlgValuesSupported, ClassName, true); + + } + #endregion case-insensitive + } + } + + if (JsonPrimitives.IsReaderAtTokenType(ref reader, JsonTokenType.EndObject, false)) + break; + } + + return config; + } + #endregion + + #region Write + public static string Write(OpenIdConnectConfiguration OpenIdConnectConfiguration) + { + using (MemoryStream memoryStream = new MemoryStream()) + { + Utf8JsonWriter writer = null; + try + { + writer = new Utf8JsonWriter(memoryStream, new JsonWriterOptions { Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping }); + Write(ref writer, OpenIdConnectConfiguration); + writer.Flush(); + return Encoding.UTF8.GetString(memoryStream.GetBuffer(), 0, (int)memoryStream.Length); + } + finally + { + writer?.Dispose(); + } + } + } + + public static void Write(ref Utf8JsonWriter writer, OpenIdConnectConfiguration config) + { + writer.WriteStartObject(); + + if (config.AcrValuesSupported.Count > 0) + JsonPrimitives.WriteStrings(ref writer, Utf8Bytes.AcrValuesSupported, config.AcrValuesSupported); + + if (!string.IsNullOrEmpty(config.AuthorizationEndpoint)) + writer.WriteString(Utf8Bytes.AuthorizationEndpoint, config.AuthorizationEndpoint); + + if (!string.IsNullOrEmpty(config.CheckSessionIframe)) + writer.WriteString(Utf8Bytes.CheckSessionIframe, config.CheckSessionIframe); + + if (config.ClaimsSupported.Count > 0) + JsonPrimitives.WriteStrings(ref writer, Utf8Bytes.ClaimsSupported, config.ClaimsSupported); + + if (config.ClaimsLocalesSupported.Count > 0) + JsonPrimitives.WriteStrings(ref writer, Utf8Bytes.ClaimsLocalesSupported, config.ClaimsLocalesSupported); + + if (config.ClaimsParameterSupported) + writer.WriteBoolean(Utf8Bytes.ClaimsParameterSupported, config.ClaimsParameterSupported); + + if (config.ClaimTypesSupported.Count > 0) + JsonPrimitives.WriteStrings(ref writer, Utf8Bytes.ClaimTypesSupported, config.ClaimTypesSupported); + + if (config.DisplayValuesSupported.Count > 0) + JsonPrimitives.WriteStrings(ref writer, Utf8Bytes.DisplayValuesSupported, config.DisplayValuesSupported); + + if (!string.IsNullOrEmpty(config.EndSessionEndpoint)) + writer.WriteString(Utf8Bytes.EndSessionEndpoint, config.EndSessionEndpoint); + + if (!string.IsNullOrEmpty(config.FrontchannelLogoutSessionSupported)) + writer.WriteString(Utf8Bytes.FrontchannelLogoutSessionSupported, config.FrontchannelLogoutSessionSupported); + + if (!string.IsNullOrEmpty(config.FrontchannelLogoutSupported)) + writer.WriteString(Utf8Bytes.FrontchannelLogoutSupported, config.FrontchannelLogoutSupported); + + if (config.GrantTypesSupported.Count > 0) + JsonPrimitives.WriteStrings(ref writer, Utf8Bytes.GrantTypesSupported, config.GrantTypesSupported); + + if (config.HttpLogoutSupported) + writer.WriteBoolean(Utf8Bytes.HttpLogoutSupported, config.HttpLogoutSupported); + + if (config.IdTokenEncryptionAlgValuesSupported.Count > 0) + JsonPrimitives.WriteStrings(ref writer, Utf8Bytes.IdTokenEncryptionAlgValuesSupported, config.IdTokenEncryptionAlgValuesSupported); + + if (config.IdTokenEncryptionEncValuesSupported.Count > 0) + JsonPrimitives.WriteStrings(ref writer, Utf8Bytes.IdTokenEncryptionEncValuesSupported, config.IdTokenEncryptionEncValuesSupported); + + if (config.IdTokenSigningAlgValuesSupported.Count > 0) + JsonPrimitives.WriteStrings(ref writer, Utf8Bytes.IdTokenSigningAlgValuesSupported, config.IdTokenSigningAlgValuesSupported); + + if (!string.IsNullOrEmpty(config.IntrospectionEndpoint)) + writer.WriteString(Utf8Bytes.IntrospectionEndpoint, config.IntrospectionEndpoint); + + if (config.IntrospectionEndpointAuthMethodsSupported.Count > 0) + JsonPrimitives.WriteStrings(ref writer, Utf8Bytes.IntrospectionEndpointAuthMethodsSupported, config.IntrospectionEndpointAuthMethodsSupported); + + if (config.IntrospectionEndpointAuthSigningAlgValuesSupported.Count > 0) + JsonPrimitives.WriteStrings(ref writer, Utf8Bytes.IntrospectionEndpointAuthSigningAlgValuesSupported, config.IntrospectionEndpointAuthSigningAlgValuesSupported); + + if (!string.IsNullOrEmpty(config.Issuer)) + writer.WriteString(Utf8Bytes.Issuer, config.Issuer); + + if (!string.IsNullOrEmpty(config.JwksUri)) + writer.WriteString(Utf8Bytes.JwksUri, config.JwksUri); + + if (config.LogoutSessionSupported) + writer.WriteBoolean(Utf8Bytes.LogoutSessionSupported, config.LogoutSessionSupported); + + if (!string.IsNullOrEmpty(config.OpPolicyUri)) + writer.WriteString(Utf8Bytes.OpPolicyUri, config.OpPolicyUri); + + if (!string.IsNullOrEmpty(config.OpTosUri)) + writer.WriteString(Utf8Bytes.OpTosUri, config.OpTosUri); + + if (!string.IsNullOrEmpty(config.RegistrationEndpoint)) + writer.WriteString(Utf8Bytes.RegistrationEndpoint, config.RegistrationEndpoint); + + if (config.RequestObjectEncryptionAlgValuesSupported.Count > 0) + JsonPrimitives.WriteStrings(ref writer, Utf8Bytes.RequestObjectEncryptionAlgValuesSupported, config.RequestObjectEncryptionAlgValuesSupported); + + if (config.RequestObjectEncryptionEncValuesSupported.Count > 0) + JsonPrimitives.WriteStrings(ref writer, Utf8Bytes.RequestObjectEncryptionEncValuesSupported, config.RequestObjectEncryptionEncValuesSupported); + + if (config.RequestObjectSigningAlgValuesSupported.Count > 0) + JsonPrimitives.WriteStrings(ref writer, Utf8Bytes.RequestObjectSigningAlgValuesSupported, config.RequestObjectSigningAlgValuesSupported); + + if (config.RequestParameterSupported) + writer.WriteBoolean(Utf8Bytes.RequestParameterSupported, config.RequestParameterSupported); + + if (config.RequestUriParameterSupported) + writer.WriteBoolean(Utf8Bytes.RequestUriParameterSupported, config.RequestUriParameterSupported); + + if (config.RequireRequestUriRegistration) + writer.WriteBoolean(Utf8Bytes.RequireRequestUriRegistration, config.RequireRequestUriRegistration); + + if (config.ResponseModesSupported.Count > 0) + JsonPrimitives.WriteStrings(ref writer, Utf8Bytes.ResponseModesSupported, config.ResponseModesSupported); + + if (config.ResponseTypesSupported.Count > 0) + JsonPrimitives.WriteStrings(ref writer, Utf8Bytes.ResponseTypesSupported, config.ResponseTypesSupported); + + if (config.ScopesSupported.Count > 0) + JsonPrimitives.WriteStrings(ref writer, Utf8Bytes.ScopesSupported, config.ScopesSupported); + + if (!string.IsNullOrEmpty(config.ServiceDocumentation)) + writer.WriteString(Utf8Bytes.ServiceDocumentation, config.ServiceDocumentation); + + if (config.SubjectTypesSupported.Count > 0) + JsonPrimitives.WriteStrings(ref writer, Utf8Bytes.SubjectTypesSupported, config.SubjectTypesSupported); + + if (!string.IsNullOrEmpty(config.TokenEndpoint)) + writer.WriteString(Utf8Bytes.TokenEndpoint, config.TokenEndpoint); + + if (config.TokenEndpointAuthMethodsSupported.Count > 0) + JsonPrimitives.WriteStrings(ref writer, Utf8Bytes.TokenEndpointAuthMethodsSupported, config.TokenEndpointAuthMethodsSupported); + + if (config.TokenEndpointAuthSigningAlgValuesSupported.Count > 0) + JsonPrimitives.WriteStrings(ref writer, Utf8Bytes.TokenEndpointAuthSigningAlgValuesSupported, config.TokenEndpointAuthSigningAlgValuesSupported); + + if (config.UILocalesSupported.Count > 0) + JsonPrimitives.WriteStrings(ref writer, Utf8Bytes.UILocalesSupported, config.UILocalesSupported); + + if (!string.IsNullOrEmpty(config.UserInfoEndpoint)) + writer.WriteString(Utf8Bytes.UserInfoEndpoint, config.UserInfoEndpoint); + + if (config.UserInfoEndpointEncryptionAlgValuesSupported.Count > 0) + JsonPrimitives.WriteStrings(ref writer, Utf8Bytes.UserInfoEncryptionAlgValuesSupported, config.UserInfoEndpointEncryptionAlgValuesSupported); + + if (config.UserInfoEndpointEncryptionEncValuesSupported.Count > 0) + JsonPrimitives.WriteStrings(ref writer, Utf8Bytes.UserInfoEncryptionEncValuesSupported, config.UserInfoEndpointEncryptionEncValuesSupported); + + if (config.UserInfoEndpointSigningAlgValuesSupported.Count > 0) + JsonPrimitives.WriteStrings(ref writer, Utf8Bytes.UserInfoSigningAlgValuesSupported, config.UserInfoEndpointSigningAlgValuesSupported); + + if (config.AdditionalData.Count > 0) + JsonPrimitives.WriteAdditionalData(ref writer, config.AdditionalData); + + writer.WriteEndObject(); + } + #endregion + } +} + diff --git a/src/Microsoft.IdentityModel.Protocols.OpenIdConnect/OpenIdConnectMessage.cs b/src/Microsoft.IdentityModel.Protocols.OpenIdConnect/OpenIdConnectMessage.cs index 6f4e45c955..7ed8fa69c5 100644 --- a/src/Microsoft.IdentityModel.Protocols.OpenIdConnect/OpenIdConnectMessage.cs +++ b/src/Microsoft.IdentityModel.Protocols.OpenIdConnect/OpenIdConnectMessage.cs @@ -5,8 +5,10 @@ using System.Collections.Generic; using System.Collections.Specialized; using System.Reflection; +using System.Text; +using System.Text.Json; using Microsoft.IdentityModel.Logging; -using Newtonsoft.Json.Linq; +using JsonPrimitives = Microsoft.IdentityModel.Tokens.Json.JsonSerializerPrimitives; namespace Microsoft.IdentityModel.Protocols.OpenIdConnect { @@ -15,6 +17,8 @@ namespace Microsoft.IdentityModel.Protocols.OpenIdConnect /// public class OpenIdConnectMessage : AuthenticationProtocolMessage { + internal const string ClassName = "Microsoft.IdentityModel.Protocols.OpenIdConnect.OpenIdConnectMessage"; + /// /// Initializes a new instance of the class. /// @@ -30,7 +34,7 @@ public OpenIdConnectMessage(string json) try { - SetJsonParameters(JObject.Parse(json)); + SetJsonParameters(json); } catch { @@ -82,7 +86,7 @@ public OpenIdConnectMessage(NameValueCollection nameValueCollection) /// /// Initializes a new instance of the class. /// - /// Enumeration of key value pairs. + /// Enumeration of key value pairs. public OpenIdConnectMessage(IEnumerable> parameters) { if (parameters == null) @@ -104,30 +108,20 @@ public OpenIdConnectMessage(IEnumerable> paramete } } - /// - /// Initializes a new instance of the class. - /// - /// The JSON object from which the instance is created. - [Obsolete("The 'OpenIdConnectMessage(object json)' constructor is obsolete. Please use 'OpenIdConnectMessage(string json)' instead.")] - public OpenIdConnectMessage(object json) - { - if (json == null) - throw LogHelper.LogArgumentNullException(nameof(json)); - - var jObject = JObject.Parse(json.ToString()); - SetJsonParameters(jObject); - } - - private void SetJsonParameters(JObject json) + private void SetJsonParameters(string json) { - if (json == null) - throw LogHelper.LogArgumentNullException("json"); + Utf8JsonReader reader = new(Encoding.UTF8.GetBytes(json).AsSpan()); - foreach (var pair in json) + while (JsonPrimitives.ReaderRead(ref reader)) { - if (json.TryGetValue(pair.Key, out JToken value)) + if (reader.TokenType == JsonTokenType.PropertyName) { - SetParameter(pair.Key, value.ToString()); + string propertyName = JsonPrimitives.GetPropertyName(ref reader, ClassName, true); + string propertyValue = null; + if (reader.TokenType == JsonTokenType.String) + propertyValue = JsonPrimitives.ReadString(ref reader, propertyName, ClassName); + + SetParameter(propertyName, propertyValue); } } } diff --git a/src/Microsoft.IdentityModel.Protocols.OpenIdConnect/OpenIdConnectParameterNames.cs b/src/Microsoft.IdentityModel.Protocols.OpenIdConnect/OpenIdConnectParameterNames.cs index 7a5903d484..623fd73ff1 100644 --- a/src/Microsoft.IdentityModel.Protocols.OpenIdConnect/OpenIdConnectParameterNames.cs +++ b/src/Microsoft.IdentityModel.Protocols.OpenIdConnect/OpenIdConnectParameterNames.cs @@ -1,6 +1,8 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. +using System; + namespace Microsoft.IdentityModel.Protocols.OpenIdConnect { /// @@ -53,4 +55,53 @@ public static class OpenIdConnectParameterNames public const string VersionTelemetry = "x-client-ver"; #pragma warning restore 1591 } + + /// + /// Parameter names for OpenIdConnect UTF8 bytes. + /// + internal static class OpenIdConnectParameterUtf8Bytes + { + public static ReadOnlySpan AccessToken => "access_token"u8; + public static ReadOnlySpan AcrValues => "acr_values"u8; + public static ReadOnlySpan ClaimsLocales => "claims_locales"u8; + public static ReadOnlySpan ClientAssertion => "client_assertion"u8; + public static ReadOnlySpan ClientAssertionType => "client_assertion_type"u8; + public static ReadOnlySpan ClientId => "client_id"u8; + public static ReadOnlySpan ClientSecret => "client_secret"u8; + public static ReadOnlySpan Code => "code"u8; + public static ReadOnlySpan Display => "display"u8; + public static ReadOnlySpan DomainHint => "domain_hint"u8; + public static ReadOnlySpan Error => "error"u8; + public static ReadOnlySpan ErrorDescription => "error_description"u8; + public static ReadOnlySpan ErrorUri => "error_uri"u8; + public static ReadOnlySpan ExpiresIn => "expires_in"u8; + public static ReadOnlySpan GrantType => "grant_type"u8; + public static ReadOnlySpan Iss => "iss"u8; + public static ReadOnlySpan IdToken => "id_token"u8; + public static ReadOnlySpan IdTokenHint => "id_token_hint"u8; + public static ReadOnlySpan IdentityProvider => "identity_provider"u8; + public static ReadOnlySpan LoginHint => "login_hint"u8; + public static ReadOnlySpan MaxAge => "max_age"u8; + public static ReadOnlySpan Nonce => "nonce"u8; + public static ReadOnlySpan Password => "password"u8; + public static ReadOnlySpan PostLogoutRedirectUri => "post_logout_redirect_uri"u8; + public static ReadOnlySpan Prompt => "prompt"u8; + public static ReadOnlySpan RedirectUri => "redirect_uri"u8; + public static ReadOnlySpan RefreshToken => "refresh_token"u8; + public static ReadOnlySpan RequestUri => "request_uri"u8; + public static ReadOnlySpan Resource => "resource"u8; + public static ReadOnlySpan ResponseMode => "response_mode"u8; + public static ReadOnlySpan ResponseType => "response_type"u8; + public static ReadOnlySpan Scope => "scope"u8; + public static ReadOnlySpan SkuTelemetry => "x-client-SKU"u8; + public static ReadOnlySpan SessionState => "session_state"u8; + public static ReadOnlySpan Sid => "sid"u8; + public static ReadOnlySpan State => "state"u8; + public static ReadOnlySpan TargetLinkUri => "target_link_uri"u8; + public static ReadOnlySpan TokenType => "token_type"u8; + public static ReadOnlySpan UiLocales => "ui_locales"u8; + public static ReadOnlySpan UserId => "user_id"u8; + public static ReadOnlySpan Username => "username"u8; + public static ReadOnlySpan VersionTelemetry => "x-client-ver"u8; + } } diff --git a/src/Microsoft.IdentityModel.Protocols.OpenIdConnect/OpenIdProviderMetadataNames.cs b/src/Microsoft.IdentityModel.Protocols.OpenIdConnect/OpenIdProviderMetadataNames.cs index 36057d8708..4f2cb60bbb 100644 --- a/src/Microsoft.IdentityModel.Protocols.OpenIdConnect/OpenIdProviderMetadataNames.cs +++ b/src/Microsoft.IdentityModel.Protocols.OpenIdConnect/OpenIdProviderMetadataNames.cs @@ -1,10 +1,12 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. +using System; + namespace Microsoft.IdentityModel.Protocols.OpenIdConnect { /// - /// OpenIdProviderConfiguration Names + /// OpenIdProviderConfiguration MetadataName /// http://openid.net/specs/openid-connect-discovery-1_0.html#ProviderMetadata /// public static class OpenIdProviderMetadataNames @@ -58,4 +60,58 @@ public static class OpenIdProviderMetadataNames public const string UserInfoSigningAlgValuesSupported = "userinfo_signing_alg_values_supported"; #pragma warning restore 1591 } + + /// + /// OpenIdProviderConfiguration MetadataName - UTF8Bytes + /// http://openid.net/specs/openid-connect-discovery-1_0.html#ProviderMetadata + /// + internal static class OpenIdProviderMetadataUtf8Bytes + { + public static ReadOnlySpan AcrValuesSupported => "acr_values_supported"u8; + public static ReadOnlySpan AuthorizationEndpoint => "authorization_endpoint"u8; + public static ReadOnlySpan CheckSessionIframe => "check_session_iframe"u8; + public static ReadOnlySpan ClaimsLocalesSupported => "claims_locales_supported"u8; + public static ReadOnlySpan ClaimsParameterSupported => "claims_parameter_supported"u8; + public static ReadOnlySpan ClaimsSupported => "claims_supported"u8; + public static ReadOnlySpan ClaimTypesSupported => "claim_types_supported"u8; + public static ReadOnlySpan Discovery => ".well-known/openid-configuration"u8; + public static ReadOnlySpan DisplayValuesSupported => "display_values_supported"u8; + public static ReadOnlySpan EndSessionEndpoint => "end_session_endpoint"u8; + public static ReadOnlySpan FrontchannelLogoutSessionSupported => "frontchannel_logout_session_supported"u8; + public static ReadOnlySpan FrontchannelLogoutSupported => "frontchannel_logout_supported"u8; + public static ReadOnlySpan HttpLogoutSupported => "http_logout_supported"u8; + public static ReadOnlySpan GrantTypesSupported => "grant_types_supported"u8; + public static ReadOnlySpan IdTokenEncryptionAlgValuesSupported => "id_token_encryption_alg_values_supported"u8; + public static ReadOnlySpan IdTokenEncryptionEncValuesSupported => "id_token_encryption_enc_values_supported"u8; + public static ReadOnlySpan IdTokenSigningAlgValuesSupported => "id_token_signing_alg_values_supported"u8; + public static ReadOnlySpan IntrospectionEndpoint => "introspection_endpoint"u8; + public static ReadOnlySpan IntrospectionEndpointAuthMethodsSupported => "introspection_endpoint_auth_methods_supported"u8; + public static ReadOnlySpan IntrospectionEndpointAuthSigningAlgValuesSupported => "introspection_endpoint_auth_signing_alg_values_supported"u8; + public static ReadOnlySpan JwksUri => "jwks_uri"u8; + public static ReadOnlySpan Issuer => "issuer"u8; + public static ReadOnlySpan LogoutSessionSupported => "logout_session_supported"u8; + public static ReadOnlySpan MicrosoftMultiRefreshToken => "microsoft_multi_refresh_token"u8; + public static ReadOnlySpan OpPolicyUri => "op_policy_uri"u8; + public static ReadOnlySpan OpTosUri => "op_tos_uri"u8; + public static ReadOnlySpan RegistrationEndpoint => "registration_endpoint"u8; + public static ReadOnlySpan RequestObjectEncryptionAlgValuesSupported => "request_object_encryption_alg_values_supported"u8; + public static ReadOnlySpan RequestObjectEncryptionEncValuesSupported => "request_object_encryption_enc_values_supported"u8; + public static ReadOnlySpan RequestObjectSigningAlgValuesSupported => "request_object_signing_alg_values_supported"u8; + public static ReadOnlySpan RequestParameterSupported => "request_parameter_supported"u8; + public static ReadOnlySpan RequestUriParameterSupported => "request_uri_parameter_supported"u8; + public static ReadOnlySpan RequireRequestUriRegistration => "require_request_uri_registration"u8; + public static ReadOnlySpan ResponseModesSupported => "response_modes_supported"u8; + public static ReadOnlySpan ResponseTypesSupported => "response_types_supported"u8; + public static ReadOnlySpan ServiceDocumentation => "service_documentation"u8; + public static ReadOnlySpan ScopesSupported => "scopes_supported"u8; + public static ReadOnlySpan SubjectTypesSupported => "subject_types_supported"u8; + public static ReadOnlySpan TokenEndpoint => "token_endpoint"u8; + public static ReadOnlySpan TokenEndpointAuthMethodsSupported => "token_endpoint_auth_methods_supported"u8; + public static ReadOnlySpan TokenEndpointAuthSigningAlgValuesSupported => "token_endpoint_auth_signing_alg_values_supported"u8; + public static ReadOnlySpan UILocalesSupported => "ui_locales_supported"u8; + public static ReadOnlySpan UserInfoEndpoint => "userinfo_endpoint"u8; + public static ReadOnlySpan UserInfoEncryptionAlgValuesSupported => "userinfo_encryption_alg_values_supported"u8; + public static ReadOnlySpan UserInfoEncryptionEncValuesSupported => "userinfo_encryption_enc_values_supported"u8; + public static ReadOnlySpan UserInfoSigningAlgValuesSupported => "userinfo_signing_alg_values_supported"u8; + } } diff --git a/src/Microsoft.IdentityModel.Protocols.SignedHttpRequest/SignedHttpRequestDescriptor.cs b/src/Microsoft.IdentityModel.Protocols.SignedHttpRequest/SignedHttpRequestDescriptor.cs index 7f0b2893c9..bec080e981 100644 --- a/src/Microsoft.IdentityModel.Protocols.SignedHttpRequest/SignedHttpRequestDescriptor.cs +++ b/src/Microsoft.IdentityModel.Protocols.SignedHttpRequest/SignedHttpRequestDescriptor.cs @@ -1,28 +1,5 @@ -//------------------------------------------------------------------------------ -// -// Copyright (c) Microsoft Corporation. -// All rights reserved. -// -// This code is licensed under the MIT License. -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files(the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and / or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions : -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -// THE SOFTWARE. -// +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. using System.Collections.Generic; using Microsoft.IdentityModel.Logging; diff --git a/src/Microsoft.IdentityModel.Protocols.SignedHttpRequest/SignedHttpRequestHandler.cs b/src/Microsoft.IdentityModel.Protocols.SignedHttpRequest/SignedHttpRequestHandler.cs index 73e215909c..d393ebbdf1 100644 --- a/src/Microsoft.IdentityModel.Protocols.SignedHttpRequest/SignedHttpRequestHandler.cs +++ b/src/Microsoft.IdentityModel.Protocols.SignedHttpRequest/SignedHttpRequestHandler.cs @@ -1,43 +1,23 @@ -//------------------------------------------------------------------------------ -// -// Copyright (c) Microsoft Corporation. -// All rights reserved. -// -// This code is licensed under the MIT License. -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files(the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and / or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions : -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -// THE SOFTWARE. -// +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. using System; using System.Collections.Generic; +using System.IO; using System.Linq; using System.Net.Http; using System.Security.Cryptography; using System.Text; +using System.Text.Encodings.Web; +using System.Text.Json; using System.Threading; using System.Threading.Tasks; using Microsoft.IdentityModel.Abstractions; using Microsoft.IdentityModel.JsonWebTokens; using Microsoft.IdentityModel.Logging; using Microsoft.IdentityModel.Tokens; -using Newtonsoft.Json; using Newtonsoft.Json.Linq; +using JsonPrimitives = Microsoft.IdentityModel.Tokens.Json.JsonSerializerPrimitives; namespace Microsoft.IdentityModel.Protocols.SignedHttpRequest { @@ -79,30 +59,78 @@ public string CreateSignedHttpRequest(SignedHttpRequestDescriptor signedHttpRequ /// A signed http request as a JWS in Compact Serialization Format. public string CreateSignedHttpRequest(SignedHttpRequestDescriptor signedHttpRequestDescriptor, CallContext callContext) { + if (callContext != null) + LogHelper.LogVerbose(callContext.ActivityId.ToString()); + if (signedHttpRequestDescriptor == null) throw LogHelper.LogArgumentNullException(nameof(signedHttpRequestDescriptor)); if (signedHttpRequestDescriptor.SigningCredentials == null) throw LogHelper.LogArgumentNullException(nameof(signedHttpRequestDescriptor.SigningCredentials)); - var payload = CreateHttpRequestPayload(signedHttpRequestDescriptor, callContext); - var header = new JObject(); - if (signedHttpRequestDescriptor.AdditionalHeaderClaims != null && signedHttpRequestDescriptor.AdditionalHeaderClaims.Count != 0) + string encodedPayload; + using (MemoryStream memoryStream = new MemoryStream()) { - if (signedHttpRequestDescriptor.AdditionalHeaderClaims.Keys.Intersect(JwtTokenUtilities.DefaultHeaderParameters, StringComparer.OrdinalIgnoreCase).Any()) - throw LogHelper.LogExceptionMessage(new SecurityTokenException(LogHelper.FormatInvariant(JsonWebTokens.LogMessages.IDX14116, LogHelper.MarkAsNonPII(nameof(signedHttpRequestDescriptor.AdditionalHeaderClaims)), LogHelper.MarkAsNonPII(string.Join(", ", JwtTokenUtilities.DefaultHeaderParameters))))); - header.Merge(JObject.FromObject(signedHttpRequestDescriptor.AdditionalHeaderClaims)); + Utf8JsonWriter payloadWriter = null; + try + { + payloadWriter = new Utf8JsonWriter(memoryStream, new JsonWriterOptions { Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping }); + payloadWriter.WriteStartObject(); + + CreateHttpRequestPayload(ref payloadWriter, signedHttpRequestDescriptor); + + payloadWriter.WriteEndObject(); + payloadWriter.Flush(); + encodedPayload = Base64UrlEncoder.Encode(memoryStream.GetBuffer(), 0, (int)memoryStream.Length); + } + finally + { + payloadWriter?.Dispose(); + } } - // set the "typ" header claim to "pop" - // https://datatracker.ietf.org/doc/html/draft-ietf-oauth-signed-http-request-03#section-6.2 - header[JwtHeaderParameterNames.Alg] = signedHttpRequestDescriptor.SigningCredentials.Algorithm; - header[JwtHeaderParameterNames.Typ] = SignedHttpRequestConstants.TokenType; + string encodedHeader; + using (MemoryStream memoryStream = new MemoryStream()) + { + Utf8JsonWriter headerWriter = null; + try + { + headerWriter = new Utf8JsonWriter(memoryStream, new JsonWriterOptions { Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping }); + headerWriter.WriteStartObject(); + + if (signedHttpRequestDescriptor.AdditionalHeaderClaims != null && signedHttpRequestDescriptor.AdditionalHeaderClaims.Count != 0) + { + if (signedHttpRequestDescriptor.AdditionalHeaderClaims.Keys.Intersect(JwtTokenUtilities.DefaultHeaderParameters, StringComparer.OrdinalIgnoreCase).Any()) + throw LogHelper.LogExceptionMessage( + new SecurityTokenException( + LogHelper.FormatInvariant( + JsonWebTokens.LogMessages.IDX14116, + LogHelper.MarkAsNonPII(nameof(signedHttpRequestDescriptor.AdditionalHeaderClaims)), + LogHelper.MarkAsNonPII(string.Join(", ", JwtTokenUtilities.DefaultHeaderParameters))))); + } - var message = Base64UrlEncoder.Encode(Encoding.UTF8.GetBytes(header.ToString(Formatting.None))) + "." + Base64UrlEncoder.Encode(Encoding.UTF8.GetBytes(payload)); + // set the "typ" header claim to "pop" + // https://datatracker.ietf.org/doc/html/draft-ietf-oauth-signed-http-request-03#section-6.2 + headerWriter.WriteString(JwtHeaderParameterNames.Alg, signedHttpRequestDescriptor.SigningCredentials.Algorithm); + headerWriter.WriteString(JwtHeaderParameterNames.Typ, SignedHttpRequestConstants.TokenType); + if (signedHttpRequestDescriptor.AdditionalHeaderClaims != null) + foreach (string key in signedHttpRequestDescriptor.AdditionalHeaderClaims.Keys) + headerWriter.WriteString(key, signedHttpRequestDescriptor.AdditionalHeaderClaims[key].ToString()); - return message + "." + JwtTokenUtilities.CreateEncodedSignature(message, signedHttpRequestDescriptor.SigningCredentials, false); + headerWriter.WriteEndObject(); + headerWriter.Flush(); + encodedHeader = Base64UrlEncoder.Encode(memoryStream.GetBuffer(), 0, (int)memoryStream.Length); + + } + finally + { + headerWriter?.Dispose(); + } + + string message = encodedHeader + "." + encodedPayload; + return message + "." + JwtTokenUtilities.CreateEncodedSignature(message, signedHttpRequestDescriptor.SigningCredentials, false); + } } /// @@ -114,187 +142,179 @@ public string CreateSignedHttpRequest(SignedHttpRequestDescriptor signedHttpRequ /// /// Users can utilize to create additional claim(s) and add them to the signed http request. /// - protected virtual string CreateHttpRequestPayload(SignedHttpRequestDescriptor signedHttpRequestDescriptor, CallContext callContext) + protected internal virtual string CreateHttpRequestPayload(SignedHttpRequestDescriptor signedHttpRequestDescriptor, CallContext callContext) { if (signedHttpRequestDescriptor == null) throw LogHelper.LogArgumentNullException(nameof(signedHttpRequestDescriptor)); - Dictionary payload = new Dictionary(); + Utf8JsonWriter writer = null; + using (MemoryStream memoryStream = new MemoryStream()) + { + try + { + writer = new Utf8JsonWriter(memoryStream, new JsonWriterOptions { Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping }); + writer.WriteStartObject(); + + CreateHttpRequestPayload(ref writer, signedHttpRequestDescriptor); + + writer.WriteEndObject(); + writer.Flush(); + return Encoding.UTF8.GetString(memoryStream.GetBuffer(), 0, (int)memoryStream.Length); + } + finally + { + writer?.Dispose(); + } + } + } - AddAtClaim(payload, signedHttpRequestDescriptor); + internal void CreateHttpRequestPayload(ref Utf8JsonWriter writer, SignedHttpRequestDescriptor signedHttpRequestDescriptor) + { + AddAtClaim(ref writer, signedHttpRequestDescriptor); if (signedHttpRequestDescriptor.SignedHttpRequestCreationParameters.CreateTs) - AddTsClaim(payload, signedHttpRequestDescriptor); + AddTsClaim(ref writer, signedHttpRequestDescriptor); if (signedHttpRequestDescriptor.SignedHttpRequestCreationParameters.CreateM) - AddMClaim(payload, signedHttpRequestDescriptor); + AddMClaim(ref writer, signedHttpRequestDescriptor); if (signedHttpRequestDescriptor.SignedHttpRequestCreationParameters.CreateU) - AddUClaim(payload, signedHttpRequestDescriptor); + AddUClaim(ref writer, signedHttpRequestDescriptor); if (signedHttpRequestDescriptor.SignedHttpRequestCreationParameters.CreateP) - AddPClaim(payload, signedHttpRequestDescriptor); + AddPClaim(ref writer, signedHttpRequestDescriptor); if (signedHttpRequestDescriptor.SignedHttpRequestCreationParameters.CreateQ) - AddQClaim(payload, signedHttpRequestDescriptor); + AddQClaim(ref writer, signedHttpRequestDescriptor); if (signedHttpRequestDescriptor.SignedHttpRequestCreationParameters.CreateH) - AddHClaim(payload, signedHttpRequestDescriptor); + AddHClaim(ref writer, signedHttpRequestDescriptor); if (signedHttpRequestDescriptor.SignedHttpRequestCreationParameters.CreateB) - AddBClaim(payload, signedHttpRequestDescriptor); + AddBClaim(ref writer, signedHttpRequestDescriptor); if (signedHttpRequestDescriptor.SignedHttpRequestCreationParameters.CreateNonce) - AddNonceClaim(payload, signedHttpRequestDescriptor); + AddNonceClaim(ref writer, signedHttpRequestDescriptor); if (signedHttpRequestDescriptor.SignedHttpRequestCreationParameters.CreateCnf) - AddCnfClaim(payload, signedHttpRequestDescriptor); + AddCnfClaim(ref writer, signedHttpRequestDescriptor); if (signedHttpRequestDescriptor.AdditionalPayloadClaims != null && signedHttpRequestDescriptor.AdditionalPayloadClaims.Any()) - { - foreach (var additionalPayloadClaim in signedHttpRequestDescriptor.AdditionalPayloadClaims) - { - if (!payload.ContainsKey(additionalPayloadClaim.Key)) - payload.Add(additionalPayloadClaim.Key, additionalPayloadClaim.Value); - } - } - - return JObject.FromObject(payload).ToString(Formatting.None); + JsonPrimitives.WriteAdditionalData(ref writer, signedHttpRequestDescriptor.AdditionalPayloadClaims); } /// - /// Adds the 'at' claim to the . + /// Adds the 'at' claim to the . /// - /// HttpRequest payload represented as a . + /// /// A structure that wraps parameters needed for SignedHttpRequest creation. - internal virtual void AddAtClaim(Dictionary payload, SignedHttpRequestDescriptor signedHttpRequestDescriptor) + internal virtual void AddAtClaim(ref Utf8JsonWriter writer, SignedHttpRequestDescriptor signedHttpRequestDescriptor) { - if (payload == null) - throw LogHelper.LogArgumentNullException(nameof(payload)); - - payload.Add(SignedHttpRequestClaimTypes.At, signedHttpRequestDescriptor.AccessToken); + // TODO - use JsonEncodedText for property names + writer.WriteString(SignedHttpRequestClaimTypes.At, signedHttpRequestDescriptor.AccessToken); } /// - /// Adds the 'ts' claim to the . + /// Adds the 'ts' claim to the . /// - /// HttpRequest payload represented as a . + /// /// A structure that wraps parameters needed for SignedHttpRequest creation. /// /// This method will be executed only if is set to true. /// - internal virtual void AddTsClaim(Dictionary payload, SignedHttpRequestDescriptor signedHttpRequestDescriptor) + internal virtual void AddTsClaim(ref Utf8JsonWriter writer, SignedHttpRequestDescriptor signedHttpRequestDescriptor) { - if (payload == null) - throw LogHelper.LogArgumentNullException(nameof(payload)); - - var signedHttpRequestCreationTime = DateTime.UtcNow.Add(signedHttpRequestDescriptor.SignedHttpRequestCreationParameters.TimeAdjustment); - payload.Add(SignedHttpRequestClaimTypes.Ts, EpochTime.GetIntDate(signedHttpRequestCreationTime)); + writer.WriteNumber( + SignedHttpRequestClaimTypes.Ts, + EpochTime.GetIntDate( + DateTime.UtcNow.Add(signedHttpRequestDescriptor.SignedHttpRequestCreationParameters.TimeAdjustment))); } /// - /// Adds the 'm' claim to the . + /// Adds the 'm' claim using the Utf8JsonWriter /// - /// HttpRequest payload represented as a . + /// /// A structure that wraps parameters needed for SignedHttpRequest creation. /// /// This method will be executed only if is set to true. /// - internal virtual void AddMClaim(Dictionary payload, SignedHttpRequestDescriptor signedHttpRequestDescriptor) + internal virtual void AddMClaim(ref Utf8JsonWriter writer, SignedHttpRequestDescriptor signedHttpRequestDescriptor) { - if (payload == null) - throw LogHelper.LogArgumentNullException(nameof(payload)); - - var httpMethod = signedHttpRequestDescriptor.HttpRequestData.Method; - - if (string.IsNullOrEmpty(httpMethod)) + if (string.IsNullOrEmpty(signedHttpRequestDescriptor.HttpRequestData.Method)) throw LogHelper.LogArgumentNullException(nameof(signedHttpRequestDescriptor.HttpRequestData.Method)); + var httpMethod = signedHttpRequestDescriptor.HttpRequestData.Method; if (!httpMethod.ToUpperInvariant().Equals(httpMethod)) throw LogHelper.LogExceptionMessage(new SignedHttpRequestCreationException(LogHelper.FormatInvariant(LogMessages.IDX23002, LogHelper.MarkAsNonPII(httpMethod)))); - payload.Add(SignedHttpRequestClaimTypes.M, httpMethod); + writer.WriteString(SignedHttpRequestClaimTypes.M, httpMethod); } /// - /// Adds the 'u' claim to the . + /// Adds the 'u' claim to the . /// - /// HttpRequest payload represented as a . + /// /// A structure that wraps parameters needed for SignedHttpRequest creation. /// /// This method will be executed only if is set to true. /// - internal virtual void AddUClaim(Dictionary payload, SignedHttpRequestDescriptor signedHttpRequestDescriptor) + internal virtual void AddUClaim(ref Utf8JsonWriter writer, SignedHttpRequestDescriptor signedHttpRequestDescriptor) { - if (payload == null) - throw LogHelper.LogArgumentNullException(nameof(payload)); - - var httpRequestUri = signedHttpRequestDescriptor.HttpRequestData.Uri; - - if (httpRequestUri == null) + if (signedHttpRequestDescriptor.HttpRequestData.Uri == null) throw LogHelper.LogArgumentNullException(nameof(signedHttpRequestDescriptor.HttpRequestData.Uri)); - if (!httpRequestUri.IsAbsoluteUri) - throw LogHelper.LogExceptionMessage(new SignedHttpRequestCreationException(LogHelper.FormatInvariant(LogMessages.IDX23001, httpRequestUri.OriginalString))); + if (!signedHttpRequestDescriptor.HttpRequestData.Uri.IsAbsoluteUri) + throw LogHelper.LogExceptionMessage(new SignedHttpRequestCreationException(LogHelper.FormatInvariant(LogMessages.IDX23001, signedHttpRequestDescriptor.HttpRequestData.Uri.OriginalString))); // https://datatracker.ietf.org/doc/html/draft-ietf-oauth-signed-http-request-03#section-3 // u claim: The HTTP URL host component as a JSON string. This MAY include the port separated from the host by a colon in host:port format. // Including the port if it not the default port for the httpRequestUri scheme. - var httpUrlHostComponent = httpRequestUri.Host; - if (!httpRequestUri.IsDefaultPort) - httpUrlHostComponent = $"{httpUrlHostComponent}:{httpRequestUri.Port}"; + string httpUrlHostComponent = signedHttpRequestDescriptor.HttpRequestData.Uri.Host; + if (!signedHttpRequestDescriptor.HttpRequestData.Uri.IsDefaultPort) + httpUrlHostComponent = $"{httpUrlHostComponent}:{signedHttpRequestDescriptor.HttpRequestData.Uri.Port}"; - payload.Add(SignedHttpRequestClaimTypes.U, httpUrlHostComponent); + writer.WriteString(SignedHttpRequestClaimTypes.U, httpUrlHostComponent); } /// - /// Adds the 'm' claim to the . + /// Adds the 'p' claim to the . /// - /// HttpRequest payload represented as a . + /// /// A structure that wraps parameters needed for SignedHttpRequest creation. /// /// This method will be executed only if is set to true. /// - internal virtual void AddPClaim(Dictionary payload, SignedHttpRequestDescriptor signedHttpRequestDescriptor) + internal virtual void AddPClaim(ref Utf8JsonWriter writer, SignedHttpRequestDescriptor signedHttpRequestDescriptor) { - if (payload == null) - throw LogHelper.LogArgumentNullException(nameof(payload)); - - var httpRequestUri = signedHttpRequestDescriptor.HttpRequestData.Uri; - - if (httpRequestUri == null) + if (signedHttpRequestDescriptor.HttpRequestData.Uri == null) throw LogHelper.LogArgumentNullException(nameof(signedHttpRequestDescriptor.HttpRequestData.Uri)); - httpRequestUri = EnsureAbsoluteUri(httpRequestUri); + Uri httpRequestUri = EnsureAbsoluteUri(signedHttpRequestDescriptor.HttpRequestData.Uri); - payload.Add(SignedHttpRequestClaimTypes.P, httpRequestUri.AbsolutePath); + writer.WriteString(SignedHttpRequestClaimTypes.P, httpRequestUri.AbsolutePath); } /// - /// Adds the 'q' claim to the . + /// Adds the 'q' claim to the . /// - /// HttpRequest payload represented as a . + /// /// A structure that wraps parameters needed for SignedHttpRequest creation. /// /// This method will be executed only if is set to true. - /// - internal virtual void AddQClaim(Dictionary payload, SignedHttpRequestDescriptor signedHttpRequestDescriptor) + /// + internal virtual void AddQClaim(ref Utf8JsonWriter writer, SignedHttpRequestDescriptor signedHttpRequestDescriptor) { - if (payload == null) - throw LogHelper.LogArgumentNullException(nameof(payload)); - - var httpRequestUri = signedHttpRequestDescriptor.HttpRequestData.Uri; - - if (httpRequestUri == null) + if (signedHttpRequestDescriptor.HttpRequestData.Uri == null) throw LogHelper.LogArgumentNullException(nameof(signedHttpRequestDescriptor.HttpRequestData.Uri)); - httpRequestUri = EnsureAbsoluteUri(httpRequestUri); - var sanitizedQueryParams = SanitizeQueryParams(httpRequestUri); + Uri httpRequestUri = EnsureAbsoluteUri(signedHttpRequestDescriptor.HttpRequestData.Uri); + IDictionary sanitizedQueryParams = SanitizeQueryParams(httpRequestUri); StringBuilder stringBuffer = new StringBuilder(); - List queryParamNameList = new List(); try { + writer.WriteStartArray(SignedHttpRequestClaimTypes.Q); + writer.WriteStartArray(); var firstQueryParam = true; foreach (var queryParam in sanitizedQueryParams) { @@ -302,13 +322,15 @@ internal virtual void AddQClaim(Dictionary payload, SignedHttpRe stringBuffer.Append("&"); stringBuffer.Append(queryParam.Key).Append('=').Append(queryParam.Value); - - queryParamNameList.Add(queryParam.Key); firstQueryParam = false; + + writer.WriteStringValue(queryParam.Key); } + writer.WriteEndArray(); var base64UrlEncodedHash = CalculateBase64UrlEncodedHash(stringBuffer.ToString()); - payload.Add(SignedHttpRequestClaimTypes.Q, new List() { queryParamNameList, base64UrlEncodedHash }); + writer.WriteStringValue(base64UrlEncodedHash); + writer.WriteEndArray(); } catch (Exception e) { @@ -317,38 +339,36 @@ internal virtual void AddQClaim(Dictionary payload, SignedHttpRe } /// - /// Adds the 'h' claim to the . + /// Adds the 'h' claim to the . /// - /// HttpRequest payload represented as a . + /// /// A structure that wraps parameters needed for SignedHttpRequest creation. /// /// This method will be executed only if is set to true. /// - internal virtual void AddHClaim(Dictionary payload, SignedHttpRequestDescriptor signedHttpRequestDescriptor) + internal void AddHClaim(ref Utf8JsonWriter writer, SignedHttpRequestDescriptor signedHttpRequestDescriptor) { - if (payload == null) - throw LogHelper.LogArgumentNullException(nameof(payload)); - - var sanitizedHeaders = SanitizeHeaders(signedHttpRequestDescriptor.HttpRequestData.Headers); + IDictionary sanitizedHeaders = SanitizeHeaders(signedHttpRequestDescriptor.HttpRequestData.Headers); StringBuilder stringBuffer = new StringBuilder(); - List headerNameList = new List(); try { + writer.WriteStartArray(SignedHttpRequestClaimTypes.H); + writer.WriteStartArray(); var firstHeader = true; foreach (var header in sanitizedHeaders) { var headerName = header.Key.ToLowerInvariant(); - headerNameList.Add(headerName); - if (!firstHeader) stringBuffer.Append(_newlineSeparator); stringBuffer.Append(headerName).Append(": ").Append(header.Value); firstHeader = false; + writer.WriteStringValue(headerName); } - var base64UrlEncodedHash = CalculateBase64UrlEncodedHash(stringBuffer.ToString()); - payload.Add(SignedHttpRequestClaimTypes.H, new List() { headerNameList, base64UrlEncodedHash }); + writer.WriteEndArray(); + writer.WriteStringValue(CalculateBase64UrlEncodedHash(stringBuffer.ToString())); + writer.WriteEndArray(); } catch (Exception e) { @@ -357,27 +377,18 @@ internal virtual void AddHClaim(Dictionary payload, SignedHttpRe } /// - /// Adds the 'b' claim to the . + /// Adds the 'b' claim to the . /// - /// HttpRequest payload represented as a . + /// /// A structure that wraps parameters needed for SignedHttpRequest creation. /// /// This method will be executed only if is set to true. /// - internal virtual void AddBClaim(Dictionary payload, SignedHttpRequestDescriptor signedHttpRequestDescriptor) + internal virtual void AddBClaim(ref Utf8JsonWriter writer, SignedHttpRequestDescriptor signedHttpRequestDescriptor) { - if (payload == null) - throw LogHelper.LogArgumentNullException(nameof(payload)); - - var httpRequestBody = signedHttpRequestDescriptor.HttpRequestData.Body; - - if (httpRequestBody == null) - httpRequestBody = Array.Empty(); - try { - var base64UrlEncodedHash = CalculateBase64UrlEncodedHash(httpRequestBody); - payload.Add(SignedHttpRequestClaimTypes.B, base64UrlEncodedHash); + writer.WriteString(SignedHttpRequestClaimTypes.B, CalculateBase64UrlEncodedHash(signedHttpRequestDescriptor.HttpRequestData.Body ?? Array.Empty())); } catch (Exception e) { @@ -386,69 +397,76 @@ internal virtual void AddBClaim(Dictionary payload, SignedHttpRe } /// - /// Adds the 'nonce' claim to the . + /// Adds the 'nonce' claim to the . /// - /// HttpRequest payload represented as a . + /// /// A structure that wraps parameters needed for SignedHttpRequest creation. /// /// This method will be executed only if is set to true. /// Users can utilize to provide a custom nonce value. /// - internal virtual void AddNonceClaim(Dictionary payload, SignedHttpRequestDescriptor signedHttpRequestDescriptor) + internal virtual void AddNonceClaim(ref Utf8JsonWriter writer, SignedHttpRequestDescriptor signedHttpRequestDescriptor) { - if (payload == null) - throw LogHelper.LogArgumentNullException(nameof(payload)); - if (!string.IsNullOrEmpty(signedHttpRequestDescriptor.CustomNonceValue)) - payload.Add(SignedHttpRequestClaimTypes.Nonce, signedHttpRequestDescriptor.CustomNonceValue); + writer.WriteString(SignedHttpRequestClaimTypes.Nonce, signedHttpRequestDescriptor.CustomNonceValue); else - payload.Add(SignedHttpRequestClaimTypes.Nonce, Guid.NewGuid().ToString("N")); + writer.WriteString(SignedHttpRequestClaimTypes.Nonce, Guid.NewGuid().ToString("N")); } /// - /// Adds the 'cnf' claim to the . + /// Adds the 'cnf' claim to the . /// - /// HttpRequest payload represented as a . + /// /// A structure that wraps parameters needed for SignedHttpRequest creation. /// /// If is not null or empty, its value will be used as a "cnf" claim value. /// Otherwise, a "cnf" claim value will be derived from the . member of . /// - internal virtual void AddCnfClaim(Dictionary payload, SignedHttpRequestDescriptor signedHttpRequestDescriptor) + internal virtual void AddCnfClaim(ref Utf8JsonWriter writer, SignedHttpRequestDescriptor signedHttpRequestDescriptor) { - if (payload == null) - throw LogHelper.LogArgumentNullException(nameof(payload)); try { + string cnfClaim = null; if (!string.IsNullOrEmpty(signedHttpRequestDescriptor.CnfClaimValue)) { - payload.Add(ConfirmationClaimTypes.Cnf, JObject.Parse(signedHttpRequestDescriptor.CnfClaimValue)); + cnfClaim = signedHttpRequestDescriptor.CnfClaimValue; } else { - var signedHttpRequestSigningKey = signedHttpRequestDescriptor.SigningCredentials.Key; JsonWebKey jsonWebKey; - if (signedHttpRequestSigningKey is JsonWebKey jwk) + if (signedHttpRequestDescriptor.SigningCredentials.Key is JsonWebKey jwk) jsonWebKey = jwk; // create a JsonWebKey from an X509SecurityKey, represented as an RsaSecurityKey. - else if (signedHttpRequestSigningKey is X509SecurityKey x509SecurityKey) + else if (signedHttpRequestDescriptor.SigningCredentials.Key is X509SecurityKey x509SecurityKey) jsonWebKey = JsonWebKeyConverter.ConvertFromX509SecurityKey(x509SecurityKey, true); - else if (signedHttpRequestSigningKey is AsymmetricSecurityKey asymmetricSecurityKey) + else if (signedHttpRequestDescriptor.SigningCredentials.Key is AsymmetricSecurityKey asymmetricSecurityKey) jsonWebKey = JsonWebKeyConverter.ConvertFromSecurityKey(asymmetricSecurityKey); else - throw LogHelper.LogExceptionMessage(new SignedHttpRequestCreationException(LogHelper.FormatInvariant(LogMessages.IDX23032, LogHelper.MarkAsNonPII(signedHttpRequestSigningKey != null ? signedHttpRequestSigningKey.GetType().ToString() : "null")))); + throw LogHelper.LogExceptionMessage( + new SignedHttpRequestCreationException( + LogHelper.FormatInvariant( + LogMessages.IDX23032, + LogHelper.MarkAsNonPII(signedHttpRequestDescriptor.SigningCredentials.Key != null ? signedHttpRequestDescriptor.SigningCredentials.Key.GetType().ToString() : "null")))); // set the jwk thumbprint as the Kid jsonWebKey.Kid = Base64UrlEncoder.Encode(jsonWebKey.ComputeJwkThumbprint()); - payload.Add(ConfirmationClaimTypes.Cnf, JObject.Parse(SignedHttpRequestUtilities.CreateJwkClaim(jsonWebKey))); + cnfClaim = SignedHttpRequestUtilities.CreateJwkClaim(jsonWebKey); } + + // need to write out cnfClaim as raw value, otherwise it will be treated as a string and not parsed correctly + writer.WritePropertyName(ConfirmationClaimTypes.Cnf); +#if NET6_0_OR_GREATER + writer.WriteRawValue(cnfClaim); +#else + JsonPrimitives.WriteAsJsonElement(ref writer, cnfClaim); +#endif } catch (Exception e) { throw LogHelper.LogExceptionMessage(new SignedHttpRequestCreationException(LogHelper.FormatInvariant(LogMessages.IDX23008, LogHelper.MarkAsNonPII(ConfirmationClaimTypes.Cnf), e), e)); } } - #endregion +#endregion #region SignedHttpRequest validation /// @@ -544,7 +562,7 @@ internal async virtual Task ValidateAccessTokenAsync(stri /// The library doesn't provide any caching logic for replay validation purposes. /// delegate can be utilized for replay validation /// - protected virtual async Task ValidateSignedHttpRequestPayloadAsync(SecurityToken signedHttpRequest, SignedHttpRequestValidationContext signedHttpRequestValidationContext, CancellationToken cancellationToken) + protected internal virtual async Task ValidateSignedHttpRequestPayloadAsync(SecurityToken signedHttpRequest, SignedHttpRequestValidationContext signedHttpRequestValidationContext, CancellationToken cancellationToken) { if (signedHttpRequest == null) throw LogHelper.LogArgumentNullException(nameof(signedHttpRequest)); @@ -1105,6 +1123,7 @@ internal virtual async Task ResolvePopKeyFromJkuAsync(string jkuSet { return popKeys[0]; } + // If there are multiple keys in the referenced JWK Set document, a "kid" member MUST also be included // with the referenced key's JWK also containing the same "kid" value. // https://datatracker.ietf.org/doc/html/rfc7800#section-3.5 @@ -1307,7 +1326,7 @@ private static Dictionary SanitizeQueryParams(Uri httpRequestUri /// https://datatracker.ietf.org/doc/html/draft-ietf-oauth-signed-http-request-03#section-4.1 /// https://datatracker.ietf.org/doc/html/draft-ietf-oauth-signed-http-request-03#section-7.5 /// - private static Dictionary SanitizeHeaders(IDictionary> headers) + private static IDictionary SanitizeHeaders(IDictionary> headers) { // Remove repeated headers according to the spec: https://datatracker.ietf.org/doc/html/draft-ietf-oauth-signed-http-request-03#section-7.5 // "If a header or query parameter is repeated on either the outgoing request from the client or the diff --git a/src/Microsoft.IdentityModel.Protocols.SignedHttpRequest/SignedHttpRequestValidationContext.cs b/src/Microsoft.IdentityModel.Protocols.SignedHttpRequest/SignedHttpRequestValidationContext.cs index 43738e4c6d..dcc25cfae2 100644 --- a/src/Microsoft.IdentityModel.Protocols.SignedHttpRequest/SignedHttpRequestValidationContext.cs +++ b/src/Microsoft.IdentityModel.Protocols.SignedHttpRequest/SignedHttpRequestValidationContext.cs @@ -1,28 +1,5 @@ -//------------------------------------------------------------------------------ -// -// Copyright (c) Microsoft Corporation. -// All rights reserved. -// -// This code is licensed under the MIT License. -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files(the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and / or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions : -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -// THE SOFTWARE. -// +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. using Microsoft.IdentityModel.Logging; using Microsoft.IdentityModel.Tokens; diff --git a/src/Microsoft.IdentityModel.Protocols.SignedHttpRequest/SignedHttpRequestValidationResult.cs b/src/Microsoft.IdentityModel.Protocols.SignedHttpRequest/SignedHttpRequestValidationResult.cs index 7d61663aa5..8c0968261d 100644 --- a/src/Microsoft.IdentityModel.Protocols.SignedHttpRequest/SignedHttpRequestValidationResult.cs +++ b/src/Microsoft.IdentityModel.Protocols.SignedHttpRequest/SignedHttpRequestValidationResult.cs @@ -1,28 +1,5 @@ -//------------------------------------------------------------------------------ -// -// Copyright (c) Microsoft Corporation. -// All rights reserved. -// -// This code is licensed under the MIT License. -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files(the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and / or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions : -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -// THE SOFTWARE. -// +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. using System; using Microsoft.IdentityModel.Tokens; diff --git a/src/Microsoft.IdentityModel.Protocols/AuthenticationProtocolMessage.cs b/src/Microsoft.IdentityModel.Protocols/AuthenticationProtocolMessage.cs index 7e939123b1..96fa9eb75e 100644 --- a/src/Microsoft.IdentityModel.Protocols/AuthenticationProtocolMessage.cs +++ b/src/Microsoft.IdentityModel.Protocols/AuthenticationProtocolMessage.cs @@ -20,7 +20,7 @@ public abstract class AuthenticationProtocolMessage private string _scriptButtonText = "Submit"; private string _scriptDisabledText = "Script is disabled. Click Submit to continue."; - private Dictionary _parameters = new Dictionary(); + private IDictionary _parameters = new Dictionary(); private string _issuerAddress = string.Empty; /// @@ -271,6 +271,6 @@ public string ScriptDisabledText _scriptDisabledText = value; } - } + } } } diff --git a/src/Microsoft.IdentityModel.Tokens/BaseConfiguration.cs b/src/Microsoft.IdentityModel.Tokens/BaseConfiguration.cs index a7e17f59f8..6f25f80c54 100644 --- a/src/Microsoft.IdentityModel.Tokens/BaseConfiguration.cs +++ b/src/Microsoft.IdentityModel.Tokens/BaseConfiguration.cs @@ -3,7 +3,7 @@ using System.Collections.Generic; using System.Collections.ObjectModel; -using Newtonsoft.Json; +using System.Text.Json.Serialization; namespace Microsoft.IdentityModel.Tokens { diff --git a/src/Microsoft.IdentityModel.Tokens/Json/EncodedJsonWebKeyParameterNames.cs b/src/Microsoft.IdentityModel.Tokens/Json/EncodedJsonWebKeyParameterNames.cs deleted file mode 100644 index 9a5828d148..0000000000 --- a/src/Microsoft.IdentityModel.Tokens/Json/EncodedJsonWebKeyParameterNames.cs +++ /dev/null @@ -1,271 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -using System.Text.Encodings.Web; -using System.Text.Json; - -namespace Microsoft.IdentityModel.Tokens -{ - /// - /// JsonEncodedText used with utf8Writer when writing property names for JsonWebKey. - /// Common names are initialized on startup, lazy for others. - /// - internal static class EncodedJsonWebKeyParameterNames - { - internal static bool _algSet; - internal static JsonEncodedText _alg; - internal static bool _crvSet; - internal static JsonEncodedText _crv; - internal static bool _dSet; - internal static JsonEncodedText _d; - internal static bool _dpSet; - internal static JsonEncodedText _dp; - internal static bool _dqSet; - internal static JsonEncodedText _dq; - internal static bool _kSet; - internal static JsonEncodedText _k; - internal static bool _keyopsSet; - internal static JsonEncodedText _keyOps; - internal static bool _othSet; - internal static JsonEncodedText _oth; - internal static bool _pSet; - internal static JsonEncodedText _p; - internal static bool _qSet; - internal static JsonEncodedText _q; - internal static bool _qiSet; - internal static JsonEncodedText _qi; - internal static bool _x5tS256Set; - internal static JsonEncodedText _x5tS256; - internal static bool _x5uSet; - internal static JsonEncodedText _x5u; - internal static bool _xSet; - internal static JsonEncodedText _x; - internal static bool _ySet; - internal static JsonEncodedText _y; - - public static JsonEncodedText Alg - { - get - { - if (!_algSet) - { - _alg = JsonEncodedText.Encode(JsonWebKeyParameterNames.Alg, JavaScriptEncoder.UnsafeRelaxedJsonEscaping); - _algSet = true; - } - - return _alg; - } - } - - public static JsonEncodedText Crv - { - get - { - if (!_crvSet) - { - _crv = JsonEncodedText.Encode(JsonWebKeyParameterNames.Crv, JavaScriptEncoder.UnsafeRelaxedJsonEscaping); - _crvSet = true; - } - - return _crv; - } - } - - public static JsonEncodedText D - { - get - { - if (!_dSet) - { - _d = JsonEncodedText.Encode(JsonWebKeyParameterNames.D, JavaScriptEncoder.UnsafeRelaxedJsonEscaping); - _dSet = true; - } - - return _d; - } - } - - public static JsonEncodedText DP - { - get - { - if (!_dpSet) - { - _dp = JsonEncodedText.Encode(JsonWebKeyParameterNames.DP, JavaScriptEncoder.UnsafeRelaxedJsonEscaping); - _dpSet = true; - } - - return _dp; - } - } - - public static JsonEncodedText DQ - { - get - { - if (!_dqSet) - { - _dq = JsonEncodedText.Encode(JsonWebKeyParameterNames.DQ, JavaScriptEncoder.UnsafeRelaxedJsonEscaping); - _dqSet = true; - } - - return _dq; - } - } - - public static readonly JsonEncodedText E = JsonEncodedText.Encode(JsonWebKeyParameterNames.E, JavaScriptEncoder.UnsafeRelaxedJsonEscaping); - - public static JsonEncodedText K - { - get - { - if (!_kSet) - { - _k = JsonEncodedText.Encode(JsonWebKeyParameterNames.K, JavaScriptEncoder.UnsafeRelaxedJsonEscaping); - _kSet = true; - } - - return _k; - } - } - - public static JsonEncodedText KeyOps - { - get - { - if (!_keyopsSet) - { - _keyOps = JsonEncodedText.Encode(JsonWebKeyParameterNames.KeyOps, JavaScriptEncoder.UnsafeRelaxedJsonEscaping); - _keyopsSet = true; - } - - return _keyOps; - } - } - - public static readonly JsonEncodedText Keys = JsonEncodedText.Encode(JsonWebKeyParameterNames.Keys, JavaScriptEncoder.UnsafeRelaxedJsonEscaping); - - public static readonly JsonEncodedText Kid = JsonEncodedText.Encode(JsonWebKeyParameterNames.Kid, JavaScriptEncoder.UnsafeRelaxedJsonEscaping); - - public static readonly JsonEncodedText Kty = JsonEncodedText.Encode(JsonWebKeyParameterNames.Kty, JavaScriptEncoder.UnsafeRelaxedJsonEscaping); - - public static readonly JsonEncodedText N = JsonEncodedText.Encode(JsonWebKeyParameterNames.N, JavaScriptEncoder.UnsafeRelaxedJsonEscaping); - - public static JsonEncodedText Oth - { - get - { - if (!_othSet) - { - _oth = JsonEncodedText.Encode(JsonWebKeyParameterNames.Oth, JavaScriptEncoder.UnsafeRelaxedJsonEscaping); - _othSet = true; - } - - return _oth; - } - } - - public static JsonEncodedText P - { - get - { - if (!_pSet) - { - _p = JsonEncodedText.Encode(JsonWebKeyParameterNames.P, JavaScriptEncoder.UnsafeRelaxedJsonEscaping); - _pSet = true; - } - - return _p; - } - } - public static JsonEncodedText Q - { - get - { - if (!_qSet) - { - _q = JsonEncodedText.Encode(JsonWebKeyParameterNames.Q, JavaScriptEncoder.UnsafeRelaxedJsonEscaping); - _qSet = true; - } - - return _q; - } - } - - public static JsonEncodedText QI - { - get - { - if (!_qiSet) - { - _qi = JsonEncodedText.Encode(JsonWebKeyParameterNames.QI, JavaScriptEncoder.UnsafeRelaxedJsonEscaping); - _qiSet = true; - } - - return _qi; - } - } - - public static readonly JsonEncodedText Use = JsonEncodedText.Encode(JsonWebKeyParameterNames.Use, JavaScriptEncoder.UnsafeRelaxedJsonEscaping); - - public static readonly JsonEncodedText X5c = JsonEncodedText.Encode(JsonWebKeyParameterNames.X5c, JavaScriptEncoder.UnsafeRelaxedJsonEscaping); - - public static readonly JsonEncodedText X5t = JsonEncodedText.Encode(JsonWebKeyParameterNames.X5t, JavaScriptEncoder.UnsafeRelaxedJsonEscaping); - - public static JsonEncodedText X5tS256 - { - get - { - if (!_x5tS256Set) - { - _x5tS256 = JsonEncodedText.Encode(JsonWebKeyParameterNames.X5tS256, JavaScriptEncoder.UnsafeRelaxedJsonEscaping); - _x5tS256Set = true; - } - - return _x5tS256; - } - } - - public static JsonEncodedText X5u - { - get - { - if (!_x5uSet) - { - _x5u = JsonEncodedText.Encode(JsonWebKeyParameterNames.X5u, JavaScriptEncoder.UnsafeRelaxedJsonEscaping); - _x5uSet = true; - } - - return _x5u; - } - } - - public static JsonEncodedText X - { - get - { - if (!_xSet) - { - _x = JsonEncodedText.Encode(JsonWebKeyParameterNames.X, JavaScriptEncoder.UnsafeRelaxedJsonEscaping); - _xSet = true; - } - - return _x; - } - } - - public static JsonEncodedText Y - { - get - { - if (!_ySet) - { - _y = JsonEncodedText.Encode(JsonWebKeyParameterNames.Y, JavaScriptEncoder.UnsafeRelaxedJsonEscaping); - _ySet = true; - } - - return _y; - } - } - } -} diff --git a/src/Microsoft.IdentityModel.Tokens/Json/JsonSerializerPrimitives.cs b/src/Microsoft.IdentityModel.Tokens/Json/JsonSerializerPrimitives.cs index 94606b1a15..0e06564b6c 100644 --- a/src/Microsoft.IdentityModel.Tokens/Json/JsonSerializerPrimitives.cs +++ b/src/Microsoft.IdentityModel.Tokens/Json/JsonSerializerPrimitives.cs @@ -5,6 +5,9 @@ using System; using System.Collections.Generic; using System.Diagnostics; +using System.IO; +using System.Text; +using System.Text.Encodings.Web; using System.Text.Json; using Microsoft.IdentityModel.Logging; @@ -58,6 +61,32 @@ internal static Exception CreateJsonReaderExceptionInvalidType(ref Utf8JsonReade LogHelper.MarkAsNonPII(reader.BytesConsumed))); } + public static JsonElement CreateJsonElement(string json) + { + Utf8JsonReader reader = new Utf8JsonReader(Encoding.UTF8.GetBytes(json).AsSpan()); + +#if NET6_0_OR_GREATER + bool ret = JsonElement.TryParseValue(ref reader, out JsonElement? jsonElement); + return jsonElement.Value; +#else + using (JsonDocument jsonDocument = JsonDocument.ParseValue(ref reader)) + return jsonDocument.RootElement.Clone(); +#endif + } + + public static void WriteAsJsonElement(ref Utf8JsonWriter writer, string json) + { + Utf8JsonReader reader = new Utf8JsonReader(Encoding.UTF8.GetBytes(json).AsSpan()); + +#if NET6_0_OR_GREATER + if (JsonElement.TryParseValue(ref reader, out JsonElement? jsonElement)) + jsonElement.Value.WriteTo(writer); +#else + using (JsonDocument jsonDocument = JsonDocument.ParseValue(ref reader)) + jsonDocument.RootElement.WriteTo(writer); +#endif + } + internal static string GetPropertyName(ref Utf8JsonReader reader, string className, bool advanceReader) { if (reader.TokenType == JsonTokenType.None) @@ -133,8 +162,11 @@ internal static bool ReaderRead(ref Utf8JsonReader reader) } } - internal static bool ReadBoolean(ref Utf8JsonReader reader, string propertyName, string className) + internal static bool ReadBoolean(ref Utf8JsonReader reader, string propertyName, string className, bool read = false) { + if (read) + ReaderRead(ref reader); + if (reader.TokenType == JsonTokenType.True || reader.TokenType == JsonTokenType.False) return reader.GetBoolean(); @@ -142,8 +174,59 @@ internal static bool ReadBoolean(ref Utf8JsonReader reader, string propertyName, CreateJsonReaderException(ref reader, "JsonTokenType.False or JsonTokenType.True", className, propertyName)); } - internal static double ReadDouble(ref Utf8JsonReader reader, string propertyName, string className) + internal static IList ReadStrings(ref Utf8JsonReader reader, IList strings, string propertyName, string className, bool read = false) + { + if (read) + ReaderRead(ref reader); + + // returning null keeps the same logic as JsonSerialization.ReadObject + if (reader.TokenType == JsonTokenType.Null) + return null; + + if (!IsReaderAtTokenType(ref reader, JsonTokenType.StartArray, false)) + throw LogHelper.LogExceptionMessage( + CreateJsonReaderExceptionInvalidType(ref reader, "JsonTokenType.StartArray", className, propertyName)); + + while (ReaderRead(ref reader)) + { + if (IsReaderAtTokenType(ref reader, JsonTokenType.EndArray, false)) + break; + + strings.Add(ReadString(ref reader, propertyName, className)); + } + + return strings; + } + + internal static ICollection ReadStrings(ref Utf8JsonReader reader, ICollection strings, string propertyName, string className, bool read = false) + { + if (read) + ReaderRead(ref reader); + + // returning null keeps the same logic as JsonSerialization.ReadObject + if (reader.TokenType == JsonTokenType.Null) + return null; + + if (!IsReaderAtTokenType(ref reader, JsonTokenType.StartArray, false)) + throw LogHelper.LogExceptionMessage( + CreateJsonReaderExceptionInvalidType(ref reader, "JsonTokenType.StartArray", className, propertyName)); + + while (ReaderRead(ref reader)) + { + if (IsReaderAtTokenType(ref reader, JsonTokenType.EndArray, false)) + break; + + strings.Add(ReadString(ref reader, propertyName, className)); + } + + return strings; + } + + internal static double ReadDouble(ref Utf8JsonReader reader, string propertyName, string className, bool read = false) { + if (read) + ReaderRead(ref reader); + if (reader.TokenType == JsonTokenType.Number) { try @@ -161,8 +244,11 @@ internal static double ReadDouble(ref Utf8JsonReader reader, string propertyName CreateJsonReaderException(ref reader, "JsonTokenType.Number", className, propertyName)); } - internal static int ReadInt(ref Utf8JsonReader reader, string propertyName, string className) + internal static int ReadInt(ref Utf8JsonReader reader, string propertyName, string className, bool read = false) { + if (read) + ReaderRead(ref reader); + if (reader.TokenType == JsonTokenType.Number) { try @@ -226,8 +312,11 @@ internal static IList ReadObjects(ref Utf8JsonReader reader, IList ReadStrings(ref Utf8JsonReader reader, IList strings, string propertyName, string className) - { - // returning null keeps the same logic as JsonSerialization.ReadObject - if (reader.TokenType == JsonTokenType.Null) - return null; - - if (!IsReaderAtTokenType(ref reader, JsonTokenType.StartArray, false)) - throw LogHelper.LogExceptionMessage( - CreateJsonReaderExceptionInvalidType(ref reader, "JsonTokenType.StartArray", className, propertyName)); - - while (ReaderRead(ref reader)) - { - if (IsReaderAtTokenType(ref reader, JsonTokenType.EndArray, false)) - break; - - strings.Add(ReadString(ref reader, propertyName, className)); - } - - return strings; - } - /// /// This method is only called when we are on a JsonTokenType.Number AND reading into AdditionalData which is an IDictionary[string, object]. /// We have to make a choice of the type to return. @@ -322,7 +390,46 @@ internal static void WriteAdditionalData(ref Utf8JsonWriter writer, IDictionary< } } - internal static void WriteStrings(ref Utf8JsonWriter writer, string propertyName, IList strings) + internal static void WriteObject(ref Utf8JsonWriter writer, string key, object obj) + { + if (obj is string) + writer.WriteString(key, obj as string); + else if (obj is int) + writer.WriteNumber(key, (int)obj); + else if (obj is bool) + writer.WriteBoolean(key, (bool)obj); + else if (obj is decimal) + writer.WriteNumber(key, (decimal)obj); + else if (obj is double) + writer.WriteNumber(key, (double)obj); + else if (obj is float) + writer.WriteNumber(key, (float)obj); + else if (obj is long) + writer.WriteNumber(key, (long)obj); + else if (obj is null) + writer.WriteNull(key); + else if (obj is JsonElement) + { + writer.WritePropertyName(key); + ((JsonElement)obj).WriteTo(writer); + } + else + { + writer.WriteString(key, obj.ToString()); + } + } + + internal static void WriteStrings(ref Utf8JsonWriter writer, ReadOnlySpan propertyName, IList strings) + { + writer.WritePropertyName(propertyName); + writer.WriteStartArray(); + foreach (string str in strings) + writer.WriteStringValue(str); + + writer.WriteEndArray(); + } + + internal static void WriteStrings(ref Utf8JsonWriter writer, ReadOnlySpan propertyName, ICollection strings) { writer.WritePropertyName(propertyName); writer.WriteStartArray(); diff --git a/src/Microsoft.IdentityModel.Tokens/Json/JsonWebKeySerializer.cs b/src/Microsoft.IdentityModel.Tokens/Json/JsonWebKeySerializer.cs index 1e029e6170..3312863869 100644 --- a/src/Microsoft.IdentityModel.Tokens/Json/JsonWebKeySerializer.cs +++ b/src/Microsoft.IdentityModel.Tokens/Json/JsonWebKeySerializer.cs @@ -14,6 +14,13 @@ namespace Microsoft.IdentityModel.Tokens.Json { internal static class JsonWebKeySerializer { + // This is used to perform performant case-insensitive property names. + // 6x used Newtonsoft and was case-insensitive w.r.t. property names. + // The serializer is written to use Utf8JsonReader.ValueTextEquals(...), to match property names. + // When we do not have a match, we check the uppercase name of the property against this table. + // If not found, then we assume we should put the value into AdditionalData. + // If we didn't do that, we would pay a performance penalty for those cases where there is AdditionalData + // but otherwise the JSON properties are all lower case. public static HashSet JsonWebKeyParameterNamesUpperCase = new HashSet { "ALG", @@ -365,70 +372,70 @@ public static void Write(ref Utf8JsonWriter writer, JsonWebKey jsonWebKey) writer.WriteStartObject(); if (!string.IsNullOrEmpty(jsonWebKey.Alg)) - writer.WriteString(EncodedJsonWebKeyParameterNames.Alg, jsonWebKey.Alg); + writer.WriteString(JsonWebKeyParameterUtf8Bytes.Alg, jsonWebKey.Alg); if (!string.IsNullOrEmpty(jsonWebKey.Crv)) - writer.WriteString(EncodedJsonWebKeyParameterNames.Crv, jsonWebKey.Crv); + writer.WriteString(JsonWebKeyParameterUtf8Bytes.Crv, jsonWebKey.Crv); if (!string.IsNullOrEmpty(jsonWebKey.D)) - writer.WriteString(EncodedJsonWebKeyParameterNames.D, jsonWebKey.D); + writer.WriteString(JsonWebKeyParameterUtf8Bytes.D, jsonWebKey.D); if (!string.IsNullOrEmpty(jsonWebKey.DP)) - writer.WriteString(EncodedJsonWebKeyParameterNames.DP, jsonWebKey.DP); + writer.WriteString(JsonWebKeyParameterUtf8Bytes.DP, jsonWebKey.DP); if (!string.IsNullOrEmpty(jsonWebKey.DQ)) - writer.WriteString(EncodedJsonWebKeyParameterNames.DQ, jsonWebKey.DQ); + writer.WriteString(JsonWebKeyParameterUtf8Bytes.DQ, jsonWebKey.DQ); if (!string.IsNullOrEmpty(jsonWebKey.E)) - writer.WriteString(EncodedJsonWebKeyParameterNames.E, jsonWebKey.E); + writer.WriteString(JsonWebKeyParameterUtf8Bytes.E, jsonWebKey.E); if (!string.IsNullOrEmpty(jsonWebKey.K)) - writer.WriteString(EncodedJsonWebKeyParameterNames.K, jsonWebKey.K); + writer.WriteString(JsonWebKeyParameterUtf8Bytes.K, jsonWebKey.K); if (jsonWebKey.KeyOps.Count > 0) - JsonSerializerPrimitives.WriteStrings(ref writer, EncodedJsonWebKeyParameterNames.KeyOps, jsonWebKey.KeyOps); + JsonSerializerPrimitives.WriteStrings(ref writer, JsonWebKeyParameterUtf8Bytes.KeyOps, jsonWebKey.KeyOps); if (!string.IsNullOrEmpty(jsonWebKey.Kid)) - writer.WriteString(EncodedJsonWebKeyParameterNames.Kid, jsonWebKey.Kid); + writer.WriteString(JsonWebKeyParameterUtf8Bytes.Kid, jsonWebKey.Kid); if (!string.IsNullOrEmpty(jsonWebKey.Kty)) - writer.WriteString(EncodedJsonWebKeyParameterNames.Kty, jsonWebKey.Kty); + writer.WriteString(JsonWebKeyParameterUtf8Bytes.Kty, jsonWebKey.Kty); if (!string.IsNullOrEmpty(jsonWebKey.N)) - writer.WriteString(EncodedJsonWebKeyParameterNames.N, jsonWebKey.N); + writer.WriteString(JsonWebKeyParameterUtf8Bytes.N, jsonWebKey.N); if (jsonWebKey.Oth.Count > 0) - JsonSerializerPrimitives.WriteStrings(ref writer, EncodedJsonWebKeyParameterNames.Oth, jsonWebKey.Oth); + JsonSerializerPrimitives.WriteStrings(ref writer, JsonWebKeyParameterUtf8Bytes.Oth, jsonWebKey.Oth); if (!string.IsNullOrEmpty(jsonWebKey.P)) - writer.WriteString(EncodedJsonWebKeyParameterNames.P, jsonWebKey.P); + writer.WriteString(JsonWebKeyParameterUtf8Bytes.P, jsonWebKey.P); if (!string.IsNullOrEmpty(jsonWebKey.Q)) - writer.WriteString(EncodedJsonWebKeyParameterNames.Q, jsonWebKey.Q); + writer.WriteString(JsonWebKeyParameterUtf8Bytes.Q, jsonWebKey.Q); if (!string.IsNullOrEmpty(jsonWebKey.QI)) - writer.WriteString(EncodedJsonWebKeyParameterNames.QI, jsonWebKey.QI); + writer.WriteString(JsonWebKeyParameterUtf8Bytes.QI, jsonWebKey.QI); if (!string.IsNullOrEmpty(jsonWebKey.Use)) - writer.WriteString(EncodedJsonWebKeyParameterNames.Use, jsonWebKey.Use); + writer.WriteString(JsonWebKeyParameterUtf8Bytes.Use, jsonWebKey.Use); if (!string.IsNullOrEmpty(jsonWebKey.X)) - writer.WriteString(EncodedJsonWebKeyParameterNames.X, jsonWebKey.X); + writer.WriteString(JsonWebKeyParameterUtf8Bytes.X, jsonWebKey.X); if (jsonWebKey.X5c.Count > 0) - JsonSerializerPrimitives.WriteStrings(ref writer, EncodedJsonWebKeyParameterNames.X5c, jsonWebKey.X5c); + JsonSerializerPrimitives.WriteStrings(ref writer, JsonWebKeyParameterUtf8Bytes.X5c, jsonWebKey.X5c); if (!string.IsNullOrEmpty(jsonWebKey.X5t)) - writer.WriteString(EncodedJsonWebKeyParameterNames.X5t, jsonWebKey.X5t); + writer.WriteString(JsonWebKeyParameterUtf8Bytes.X5t, jsonWebKey.X5t); if (!string.IsNullOrEmpty(jsonWebKey.X5tS256)) - writer.WriteString(EncodedJsonWebKeyParameterNames.X5tS256, jsonWebKey.X5tS256); + writer.WriteString(JsonWebKeyParameterUtf8Bytes.X5tS256, jsonWebKey.X5tS256); if (!string.IsNullOrEmpty(jsonWebKey.X5u)) - writer.WriteString(EncodedJsonWebKeyParameterNames.X5u, jsonWebKey.X5u); + writer.WriteString(JsonWebKeyParameterUtf8Bytes.X5u, jsonWebKey.X5u); if (!string.IsNullOrEmpty(jsonWebKey.Y)) - writer.WriteString(EncodedJsonWebKeyParameterNames.Y, jsonWebKey.Y); + writer.WriteString(JsonWebKeyParameterUtf8Bytes.Y, jsonWebKey.Y); JsonSerializerPrimitives.WriteAdditionalData(ref writer, jsonWebKey.AdditionalData); diff --git a/src/Microsoft.IdentityModel.Tokens/Json/JsonWebKeySetSerializer.cs b/src/Microsoft.IdentityModel.Tokens/Json/JsonWebKeySetSerializer.cs index 0ab209b31f..6b0542f824 100644 --- a/src/Microsoft.IdentityModel.Tokens/Json/JsonWebKeySetSerializer.cs +++ b/src/Microsoft.IdentityModel.Tokens/Json/JsonWebKeySetSerializer.cs @@ -120,7 +120,7 @@ public static void Write(ref Utf8JsonWriter writer, JsonWebKeySet jsonWebKeySet) { writer.WriteStartObject(); - writer.WritePropertyName(EncodedJsonWebKeyParameterNames.Keys); + writer.WritePropertyName(JsonWebKeyParameterUtf8Bytes.Keys); writer.WriteStartArray(); foreach (JsonWebKey jsonWebKey in jsonWebKeySet.Keys) diff --git a/src/Microsoft.IdentityModel.Tokens/JsonWebKeyParameterNames.cs b/src/Microsoft.IdentityModel.Tokens/JsonWebKeyParameterNames.cs index 0aecd691c6..c5f051114b 100644 --- a/src/Microsoft.IdentityModel.Tokens/JsonWebKeyParameterNames.cs +++ b/src/Microsoft.IdentityModel.Tokens/JsonWebKeyParameterNames.cs @@ -1,17 +1,16 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -using System.Text; +using System; namespace Microsoft.IdentityModel.Tokens { -#pragma warning disable 1591 - /// /// JsonWebKey parameter names /// public static class JsonWebKeyParameterNames { +#pragma warning disable 1591 public const string Alg = "alg"; public const string Crv = "crv"; public const string D = "d"; @@ -35,33 +34,33 @@ public static class JsonWebKeyParameterNames public const string X5tS256 = "x5t#S256"; public const string X5u = "x5u"; public const string Y = "y"; +#pragma warning restore 1591 } internal static class JsonWebKeyParameterUtf8Bytes { - public static readonly byte[] Alg = Encoding.UTF8.GetBytes("alg"); - public static readonly byte[] Crv = Encoding.UTF8.GetBytes("crv"); - public static readonly byte[] D = Encoding.UTF8.GetBytes("d"); - public static readonly byte[] DP = Encoding.UTF8.GetBytes("dp"); - public static readonly byte[] DQ = Encoding.UTF8.GetBytes("dq"); - public static readonly byte[] E = Encoding.UTF8.GetBytes("e"); - public static readonly byte[] K = Encoding.UTF8.GetBytes("k"); - public static readonly byte[] KeyOps = Encoding.UTF8.GetBytes("key_ops"); - public static readonly byte[] Keys = Encoding.UTF8.GetBytes("keys"); - public static readonly byte[] Kid = Encoding.UTF8.GetBytes("kid"); - public static readonly byte[] Kty = Encoding.UTF8.GetBytes("kty"); - public static readonly byte[] N = Encoding.UTF8.GetBytes("n"); - public static readonly byte[] Oth = Encoding.UTF8.GetBytes("oth"); - public static readonly byte[] P = Encoding.UTF8.GetBytes("p"); - public static readonly byte[] Q = Encoding.UTF8.GetBytes("q"); - public static readonly byte[] QI = Encoding.UTF8.GetBytes("qi"); - public static readonly byte[] Use = Encoding.UTF8.GetBytes("use"); - public static readonly byte[] X5c = Encoding.UTF8.GetBytes("x5c"); - public static readonly byte[] X5t = Encoding.UTF8.GetBytes("x5t"); - public static readonly byte[] X5tS256 = Encoding.UTF8.GetBytes("x5t#S256"); - public static readonly byte[] X5u = Encoding.UTF8.GetBytes("x5u"); - public static readonly byte[] X = Encoding.UTF8.GetBytes("x"); - public static readonly byte[] Y = Encoding.UTF8.GetBytes("y"); + public static ReadOnlySpan Alg => "alg"u8; + public static ReadOnlySpan Crv => "crv"u8; + public static ReadOnlySpan D => "d"u8; + public static ReadOnlySpan DP => "dp"u8; + public static ReadOnlySpan DQ => "dq"u8; + public static ReadOnlySpan E => "e"u8; + public static ReadOnlySpan K => "k"u8; + public static ReadOnlySpan KeyOps => "key_ops"u8; + public static ReadOnlySpan Keys => "keys"u8; + public static ReadOnlySpan Kid => "kid"u8; + public static ReadOnlySpan Kty => "kty"u8; + public static ReadOnlySpan N => "n"u8; + public static ReadOnlySpan Oth => "oth"u8; + public static ReadOnlySpan P => "p"u8; + public static ReadOnlySpan Q => "q"u8; + public static ReadOnlySpan QI => "qi"u8; + public static ReadOnlySpan Use => "use"u8; + public static ReadOnlySpan X5c => "x5c"u8; + public static ReadOnlySpan X5t => "x5t"u8; + public static ReadOnlySpan X5tS256 => "x5t#S256"u8; + public static ReadOnlySpan X5u => "x5u"u8; + public static ReadOnlySpan X => "x"u8; + public static ReadOnlySpan Y => "y"u8; } -#pragma warning restore 1591 } diff --git a/test/Microsoft.IdentityModel.Protocols.OpenIdConnect.Tests/Json/OpenIdConnectConfiguration6x.cs b/test/Microsoft.IdentityModel.Protocols.OpenIdConnect.Tests/Json/OpenIdConnectConfiguration6x.cs new file mode 100644 index 0000000000..0afb600ee3 --- /dev/null +++ b/test/Microsoft.IdentityModel.Protocols.OpenIdConnect.Tests/Json/OpenIdConnectConfiguration6x.cs @@ -0,0 +1,671 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.ComponentModel; +using Microsoft.IdentityModel.Abstractions; +using Microsoft.IdentityModel.Logging; +using Microsoft.IdentityModel.Tokens; +using Newtonsoft.Json; + +namespace Microsoft.IdentityModel.Protocols.OpenIdConnect.Json.Tests +{ + internal static class LogMessages + { + // properties, configuration + internal const string IDX21105 = "IDX21105: NonceLifetime must be greater than zero. value: '{0}'"; + internal const string IDX21106 = "IDX21106: Error in deserializing to json: '{0}'"; + + internal const string IDX21806 = "IDX21806: Deserializing json string into json web keys."; + internal const string IDX21808 = "IDX21808: Deserializing json into OpenIdConnectConfiguration object: '{0}'."; + internal const string IDX21809 = "IDX21809: Serializing OpenIdConfiguration object to json string."; + internal const string IDX21811 = "IDX21811: Deserializing the string: '{0}' obtained from metadata endpoint into openIdConnectConfiguration object."; + internal const string IDX21812 = "IDX21812: Retrieving json web keys from: '{0}'."; + internal const string IDX21813 = "IDX21813: Deserializing json web keys: '{0}'."; + internal const string IDX21815 = "IDX21815: Error deserializing json: '{0}' into '{1}'."; + internal const string IDX21816 = "IDX21816: The number of signing keys must be greater or equal to '{0}'. Value: '{1}'."; + internal const string IDX21817 = "IDX21817: The OpenIdConnectConfiguration did not contain any JsonWebKeys. This is required to validate the configuration."; + internal const string IDX21818 = "IDX21818: The OpenIdConnectConfiguration's valid signing keys cannot be less than {0}. Values: {1}. Invalid keys: {2}"; + } + + /// + /// Contains OpenIdConnect configuration that can be populated from a json string. + /// This is the original OpenIdConnectConfiguration in the 6x branch. + /// Used for ensuring backcompat. + /// + [JsonObject] + public class OpenIdConnectConfiguration6x : BaseConfiguration + { + private const string _className = "Microsoft.IdentityModel.Protocols.OpenIdConnect.OpenIdConnectConfiguration"; + + /// + /// Deserializes the json string into an object. + /// + /// json string representing the configuration. + /// object representing the configuration. + /// If 'json' is null or empty. + /// If 'json' fails to deserialize. + public static OpenIdConnectConfiguration6x Create(string json) + { + if (string.IsNullOrEmpty(json)) + throw LogHelper.LogArgumentNullException(nameof(json)); + + if (LogHelper.IsEnabled(EventLogLevel.Verbose)) + LogHelper.LogVerbose(LogMessages.IDX21808, json); + + return new OpenIdConnectConfiguration6x(json); + } + + /// + /// Serializes the object to a json string. + /// + /// object to serialize. + /// json string representing the configuration object. + /// If 'configuration' is null. + public static string Write(OpenIdConnectConfiguration configuration) + { + if (configuration == null) + throw LogHelper.LogArgumentNullException(nameof(configuration)); + + LogHelper.LogVerbose(LogMessages.IDX21809); + return JsonConvert.SerializeObject(configuration); + } + + /// + /// Initializes an new instance of . + /// + public OpenIdConnectConfiguration6x() + { + } + + /// + /// Initializes an new instance of from a json string. + /// + /// a json string containing the metadata + /// If 'json' is null or empty. + public OpenIdConnectConfiguration6x(string json) + { + if(string.IsNullOrEmpty(json)) + throw LogHelper.LogArgumentNullException(nameof(json)); + + try + { + if (LogHelper.IsEnabled(EventLogLevel.Verbose)) + LogHelper.LogVerbose(LogMessages.IDX21806, json, LogHelper.MarkAsNonPII(_className)); + + JsonConvert.PopulateObject(json, this); + } + catch (Exception ex) + { + throw LogHelper.LogExceptionMessage(new ArgumentException(LogHelper.FormatInvariant(LogMessages.IDX21815, json, LogHelper.MarkAsNonPII(_className)), ex)); + } + } + + /// + /// When deserializing from JSON any properties that are not defined will be placed here. + /// + [JsonExtensionData] + public virtual IDictionary AdditionalData { get; } = new Dictionary(); + + /// + /// Gets the collection of 'acr_values_supported' + /// + [JsonProperty(DefaultValueHandling = DefaultValueHandling.Ignore, NullValueHandling = NullValueHandling.Ignore, PropertyName = OpenIdProviderMetadataNames.AcrValuesSupported, Required = Required.Default)] + public ICollection AcrValuesSupported { get; } = new Collection(); + + /// + /// Gets or sets the 'authorization_endpoint'. + /// + [JsonProperty(DefaultValueHandling = DefaultValueHandling.Ignore, NullValueHandling = NullValueHandling.Ignore, PropertyName = OpenIdProviderMetadataNames.AuthorizationEndpoint, Required = Required.Default)] + public string AuthorizationEndpoint { get; set; } + + /// + /// Gets or sets the 'check_session_iframe'. + /// + [JsonProperty(DefaultValueHandling = DefaultValueHandling.Ignore, NullValueHandling = NullValueHandling.Ignore, PropertyName = OpenIdProviderMetadataNames.CheckSessionIframe, Required = Required.Default)] + public string CheckSessionIframe { get; set; } + + /// + /// Gets the collection of 'claims_supported' + /// + [JsonProperty(DefaultValueHandling = DefaultValueHandling.Ignore, NullValueHandling = NullValueHandling.Ignore, PropertyName = OpenIdProviderMetadataNames.ClaimsSupported, Required = Required.Default)] + public ICollection ClaimsSupported { get; } = new Collection(); + + /// + /// Gets the collection of 'claims_locales_supported' + /// + [JsonProperty(DefaultValueHandling = DefaultValueHandling.Ignore, NullValueHandling = NullValueHandling.Ignore, PropertyName = OpenIdProviderMetadataNames.ClaimsLocalesSupported, Required = Required.Default)] + public ICollection ClaimsLocalesSupported { get; } = new Collection(); + + /// + /// Gets or sets the 'claims_parameter_supported' + /// + [JsonProperty(DefaultValueHandling = DefaultValueHandling.Ignore, NullValueHandling = NullValueHandling.Ignore, PropertyName = OpenIdProviderMetadataNames.ClaimsParameterSupported, Required = Required.Default)] + public bool ClaimsParameterSupported { get; set; } + + /// + /// Gets the collection of 'claim_types_supported' + /// + [JsonProperty(DefaultValueHandling = DefaultValueHandling.Ignore, NullValueHandling = NullValueHandling.Ignore, PropertyName = OpenIdProviderMetadataNames.ClaimTypesSupported, Required = Required.Default)] + public ICollection ClaimTypesSupported { get; } = new Collection(); + + /// + /// Gets the collection of 'display_values_supported' + /// + [JsonProperty(DefaultValueHandling = DefaultValueHandling.Ignore, NullValueHandling = NullValueHandling.Ignore, PropertyName = OpenIdProviderMetadataNames.DisplayValuesSupported, Required = Required.Default)] + public ICollection DisplayValuesSupported { get; } = new Collection(); + + /// + /// Gets or sets the 'end_session_endpoint'. + /// + [JsonProperty(DefaultValueHandling = DefaultValueHandling.Ignore, NullValueHandling = NullValueHandling.Ignore, PropertyName = OpenIdProviderMetadataNames.EndSessionEndpoint, Required = Required.Default)] + public string EndSessionEndpoint { get; set; } + + /// + /// Gets or sets the 'frontchannel_logout_session_supported'. + /// + [JsonProperty(DefaultValueHandling = DefaultValueHandling.Ignore, NullValueHandling = NullValueHandling.Ignore, PropertyName = OpenIdProviderMetadataNames.FrontchannelLogoutSessionSupported, Required = Required.Default)] + public string FrontchannelLogoutSessionSupported { get; set; } + + /// + /// Gets or sets the 'frontchannel_logout_supported'. + /// + [JsonProperty(DefaultValueHandling = DefaultValueHandling.Ignore, NullValueHandling = NullValueHandling.Ignore, PropertyName = OpenIdProviderMetadataNames.FrontchannelLogoutSupported, Required = Required.Default)] + public string FrontchannelLogoutSupported { get; set; } + + /// + /// Gets the collection of 'grant_types_supported' + /// + [JsonProperty(DefaultValueHandling = DefaultValueHandling.Ignore, NullValueHandling = NullValueHandling.Ignore, PropertyName = OpenIdProviderMetadataNames.GrantTypesSupported, Required = Required.Default)] + public ICollection GrantTypesSupported { get; } = new Collection(); + + /// + /// Boolean value specifying whether the OP supports HTTP-based logout. Default is false. + /// + [JsonProperty(DefaultValueHandling = DefaultValueHandling.Ignore, NullValueHandling = NullValueHandling.Ignore, PropertyName = OpenIdProviderMetadataNames.HttpLogoutSupported, Required = Required.Default)] + public bool HttpLogoutSupported { get; set; } + + /// + /// Gets the collection of 'id_token_encryption_alg_values_supported'. + /// + [JsonProperty(DefaultValueHandling = DefaultValueHandling.Ignore, NullValueHandling = NullValueHandling.Ignore, PropertyName = OpenIdProviderMetadataNames.IdTokenEncryptionAlgValuesSupported, Required = Required.Default)] + public ICollection IdTokenEncryptionAlgValuesSupported { get; } = new Collection(); + + /// + /// Gets the collection of 'id_token_encryption_enc_values_supported'. + /// + [JsonProperty(DefaultValueHandling = DefaultValueHandling.Ignore, NullValueHandling = NullValueHandling.Ignore, PropertyName = OpenIdProviderMetadataNames.IdTokenEncryptionEncValuesSupported, Required = Required.Default)] + public ICollection IdTokenEncryptionEncValuesSupported { get; } = new Collection(); + + /// + /// Gets the collection of 'id_token_signing_alg_values_supported'. + /// + [JsonProperty(DefaultValueHandling = DefaultValueHandling.Ignore, NullValueHandling = NullValueHandling.Ignore, PropertyName = OpenIdProviderMetadataNames.IdTokenSigningAlgValuesSupported, Required = Required.Default)] + public ICollection IdTokenSigningAlgValuesSupported { get; } = new Collection(); + + /// + /// Gets or sets the 'introspection_endpoint'. + /// + [JsonProperty(DefaultValueHandling = DefaultValueHandling.Ignore, NullValueHandling = NullValueHandling.Ignore, PropertyName = OpenIdProviderMetadataNames.IntrospectionEndpoint, Required = Required.Default)] + public string IntrospectionEndpoint { get; set; } + + /// + /// Gets the collection of 'introspection_endpoint_auth_methods_supported'. + /// + [JsonProperty(DefaultValueHandling = DefaultValueHandling.Ignore, NullValueHandling = NullValueHandling.Ignore, PropertyName = OpenIdProviderMetadataNames.IntrospectionEndpointAuthMethodsSupported, Required = Required.Default)] + public ICollection IntrospectionEndpointAuthMethodsSupported { get; } = new Collection(); + + /// + /// Gets the collection of 'introspection_endpoint_auth_signing_alg_values_supported'. + /// + [JsonProperty(DefaultValueHandling = DefaultValueHandling.Ignore, NullValueHandling = NullValueHandling.Ignore, PropertyName = OpenIdProviderMetadataNames.IntrospectionEndpointAuthSigningAlgValuesSupported, Required = Required.Default)] + public ICollection IntrospectionEndpointAuthSigningAlgValuesSupported { get; } = new Collection(); + + /// + /// Gets or sets the 'issuer'. + /// + [JsonProperty(DefaultValueHandling = DefaultValueHandling.Ignore, NullValueHandling = NullValueHandling.Ignore, PropertyName = OpenIdProviderMetadataNames.Issuer, Required = Required.Default)] + public override string Issuer { get; set; } + + /// + /// Gets or sets the 'jwks_uri' + /// + [JsonProperty(DefaultValueHandling = DefaultValueHandling.Ignore, NullValueHandling = NullValueHandling.Ignore, PropertyName = OpenIdProviderMetadataNames.JwksUri, Required = Required.Default)] + public string JwksUri { get; set; } + + /// + /// Gets or sets the + /// + public JsonWebKeySet JsonWebKeySet {get; set;} + + /// + /// Boolean value specifying whether the OP can pass a sid (session ID) query parameter to identify the RP session at the OP when the logout_uri is used. Dafault Value is false. + /// + [JsonProperty(DefaultValueHandling = DefaultValueHandling.Ignore, NullValueHandling = NullValueHandling.Ignore, PropertyName = OpenIdProviderMetadataNames.LogoutSessionSupported, Required = Required.Default)] + public bool LogoutSessionSupported { get; set; } + + /// + /// Gets or sets the 'op_policy_uri' + /// + [JsonProperty(DefaultValueHandling = DefaultValueHandling.Ignore, NullValueHandling = NullValueHandling.Ignore, PropertyName = OpenIdProviderMetadataNames.OpPolicyUri, Required = Required.Default)] + public string OpPolicyUri { get; set; } + + /// + /// Gets or sets the 'op_tos_uri' + /// + [JsonProperty(DefaultValueHandling = DefaultValueHandling.Ignore, NullValueHandling = NullValueHandling.Ignore, PropertyName = OpenIdProviderMetadataNames.OpTosUri, Required = Required.Default)] + public string OpTosUri { get; set; } + + /// + /// Gets or sets the 'registration_endpoint' + /// + [JsonProperty(DefaultValueHandling = DefaultValueHandling.Ignore, NullValueHandling = NullValueHandling.Ignore, PropertyName = OpenIdProviderMetadataNames.RegistrationEndpoint, Required = Required.Default)] + public string RegistrationEndpoint { get; set; } + + /// + /// Gets the collection of 'request_object_encryption_alg_values_supported'. + /// + [JsonProperty(DefaultValueHandling = DefaultValueHandling.Ignore, NullValueHandling = NullValueHandling.Ignore, PropertyName = OpenIdProviderMetadataNames.RequestObjectEncryptionAlgValuesSupported, Required = Required.Default)] + public ICollection RequestObjectEncryptionAlgValuesSupported { get; } = new Collection(); + + /// + /// Gets the collection of 'request_object_encryption_enc_values_supported'. + /// + [JsonProperty(DefaultValueHandling = DefaultValueHandling.Ignore, NullValueHandling = NullValueHandling.Ignore, PropertyName = OpenIdProviderMetadataNames.RequestObjectEncryptionEncValuesSupported, Required = Required.Default)] + public ICollection RequestObjectEncryptionEncValuesSupported { get; } = new Collection(); + + /// + /// Gets the collection of 'request_object_signing_alg_values_supported'. + /// + [JsonProperty(DefaultValueHandling = DefaultValueHandling.Ignore, NullValueHandling = NullValueHandling.Ignore, PropertyName = OpenIdProviderMetadataNames.RequestObjectSigningAlgValuesSupported, Required = Required.Default)] + public ICollection RequestObjectSigningAlgValuesSupported { get; } = new Collection(); + + /// + /// Gets or sets the 'request_parameter_supported' + /// + [JsonProperty(DefaultValueHandling = DefaultValueHandling.Ignore, NullValueHandling = NullValueHandling.Ignore, PropertyName = OpenIdProviderMetadataNames.RequestParameterSupported, Required = Required.Default)] + public bool RequestParameterSupported { get; set; } + + /// + /// Gets or sets the 'request_uri_parameter_supported' + /// + [JsonProperty(DefaultValueHandling = DefaultValueHandling.Ignore, NullValueHandling = NullValueHandling.Ignore, PropertyName = OpenIdProviderMetadataNames.RequestUriParameterSupported, Required = Required.Default)] + public bool RequestUriParameterSupported { get; set; } + + /// + /// Gets or sets the 'require_request_uri_registration' + /// + [JsonProperty(DefaultValueHandling = DefaultValueHandling.Ignore, NullValueHandling = NullValueHandling.Ignore, PropertyName = OpenIdProviderMetadataNames.RequireRequestUriRegistration, Required = Required.Default)] + public bool RequireRequestUriRegistration { get; set; } + + /// + /// Gets the collection of 'response_modes_supported'. + /// + [JsonProperty(DefaultValueHandling = DefaultValueHandling.Ignore, NullValueHandling = NullValueHandling.Ignore, PropertyName = OpenIdProviderMetadataNames.ResponseModesSupported, Required = Required.Default)] + public ICollection ResponseModesSupported { get; } = new Collection(); + + /// + /// Gets the collection of 'response_types_supported'. + /// + [JsonProperty(DefaultValueHandling = DefaultValueHandling.Ignore, NullValueHandling = NullValueHandling.Ignore, PropertyName = OpenIdProviderMetadataNames.ResponseTypesSupported, Required = Required.Default)] + public ICollection ResponseTypesSupported { get; } = new Collection(); + + /// + /// Gets or sets the 'service_documentation' + /// + [JsonProperty(DefaultValueHandling = DefaultValueHandling.Ignore, NullValueHandling = NullValueHandling.Ignore, PropertyName = OpenIdProviderMetadataNames.ServiceDocumentation, Required = Required.Default)] + public string ServiceDocumentation { get; set; } + + /// + /// Gets the collection of 'scopes_supported' + /// + [JsonProperty(DefaultValueHandling = DefaultValueHandling.Ignore, NullValueHandling = NullValueHandling.Ignore, PropertyName = OpenIdProviderMetadataNames.ScopesSupported, Required = Required.Default)] + public ICollection ScopesSupported { get; } = new Collection(); + + /// + /// Gets the that the IdentityProvider indicates are to be used signing tokens. + /// + [JsonIgnore] + public override ICollection SigningKeys { get; } = new Collection(); + + /// + /// Gets the collection of 'subject_types_supported'. + /// + [JsonProperty(DefaultValueHandling = DefaultValueHandling.Ignore, NullValueHandling = NullValueHandling.Ignore, PropertyName = OpenIdProviderMetadataNames.SubjectTypesSupported, Required = Required.Default)] + public ICollection SubjectTypesSupported { get; } = new Collection(); + + /// + /// Gets or sets the 'token_endpoint'. + /// + [JsonProperty(DefaultValueHandling = DefaultValueHandling.Ignore, NullValueHandling = NullValueHandling.Ignore, PropertyName = OpenIdProviderMetadataNames.TokenEndpoint, Required = Required.Default)] + public override string TokenEndpoint { get; set; } + + /// + /// This base class property is not used in OpenIdConnect. + /// + [JsonIgnore] + public override string ActiveTokenEndpoint { get; set; } + + /// + /// Gets the collection of 'token_endpoint_auth_methods_supported'. + /// + [JsonProperty(DefaultValueHandling = DefaultValueHandling.Ignore, NullValueHandling = NullValueHandling.Ignore, PropertyName = OpenIdProviderMetadataNames.TokenEndpointAuthMethodsSupported, Required = Required.Default)] + public ICollection TokenEndpointAuthMethodsSupported { get; } = new Collection(); + + /// + /// Gets the collection of 'token_endpoint_auth_signing_alg_values_supported'. + /// + [JsonProperty(DefaultValueHandling = DefaultValueHandling.Ignore, NullValueHandling = NullValueHandling.Ignore, PropertyName = OpenIdProviderMetadataNames.TokenEndpointAuthSigningAlgValuesSupported, Required = Required.Default)] + public ICollection TokenEndpointAuthSigningAlgValuesSupported { get; } = new Collection(); + + /// + /// Gets the collection of 'ui_locales_supported' + /// + [JsonProperty(DefaultValueHandling = DefaultValueHandling.Ignore, NullValueHandling = NullValueHandling.Ignore, PropertyName = OpenIdProviderMetadataNames.UILocalesSupported, Required = Required.Default)] + public ICollection UILocalesSupported { get; } = new Collection(); + + /// + /// Gets or sets the 'user_info_endpoint'. + /// + [JsonProperty(DefaultValueHandling = DefaultValueHandling.Ignore, NullValueHandling = NullValueHandling.Ignore, PropertyName = OpenIdProviderMetadataNames.UserInfoEndpoint, Required = Required.Default)] + public string UserInfoEndpoint { get; set; } + + /// + /// Gets the collection of 'userinfo_encryption_alg_values_supported' + /// + [JsonProperty(DefaultValueHandling = DefaultValueHandling.Ignore, NullValueHandling = NullValueHandling.Ignore, PropertyName = OpenIdProviderMetadataNames.UserInfoEncryptionAlgValuesSupported, Required = Required.Default)] + public ICollection UserInfoEndpointEncryptionAlgValuesSupported { get; } = new Collection(); + + /// + /// Gets the collection of 'userinfo_encryption_enc_values_supported' + /// + [JsonProperty(DefaultValueHandling = DefaultValueHandling.Ignore, NullValueHandling = NullValueHandling.Ignore, PropertyName = OpenIdProviderMetadataNames.UserInfoEncryptionEncValuesSupported, Required = Required.Default)] + public ICollection UserInfoEndpointEncryptionEncValuesSupported { get; } = new Collection(); + + /// + /// Gets the collection of 'userinfo_signing_alg_values_supported' + /// + [JsonProperty(DefaultValueHandling = DefaultValueHandling.Ignore, NullValueHandling = NullValueHandling.Ignore, PropertyName = OpenIdProviderMetadataNames.UserInfoSigningAlgValuesSupported, Required = Required.Default)] + public ICollection UserInfoEndpointSigningAlgValuesSupported { get; } = new Collection(); + +#region shouldserialize + /// + /// Gets a bool that determines if the 'acr_values_supported' (AcrValuesSupported) property should be serialized. + /// This is used by Json.NET in order to conditionally serialize properties. + /// + /// true if 'acr_values_supported' (AcrValuesSupported) is not empty; otherwise, false. + [EditorBrowsable(EditorBrowsableState.Never)] + public bool ShouldSerializeAcrValuesSupported() + { + return AcrValuesSupported.Count > 0; + } + + /// + /// Gets a bool that determines if the 'claims_supported' (ClaimsSupported) property should be serialized. + /// This is used by Json.NET in order to conditionally serialize properties. + /// + /// true if 'claims_supported' (ClaimsSupported) is not empty; otherwise, false. + [EditorBrowsable(EditorBrowsableState.Never)] + public bool ShouldSerializeClaimsSupported() + { + return ClaimsSupported.Count > 0; + } + + /// + /// Gets a bool that determines if the 'claims_locales_supported' (ClaimsLocalesSupported) property should be serialized. + /// This is used by Json.NET in order to conditionally serialize properties. + /// + /// true if 'claims_locales_supported' (ClaimsLocalesSupported) is not empty; otherwise, false. + [EditorBrowsable(EditorBrowsableState.Never)] + public bool ShouldSerializeClaimsLocalesSupported() + { + return ClaimsLocalesSupported.Count > 0; + } + + /// + /// Gets a bool that determines if the 'claim_types_supported' (ClaimTypesSupported) property should be serialized. + /// This is used by Json.NET in order to conditionally serialize properties. + /// + /// true if 'claim_types_supported' (ClaimTypesSupported) is not empty; otherwise, false. + [EditorBrowsable(EditorBrowsableState.Never)] + public bool ShouldSerializeClaimTypesSupported() + { + return ClaimTypesSupported.Count > 0; + } + + /// + /// Gets a bool that determines if the 'display_values_supported' (DisplayValuesSupported) property should be serialized. + /// This is used by Json.NET in order to conditionally serialize properties. + /// + /// true if 'display_values_supported' (DisplayValuesSupported) is not empty; otherwise, false. + [EditorBrowsable(EditorBrowsableState.Never)] + public bool ShouldSerializeDisplayValuesSupported() + { + return DisplayValuesSupported.Count > 0; + } + + /// + /// Gets a bool that determines if the 'grant_types_supported' (GrantTypesSupported) property should be serialized. + /// This is used by Json.NET in order to conditionally serialize properties. + /// + /// true if 'grant_types_supported' (GrantTypesSupported) is not empty; otherwise, false. + [EditorBrowsable(EditorBrowsableState.Never)] + public bool ShouldSerializeGrantTypesSupported() + { + return GrantTypesSupported.Count > 0; + } + + /// + /// Gets a bool that determines if the 'id_token_encryption_alg_values_supported' (IdTokenEncryptionAlgValuesSupported) property should be serialized. + /// This is used by Json.NET in order to conditionally serialize properties. + /// + /// true if 'id_token_encryption_alg_values_supported' (IdTokenEncryptionAlgValuesSupported) is not empty; otherwise, false. + [EditorBrowsable(EditorBrowsableState.Never)] + public bool ShouldSerializeIdTokenEncryptionAlgValuesSupported() + { + return IdTokenEncryptionAlgValuesSupported.Count > 0; + } + + /// + /// Gets a bool that determines if the 'id_token_encryption_enc_values_supported' (IdTokenEncryptionEncValuesSupported) property should be serialized. + /// This is used by Json.NET in order to conditionally serialize properties. + /// + /// true if 'id_token_encryption_enc_values_supported' (IdTokenEncryptionEncValuesSupported) is not empty; otherwise, false. + [EditorBrowsable(EditorBrowsableState.Never)] + public bool ShouldSerializeIdTokenEncryptionEncValuesSupported() + { + return IdTokenEncryptionEncValuesSupported.Count > 0; + } + + /// + /// Gets a bool that determines if the 'id_token_signing_alg_values_supported' (IdTokenSigningAlgValuesSupported) property should be serialized. + /// This is used by Json.NET in order to conditionally serialize properties. + /// + /// true if 'id_token_signing_alg_values_supported' (IdTokenSigningAlgValuesSupported) is not empty; otherwise, false. + [EditorBrowsable(EditorBrowsableState.Never)] + public bool ShouldSerializeIdTokenSigningAlgValuesSupported() + { + return IdTokenSigningAlgValuesSupported.Count > 0; + } + + /// + /// Gets a bool that determines if the 'introspection_endpoint_auth_methods_supported' (IntrospectionEndpointAuthMethodsSupported) property should be serialized. + /// This is used by Json.NET in order to conditionally serialize properties. + /// + /// true if 'introspection_endpoint_auth_methods_supported' (IntrospectionEndpointAuthMethodsSupported) is not empty; otherwise, false. + [EditorBrowsable(EditorBrowsableState.Never)] + public bool ShouldSerializeIntrospectionEndpointAuthMethodsSupported() + { + return IntrospectionEndpointAuthMethodsSupported.Count > 0; + } + + /// + /// Gets a bool that determines if the 'introspection_endpoint_auth_signing_alg_values_supported' (IntrospectionEndpointAuthSigningAlgValuesSupported) property should be serialized. + /// This is used by Json.NET in order to conditionally serialize properties. + /// + /// true if 'introspection_endpoint_auth_signing_alg_values_supported' (IntrospectionEndpointAuthSigningAlgValuesSupported) is not empty; otherwise, false. + [EditorBrowsable(EditorBrowsableState.Never)] + public bool ShouldSerializeIntrospectionEndpointAuthSigningAlgValuesSupported() + { + return IntrospectionEndpointAuthSigningAlgValuesSupported.Count > 0; + } + + /// + /// Gets a bool that determines if the 'request_object_encryption_alg_values_supported' (RequestObjectEncryptionAlgValuesSupported) property should be serialized. + /// This is used by Json.NET in order to conditionally serialize properties. + /// + /// true if 'request_object_encryption_alg_values_supported' (RequestObjectEncryptionAlgValuesSupported) is not empty; otherwise, false. + [EditorBrowsable(EditorBrowsableState.Never)] + public bool ShouldSerializeRequestObjectEncryptionAlgValuesSupported() + { + return RequestObjectEncryptionAlgValuesSupported.Count > 0; + } + + /// + /// Gets a bool that determines if the 'request_object_encryption_enc_values_supported' (RequestObjectEncryptionEncValuesSupported) property should be serialized. + /// This is used by Json.NET in order to conditionally serialize properties. + /// + /// true if 'request_object_encryption_enc_values_supported' (RequestObjectEncryptionEncValuesSupported) is not empty; otherwise, false. + [EditorBrowsable(EditorBrowsableState.Never)] + public bool ShouldSerializeRequestObjectEncryptionEncValuesSupported() + { + return RequestObjectEncryptionEncValuesSupported.Count > 0; + } + + /// + /// Gets a bool that determines if the 'request_object_signing_alg_values_supported' (RequestObjectSigningAlgValuesSupported) property should be serialized. + /// This is used by Json.NET in order to conditionally serialize properties. + /// + /// true if 'request_object_signing_alg_values_supported' (RequestObjectSigningAlgValuesSupported) is not empty; otherwise, false. + [EditorBrowsable(EditorBrowsableState.Never)] + public bool ShouldSerializeRequestObjectSigningAlgValuesSupported() + { + return RequestObjectSigningAlgValuesSupported.Count > 0; + } + + /// + /// Gets a bool that determines if the 'response_modes_supported' (ResponseModesSupported) property should be serialized. + /// This is used by Json.NET in order to conditionally serialize properties. + /// + /// true if 'response_modes_supported' (ResponseModesSupported) is not empty; otherwise, false. + [EditorBrowsable(EditorBrowsableState.Never)] + public bool ShouldSerializeResponseModesSupported() + { + return ResponseModesSupported.Count > 0; + } + + /// + /// Gets a bool that determines if the 'response_types_supported' (ResponseTypesSupported) property should be serialized. + /// This is used by Json.NET in order to conditionally serialize properties. + /// + /// true if 'response_types_supported' (ResponseTypesSupported) is not empty; otherwise, false. + [EditorBrowsable(EditorBrowsableState.Never)] + public bool ShouldSerializeResponseTypesSupported() + { + return ResponseTypesSupported.Count > 0; + } + + /// + /// Gets a bool that determines if the 'SigningKeys' property should be serialized. + /// This is used by Json.NET in order to conditionally serialize properties. + /// + /// This method always returns false. + [EditorBrowsable(EditorBrowsableState.Never)] + public bool ShouldSerializeSigningKeys() + { + return false; + } + + /// + /// Gets a bool that determines if the 'scopes_supported' (ScopesSupported) property should be serialized. + /// This is used by Json.NET in order to conditionally serialize properties. + /// + /// true if 'scopes_supported' (ScopesSupported) is not empty; otherwise, false. + [EditorBrowsable(EditorBrowsableState.Never)] + public bool ShouldSerializeScopesSupported() + { + return ScopesSupported.Count > 0; + } + + /// + /// Gets a bool that determines if the 'subject_types_supported' (SubjectTypesSupported) property should be serialized. + /// This is used by Json.NET in order to conditionally serialize properties. + /// + /// true if 'subject_types_supported' (SubjectTypesSupported) is not empty; otherwise, false. + [EditorBrowsable(EditorBrowsableState.Never)] + public bool ShouldSerializeSubjectTypesSupported() + { + return SubjectTypesSupported.Count > 0; + } + + /// + /// Gets a bool that determines if the 'token_endpoint_auth_methods_supported' (TokenEndpointAuthMethodsSupported) property should be serialized. + /// This is used by Json.NET in order to conditionally serialize properties. + /// + /// true if 'token_endpoint_auth_methods_supported' (TokenEndpointAuthMethodsSupported) is not empty; otherwise, false. + [EditorBrowsable(EditorBrowsableState.Never)] + public bool ShouldSerializeTokenEndpointAuthMethodsSupported() + { + return TokenEndpointAuthMethodsSupported.Count > 0; + } + + /// + /// Gets a bool that determines if the 'token_endpoint_auth_signing_alg_values_supported' (TokenEndpointAuthSigningAlgValuesSupported) property should be serialized. + /// This is used by Json.NET in order to conditionally serialize properties. + /// + /// true if 'token_endpoint_auth_signing_alg_values_supported' (TokenEndpointAuthSigningAlgValuesSupported) is not empty; otherwise, false. + [EditorBrowsable(EditorBrowsableState.Never)] + public bool ShouldSerializeTokenEndpointAuthSigningAlgValuesSupported() + { + return TokenEndpointAuthSigningAlgValuesSupported.Count > 0; + } + + /// + /// Gets a bool that determines if the 'ui_locales_supported' (UILocalesSupported) property should be serialized. + /// This is used by Json.NET in order to conditionally serialize properties. + /// + /// true if 'ui_locales_supported' (UILocalesSupported) is not empty; otherwise, false. + [EditorBrowsable(EditorBrowsableState.Never)] + public bool ShouldSerializeUILocalesSupported() + { + return UILocalesSupported.Count > 0; + } + + /// + /// Gets a bool that determines if the 'userinfo_encryption_alg_values_supported' (UserInfoEndpointEncryptionAlgValuesSupported ) property should be serialized. + /// This is used by Json.NET in order to conditionally serialize properties. + /// + /// true if 'userinfo_encryption_alg_values_supported' (UserInfoEndpointEncryptionAlgValuesSupported ) is not empty; otherwise, false. + [EditorBrowsable(EditorBrowsableState.Never)] + public bool ShouldSerializeUserInfoEndpointEncryptionAlgValuesSupported() + { + return UserInfoEndpointEncryptionAlgValuesSupported.Count > 0; + } + + /// + /// Gets a bool that determines if the 'userinfo_encryption_enc_values_supported' (UserInfoEndpointEncryptionEncValuesSupported) property should be serialized. + /// This is used by Json.NET in order to conditionally serialize properties. + /// + /// true if 'userinfo_encryption_enc_values_supported' (UserInfoEndpointEncryptionEncValuesSupported) is not empty; otherwise, false. + [EditorBrowsable(EditorBrowsableState.Never)] + public bool ShouldSerializeUserInfoEndpointEncryptionEncValuesSupported() + { + return UserInfoEndpointEncryptionEncValuesSupported.Count > 0; + } + + /// + /// Gets a bool that determines if the 'userinfo_signing_alg_values_supported' (UserInfoEndpointSigningAlgValuesSupported) property should be serialized. + /// This is used by Json.NET in order to conditionally serialize properties. + /// + /// true if 'userinfo_signing_alg_values_supported' (UserInfoEndpointSigningAlgValuesSupported) is not empty; otherwise, false. + [EditorBrowsable(EditorBrowsableState.Never)] + public bool ShouldSerializeUserInfoEndpointSigningAlgValuesSupported() + { + return UserInfoEndpointSigningAlgValuesSupported.Count > 0; + } + +#endregion shouldserialize + } +} diff --git a/test/Microsoft.IdentityModel.Protocols.OpenIdConnect.Tests/Json/OpenIdConnectMessage6x.cs b/test/Microsoft.IdentityModel.Protocols.OpenIdConnect.Tests/Json/OpenIdConnectMessage6x.cs new file mode 100644 index 0000000000..23224ba29d --- /dev/null +++ b/test/Microsoft.IdentityModel.Protocols.OpenIdConnect.Tests/Json/OpenIdConnectMessage6x.cs @@ -0,0 +1,587 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System; +using System.Collections.Generic; +using System.Collections.Specialized; +using System.Reflection; +using Microsoft.IdentityModel.Logging; +using Microsoft.IdentityModel.Protocols.Json.Tests; +using Newtonsoft.Json.Linq; + +namespace Microsoft.IdentityModel.Protocols.OpenIdConnect.Json.Tests +{ + /// + /// Provides access to common OpenIdConnect parameters. + /// base class for authentication protocol messages. + /// This is the original OpenIdConnectMessage in the 6x branch. + /// Used for ensuring backcompat. + /// + public class OpenIdConnectMessage6x : AuthenticationProtocolMessage6x + { + /// + /// Initializes a new instance of the class. + /// + public OpenIdConnectMessage6x() { } + + /// + /// Initializes an instance of class with a json string. + /// + public OpenIdConnectMessage6x(string json) + { + if (string.IsNullOrEmpty(json)) + throw LogHelper.LogArgumentNullException("json"); + + try + { + SetJsonParameters(JObject.Parse(json)); + } + catch + { + throw LogHelper.LogExceptionMessage(new ArgumentException(LogHelper.FormatInvariant(LogMessages.IDX21106, json))); + } + + } + + /// + /// Initializes a new instance of the class. + /// + /// an to copy. + /// If 'other' is null. + protected OpenIdConnectMessage6x(OpenIdConnectMessage other) + { + if (other == null) + throw LogHelper.LogArgumentNullException("other"); + + foreach (KeyValuePair keyValue in other.Parameters) + { + SetParameter(keyValue.Key, keyValue.Value); + } + + AuthorizationEndpoint = other.AuthorizationEndpoint; + IssuerAddress = other.IssuerAddress; + RequestType = other.RequestType; + TokenEndpoint = other.TokenEndpoint; + EnableTelemetryParameters = other.EnableTelemetryParameters; + } + + /// + /// Initializes a new instance of the class. + /// + /// Collection of key value pairs. + public OpenIdConnectMessage6x(NameValueCollection nameValueCollection) + { + if (nameValueCollection == null) + throw LogHelper.LogArgumentNullException("nameValueCollection"); + + foreach (var key in nameValueCollection.AllKeys) + { + if (key != null) + { + SetParameter(key, nameValueCollection[key]); + } + } + } + + /// + /// Initializes a new instance of the class. + /// + /// Enumeration of key value pairs. + public OpenIdConnectMessage6x(IEnumerable> parameters) + { + if (parameters == null) + throw LogHelper.LogArgumentNullException("parameters"); + + foreach (KeyValuePair keyValue in parameters) + { + if (keyValue.Value != null && !string.IsNullOrWhiteSpace(keyValue.Key)) + { + foreach (string strValue in keyValue.Value) + { + if (strValue != null) + { + SetParameter(keyValue.Key, strValue); + break; + } + } + } + } + } + + /// + /// Initializes a new instance of the class. + /// + /// The JSON object from which the instance is created. + [Obsolete("The 'OpenIdConnectMessage(object json)' constructor is obsolete. Please use 'OpenIdConnectMessage(string json)' instead.")] + + public OpenIdConnectMessage6x(object json) + { + if (json == null) + throw LogHelper.LogArgumentNullException(nameof(json)); + + var jObject = JObject.Parse(json.ToString()); + SetJsonParameters(jObject); + } + + private void SetJsonParameters(JObject json) + { + if (json == null) + throw LogHelper.LogArgumentNullException("json"); + + foreach (var pair in json) + { + if (json.TryGetValue(pair.Key, out JToken value)) + { + SetParameter(pair.Key, value.ToString()); + } + } + } + + /// + /// Returns a new instance of with values copied from this object. + /// + /// A new object copied from this object + /// This is a shallow Clone. + public virtual OpenIdConnectMessage6x Clone() + { +#pragma warning disable CS0618 // Type or member is obsolete + return new OpenIdConnectMessage6x(this); +#pragma warning restore CS0618 // Type or member is obsolete + } + + /// + /// Creates an OpenIdConnect message using the current contents of this . + /// + /// The uri to use for a redirect. + public virtual string CreateAuthenticationRequestUrl() + { + OpenIdConnectMessage6x openIdConnectMessage = Clone(); + openIdConnectMessage.RequestType = OpenIdConnectRequestType.Authentication; + EnsureTelemetryValues(openIdConnectMessage); + return openIdConnectMessage.BuildRedirectUrl(); + } + + /// + /// Creates a query string using the current contents of this . + /// + /// The uri to use for a redirect. + public virtual string CreateLogoutRequestUrl() + { + OpenIdConnectMessage6x openIdConnectMessage = Clone(); + openIdConnectMessage.RequestType = OpenIdConnectRequestType.Logout; + EnsureTelemetryValues(openIdConnectMessage); + return openIdConnectMessage.BuildRedirectUrl(); + } + + /// + /// Adds telemetry values to the message parameters. + /// + private void EnsureTelemetryValues(OpenIdConnectMessage6x clonedMessage) + { + if (this.EnableTelemetryParameters) + { + clonedMessage.SetParameter(OpenIdConnectParameterNames.SkuTelemetry, SkuTelemetryValue); + clonedMessage.SetParameter(OpenIdConnectParameterNames.VersionTelemetry, typeof(OpenIdConnectMessage).GetTypeInfo().Assembly.GetName().Version.ToString()); + } + } + + /// + /// Gets or sets the value for the AuthorizationEndpoint + /// + public string AuthorizationEndpoint { get; set; } + + /// + /// Gets or sets 'access_Token'. + /// + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1707", Justification = "Follows protocol names")] + public string AccessToken + { + get { return GetParameter(OpenIdConnectParameterNames.AccessToken); } + set { SetParameter(OpenIdConnectParameterNames.AccessToken, value); } + } + + /// + /// Gets or sets 'acr_values'. + /// + public string AcrValues + { + get { return GetParameter(OpenIdConnectParameterNames.AcrValues); } + set { SetParameter(OpenIdConnectParameterNames.AcrValues, value); } + } + + /// + /// Gets or sets 'claims_Locales'. + /// + public string ClaimsLocales + { + get { return GetParameter(OpenIdConnectParameterNames.ClaimsLocales); } + set { SetParameter(OpenIdConnectParameterNames.ClaimsLocales, value); } + } + + /// + /// Gets or sets 'client_assertion'. + /// + public string ClientAssertion + { + get { return GetParameter(OpenIdConnectParameterNames.ClientAssertion); } + set { SetParameter(OpenIdConnectParameterNames.ClientAssertion, value); } + } + + /// + /// Gets or sets 'client_assertion_type'. + /// + public string ClientAssertionType + { + get { return GetParameter(OpenIdConnectParameterNames.ClientAssertionType); } + set { SetParameter(OpenIdConnectParameterNames.ClientAssertionType, value); } + } + + /// + /// Gets or sets 'client_id'. + /// + public string ClientId + { + get { return GetParameter(OpenIdConnectParameterNames.ClientId); } + set { SetParameter(OpenIdConnectParameterNames.ClientId, value); } + } + + /// + /// Gets or sets 'client_secret'. + /// + public string ClientSecret + { + get { return GetParameter(OpenIdConnectParameterNames.ClientSecret); } + set { SetParameter(OpenIdConnectParameterNames.ClientSecret, value); } + } + + /// + /// Gets or sets 'code'. + /// + public string Code + { + get { return GetParameter(OpenIdConnectParameterNames.Code); } + set { SetParameter(OpenIdConnectParameterNames.Code, value); } + } + + /// + /// Gets or sets 'display'. + /// + public string Display + { + get { return GetParameter(OpenIdConnectParameterNames.Display); } + set { SetParameter(OpenIdConnectParameterNames.Display, value); } + } + + /// + /// Gets or sets 'domain_hint'. + /// + public string DomainHint + { + get { return GetParameter(OpenIdConnectParameterNames.DomainHint); } + set { SetParameter(OpenIdConnectParameterNames.DomainHint, value); } + } + + /// + /// Gets or sets whether parameters for the library and version are sent on the query string for this instance. + /// This value is set to the value of EnableTelemetryParametersByDefault at message creation time. + /// + public bool EnableTelemetryParameters { get; set; } = EnableTelemetryParametersByDefault; + + + /// + /// Gets or sets whether parameters for the library and version are sent on the query string for all instances of . + /// + public static bool EnableTelemetryParametersByDefault { get; set; } = true; + + /// + /// Gets or sets 'error'. + /// + public string Error + { + get { return GetParameter(OpenIdConnectParameterNames.Error); } + set { SetParameter(OpenIdConnectParameterNames.Error, value); } + } + + /// + /// Gets or sets 'error_description'. + /// + public string ErrorDescription + { + get { return GetParameter(OpenIdConnectParameterNames.ErrorDescription); } + set { SetParameter(OpenIdConnectParameterNames.ErrorDescription, value); } + } + + /// + /// Gets or sets 'error_uri'. + /// + public string ErrorUri + { + get { return GetParameter(OpenIdConnectParameterNames.ErrorUri); } + set { SetParameter(OpenIdConnectParameterNames.ErrorUri, value); } + } + + /// + /// Gets or sets 'expires_in'. + /// + public string ExpiresIn + { + get { return GetParameter(OpenIdConnectParameterNames.ExpiresIn); } + set { SetParameter(OpenIdConnectParameterNames.ExpiresIn, value); } + } + + /// + /// Gets or sets 'grant_type'. + /// + public string GrantType + { + get { return GetParameter(OpenIdConnectParameterNames.GrantType); } + set { SetParameter(OpenIdConnectParameterNames.GrantType, value); } + } + + /// + /// Gets or sets 'id_token'. + /// + public string IdToken + { + get { return GetParameter(OpenIdConnectParameterNames.IdToken); } + set { SetParameter(OpenIdConnectParameterNames.IdToken, value); } + } + + /// + /// Gets or sets 'id_token_hint'. + /// + public string IdTokenHint + { + get { return GetParameter(OpenIdConnectParameterNames.IdTokenHint); } + set { SetParameter(OpenIdConnectParameterNames.IdTokenHint, value); } + } + + /// + /// Gets or sets 'identity_provider'. + /// + public string IdentityProvider + { + get { return GetParameter(OpenIdConnectParameterNames.IdentityProvider); } + set { SetParameter(OpenIdConnectParameterNames.IdentityProvider, value); } + } + + /// + /// Gets or sets 'iss'. + /// + public string Iss + { + get { return GetParameter(OpenIdConnectParameterNames.Iss); } + set { SetParameter(OpenIdConnectParameterNames.Iss, value); } + } + + /// + /// Gets or sets 'login_hint'. + /// + [property: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1707")] + public string LoginHint + { + get { return GetParameter(OpenIdConnectParameterNames.LoginHint); } + set { SetParameter(OpenIdConnectParameterNames.LoginHint, value); } + } + + /// + /// Gets or sets 'max_age'. + /// + public string MaxAge + { + get { return GetParameter(OpenIdConnectParameterNames.MaxAge); } + set { SetParameter(OpenIdConnectParameterNames.MaxAge, value); } + } + + /// + /// Gets or sets 'nonce'. + /// + public string Nonce + { + get { return GetParameter(OpenIdConnectParameterNames.Nonce); } + set { SetParameter(OpenIdConnectParameterNames.Nonce, value); } + } + + /// + /// Gets or sets 'password'. + /// + public string Password + { + get { return GetParameter(OpenIdConnectParameterNames.Password); } + set { SetParameter(OpenIdConnectParameterNames.Password, value); } + } + + /// + /// Gets or sets 'post_logout_redirect_uri'. + /// + public string PostLogoutRedirectUri + { + get { return GetParameter(OpenIdConnectParameterNames.PostLogoutRedirectUri); } + set { SetParameter(OpenIdConnectParameterNames.PostLogoutRedirectUri, value); } + } + + /// + /// Gets or sets 'prompt'. + /// + public string Prompt + { + get { return GetParameter(OpenIdConnectParameterNames.Prompt); } + set { SetParameter(OpenIdConnectParameterNames.Prompt, value); } + } + + /// + /// Gets or sets 'redirect_uri'. + /// + public string RedirectUri + { + get { return GetParameter(OpenIdConnectParameterNames.RedirectUri); } + set { SetParameter(OpenIdConnectParameterNames.RedirectUri, value); } + } + + /// + /// Gets or sets 'refresh_token'. + /// + public string RefreshToken + { + get { return GetParameter(OpenIdConnectParameterNames.RefreshToken); } + set { SetParameter(OpenIdConnectParameterNames.RefreshToken, value); } + } + + /// + /// Gets or set the request type for this message + /// + /// This is helpful when sending different messages through a common routine, when extra parameters need to be set or checked. + public OpenIdConnectRequestType RequestType + { + get; + set; + } + + /// + /// Gets or sets 'request_uri'. + /// + public string RequestUri + { + get { return GetParameter(OpenIdConnectParameterNames.RequestUri); } + set { SetParameter(OpenIdConnectParameterNames.RequestUri, value); } + } + + /// + /// Gets or sets 'response_mode'. + /// + public string ResponseMode + { + get { return GetParameter(OpenIdConnectParameterNames.ResponseMode); } + set { SetParameter(OpenIdConnectParameterNames.ResponseMode, value); } + } + + /// + /// Gets or sets 'response_type'. + /// + public string ResponseType + { + get { return GetParameter(OpenIdConnectParameterNames.ResponseType); } + set { SetParameter(OpenIdConnectParameterNames.ResponseType, value); } + } + + /// + /// Gets or sets 'resource' + /// + public string Resource + { + get { return GetParameter(OpenIdConnectParameterNames.Resource); } + set { SetParameter(OpenIdConnectParameterNames.Resource, value); } + } + + /// + /// Gets or sets 'scope'. + /// + public string Scope + { + get { return GetParameter(OpenIdConnectParameterNames.Scope); } + set { SetParameter(OpenIdConnectParameterNames.Scope, value); } + } + + /// + /// Gets or sets 'session_state'. + /// + public string SessionState + { + get { return GetParameter(OpenIdConnectParameterNames.SessionState); } + set { SetParameter(OpenIdConnectParameterNames.SessionState, value); } + } + + /// + /// Gets or sets 'sid'. + /// + public string Sid + { + get { return GetParameter(OpenIdConnectParameterNames.Sid); } + set { SetParameter(OpenIdConnectParameterNames.Sid, value); } + } + + /// + /// Gets the string that is sent as telemetry data in an OpenIdConnectMessage. + /// + public string SkuTelemetryValue { get; set; } = IdentityModelTelemetryUtil.ClientSku; + + /// + /// Gets or sets 'state'. + /// + public string State + { + get { return GetParameter(OpenIdConnectParameterNames.State); } + set { SetParameter(OpenIdConnectParameterNames.State, value); } + } + + /// + /// Gets or sets 'target_link_uri'. + /// + public string TargetLinkUri + { + get { return GetParameter(OpenIdConnectParameterNames.TargetLinkUri); } + set { SetParameter(OpenIdConnectParameterNames.TargetLinkUri, value); } + } + + /// + /// Gets or sets the value for the token endpoint. + /// + public string TokenEndpoint { get; set; } + + /// + /// Gets or sets 'token_type'. + /// + public string TokenType + { + get { return GetParameter(OpenIdConnectParameterNames.TokenType); } + set { SetParameter(OpenIdConnectParameterNames.TokenType, value); } + } + + /// + /// Gets or sets 'ui_locales'. + /// + public string UiLocales + { + get { return GetParameter(OpenIdConnectParameterNames.UiLocales); } + set { SetParameter(OpenIdConnectParameterNames.UiLocales, value); } + } + + /// + /// Gets or sets 'user_id'. + /// + public string UserId + { + get { return GetParameter(OpenIdConnectParameterNames.UserId); } + set { SetParameter(OpenIdConnectParameterNames.UserId, value); } + } + + /// + /// Gets or sets 'username'. + /// + public string Username + { + get { return GetParameter(OpenIdConnectParameterNames.Username); } + set { SetParameter(OpenIdConnectParameterNames.Username, value); } + } + } +} diff --git a/test/Microsoft.IdentityModel.Protocols.OpenIdConnect.Tests/Microsoft.IdentityModel.Protocols.OpenIdConnect.Tests.csproj b/test/Microsoft.IdentityModel.Protocols.OpenIdConnect.Tests/Microsoft.IdentityModel.Protocols.OpenIdConnect.Tests.csproj index 6b4c50db2f..586b157dc8 100644 --- a/test/Microsoft.IdentityModel.Protocols.OpenIdConnect.Tests/Microsoft.IdentityModel.Protocols.OpenIdConnect.Tests.csproj +++ b/test/Microsoft.IdentityModel.Protocols.OpenIdConnect.Tests/Microsoft.IdentityModel.Protocols.OpenIdConnect.Tests.csproj @@ -19,6 +19,7 @@ + diff --git a/test/Microsoft.IdentityModel.Protocols.OpenIdConnect.Tests/OpenIdConnectConfigurationRetrieverTests.cs b/test/Microsoft.IdentityModel.Protocols.OpenIdConnect.Tests/OpenIdConnectConfigurationRetrieverTests.cs index 074e4e2833..e59c3d851c 100644 --- a/test/Microsoft.IdentityModel.Protocols.OpenIdConnect.Tests/OpenIdConnectConfigurationRetrieverTests.cs +++ b/test/Microsoft.IdentityModel.Protocols.OpenIdConnect.Tests/OpenIdConnectConfigurationRetrieverTests.cs @@ -57,7 +57,7 @@ public async Task FromJson() await GetConfigurationFromTextAsync(OpenIdConfigData.OpenIdConnectMetadataBadUriKeysString, string.Empty, expectedException: ExpectedException.IOException()); // stream is not well formated - await GetConfigurationFromTextAsync(OpenIdConfigData.OpenIdConnectMetadataBadFormatString, string.Empty, expectedException: new ExpectedException(typeExpected: typeof(JsonReaderException))); + await GetConfigurationFromTextAsync(OpenIdConfigData.OpenIdConnectMetadataBadFormatString, string.Empty, expectedException: new ExpectedException(typeExpected: typeof(System.Text.Json.JsonException), ignoreInnerException: true)); configuration = await GetConfigurationFromMixedAsync(OpenIdConfigData.OpenIdConnectMetadataSingleX509DataString, expectedException: ExpectedException.NoExceptionExpected); IdentityComparer.AreEqual(configuration, OpenIdConfigData.SingleX509Data, context); diff --git a/test/Microsoft.IdentityModel.Protocols.OpenIdConnect.Tests/OpenIdConnectConfigurationTests.cs b/test/Microsoft.IdentityModel.Protocols.OpenIdConnect.Tests/OpenIdConnectConfigurationTests.cs index c77c414df8..606715ca43 100644 --- a/test/Microsoft.IdentityModel.Protocols.OpenIdConnect.Tests/OpenIdConnectConfigurationTests.cs +++ b/test/Microsoft.IdentityModel.Protocols.OpenIdConnect.Tests/OpenIdConnectConfigurationTests.cs @@ -6,6 +6,7 @@ using System.Collections.ObjectModel; using System.Globalization; using System.Reflection; +using System.Text.Json; using Microsoft.IdentityModel.TestUtils; using Microsoft.IdentityModel.Tokens; using Newtonsoft.Json; @@ -23,7 +24,7 @@ public void Constructors() var context = new CompareContext { Title = "OpenIdConnectConfigurationTests.Constructors" }; RunOpenIdConnectConfigurationTest((string)null, new OpenIdConnectConfiguration(), ExpectedException.ArgumentNullException(), context); RunOpenIdConnectConfigurationTest(OpenIdConfigData.JsonAllValues, OpenIdConfigData.FullyPopulated, ExpectedException.NoExceptionExpected, context); - RunOpenIdConnectConfigurationTest(OpenIdConfigData.OpenIdConnectMetatadataBadJson, null, ExpectedException.ArgumentException(substringExpected: "IDX21815:", inner: typeof(JsonReaderException)), context); + RunOpenIdConnectConfigurationTest(OpenIdConfigData.OpenIdConnectMetatadataBadJson, null, ExpectedException.ArgumentException(substringExpected: "IDX21815:", inner: typeof(System.Text.Json.JsonException)), context); TestUtilities.AssertFailIfErrors(context); } @@ -92,8 +93,9 @@ public void DeserializeOpenIdConnectConfigurationWithSigningKeys() TestUtilities.WriteHeader($"{this}.DeserializeOpenIdConnectConfigurationWithSigningKeys"); var context = new CompareContext(); - var config = OpenIdConnectConfiguration.Create( - OpenIdConnectConfiguration.Write(new OpenIdConnectConfiguration(OpenIdConfigData.JsonWithSigningKeys))); + string json = OpenIdConnectConfiguration.Write(new OpenIdConnectConfiguration(OpenIdConfigData.JsonWithSigningKeys)); + + var config = OpenIdConnectConfiguration.Create(json); // "SigningKeys" should be found in AdditionalData. if (!config.AdditionalData.ContainsKey("SigningKeys")) @@ -230,7 +232,7 @@ public void EmptyCollectionSerialization() var oidcWithEmptyCollections = new OpenIdConnectConfiguration(); var oidcWithEmptyCollectionsJson = OpenIdConnectConfiguration.Write(oidcWithEmptyCollections); - IdentityComparer.AreEqual(oidcWithEmptyCollectionsJson, "{\"JsonWebKeySet\":null}", context); + IdentityComparer.AreEqual(oidcWithEmptyCollectionsJson, "{}", context); TestUtilities.AssertFailIfErrors(context); } diff --git a/test/Microsoft.IdentityModel.Protocols.OpenIdConnect.Tests/OpenIdConnectMessageTests.cs b/test/Microsoft.IdentityModel.Protocols.OpenIdConnect.Tests/OpenIdConnectMessageTests.cs index 3337fb1f9b..9bf9cf6e37 100644 --- a/test/Microsoft.IdentityModel.Protocols.OpenIdConnect.Tests/OpenIdConnectMessageTests.cs +++ b/test/Microsoft.IdentityModel.Protocols.OpenIdConnect.Tests/OpenIdConnectMessageTests.cs @@ -6,6 +6,7 @@ using System.Globalization; using System.Reflection; using Microsoft.IdentityModel.TestUtils; +using Microsoft.IdentityModel.Protocols.OpenIdConnect.Json.Tests; using Newtonsoft.Json.Linq; using Xunit; @@ -25,15 +26,17 @@ public void Constructors(OpenIdConnectMessageTheoryData theoryData) TestUtilities.WriteHeader($"{this}.Constructors", theoryData); var context = new CompareContext($"{this}.ReadMetadata, {theoryData.TestId}"); OpenIdConnectMessage messageFromJson; - OpenIdConnectMessage messageFromJsonObj; + // TODO - these local variable has been left commented out as + // we will need this to check a 6x -> 7x compatibility test that needs to be written. + //OpenIdConnectMessage messageFromJsonObj; var diffs = new List(); try { messageFromJson = new OpenIdConnectMessage(theoryData.Json); -#pragma warning disable CS0618 // Type or member is obsolete - messageFromJsonObj = new OpenIdConnectMessage(theoryData.JObject); -#pragma warning restore CS0618 // Type or member is obsolete - IdentityComparer.AreEqual(messageFromJson, messageFromJsonObj, context); +//#pragma warning disable CS0618 // Type or member is obsolete +// messageFromJsonObj = new OpenIdConnectMessage6x(theoryData.JObject); +//#pragma warning restore CS0618 // Type or member is obsolete + //IdentityComparer.AreEqual(messageFromJson, messageFromJsonObj, context); IdentityComparer.AreEqual(messageFromJson, theoryData.Message, context); theoryData.ExpectedException.ProcessNoException(); } @@ -49,38 +52,27 @@ public static TheoryData ConstructorsTheoryData( { return new TheoryData { - new OpenIdConnectMessageTheoryData + new OpenIdConnectMessageTheoryData("EmptyString") { - First = true, ExpectedException = ExpectedException.ArgumentNullException("IDX10000:"), - Json = "", - TestId = "empty string" + Json = "" }, - new OpenIdConnectMessageTheoryData + new OpenIdConnectMessageTheoryData("NullString") { - ExpectedException = ExpectedException.ArgumentNullException("IDX10000:"), - TestId = "null string" - }, - new OpenIdConnectMessageTheoryData - { - ExpectedException = ExpectedException.ArgumentNullException("IDX10000:"), - Json = @"{""response_mode"":""responseMode"", ""response_type"":""responseType"", ""refresh_token"":""refreshToken""}", - TestId = "null jobject" + ExpectedException = ExpectedException.ArgumentNullException("IDX10000:") }, - new OpenIdConnectMessageTheoryData + new OpenIdConnectMessageTheoryData("ResponseModeDuplicated") { ExpectedException = ExpectedException.ArgumentException("IDX21106"), - Json = @"{""response_mode"":""responseMode"";""respone_mode"":""duplicateResponeMode""}", - TestId = "ResponseMode duplicated" + Json = @"{""response_mode"":""responseMode"";""respone_mode"":""duplicateResponeMode""}" }, - new OpenIdConnectMessageTheoryData + new OpenIdConnectMessageTheoryData("EmptyJsonStringEmptyJobj") { JObject = new JObject(), Json = "{}", - Message = new OpenIdConnectMessage(), - TestId = "empty json string, empty jobj" + Message = new OpenIdConnectMessage() }, - new OpenIdConnectMessageTheoryData + new OpenIdConnectMessageTheoryData("ValidJson") { JObject = JObject.Parse(@"{""response_mode"":""responseMode"", ""response_type"":""responseType"", ""refresh_token"":""refreshToken""}"), Json = @"{""response_mode"":""responseMode"", ""response_type"":""responseType"", ""refresh_token"":""refreshToken""}", @@ -89,8 +81,7 @@ public static TheoryData ConstructorsTheoryData( RefreshToken = "refreshToken", ResponseMode = "responseMode", ResponseType = "responseType" - }, - TestId = "ValidJson" + } } }; } @@ -99,7 +90,7 @@ public static TheoryData ConstructorsTheoryData( public void Defaults() { List errors = new List(); - var message = new OpenIdConnectMessage(); + var message = new OpenIdConnectMessage6x(); if (message.AcrValues != null) errors.Add("message.ArcValues != null"); @@ -622,6 +613,10 @@ public override string CreateLogoutRequestUrl() public class OpenIdConnectMessageTheoryData : TheoryDataBase { + public OpenIdConnectMessageTheoryData() { } + + public OpenIdConnectMessageTheoryData(string testId) : base(testId) { } + public OpenIdConnectMessage Message { get; set; } public string Json { get; set; } diff --git a/test/Microsoft.IdentityModel.Protocols.SignedHttpRequest.Tests/PopKeyResolvingTests.cs b/test/Microsoft.IdentityModel.Protocols.SignedHttpRequest.Tests/PopKeyResolvingTests.cs index ca5101af74..4631d668c1 100644 --- a/test/Microsoft.IdentityModel.Protocols.SignedHttpRequest.Tests/PopKeyResolvingTests.cs +++ b/test/Microsoft.IdentityModel.Protocols.SignedHttpRequest.Tests/PopKeyResolvingTests.cs @@ -26,7 +26,7 @@ public async Task ResolvePopKeyFromCnfClaimAsync(ResolvePopKeyTheoryData theoryD { var signedHttpRequestValidationContext = theoryData.BuildSignedHttpRequestValidationContext(); var handler = new SignedHttpRequestHandlerPublic(); - _ = await handler.ResolvePopKeyFromCnfClaimPublicAsync(theoryData.ConfirmationClaim, theoryData.SignedHttpRequestToken, theoryData.ValidatedAccessToken, signedHttpRequestValidationContext, CancellationToken.None).ConfigureAwait(false); + _ = await handler.ResolvePopKeyFromCnfClaimAsync(theoryData.ConfirmationClaim, theoryData.SignedHttpRequestToken, theoryData.ValidatedAccessToken, signedHttpRequestValidationContext, CancellationToken.None).ConfigureAwait(false); if ((bool)signedHttpRequestValidationContext.CallContext.PropertyBag[theoryData.MethodToCall] == false) context.AddDiff($"{theoryData.MethodToCall} was not called."); @@ -144,8 +144,8 @@ public async Task ResolvePopKeyAsync(ResolvePopKeyTheoryData theoryData) try { var signedHttpRequestValidationContext = theoryData.BuildSignedHttpRequestValidationContext(); - var handler = new SignedHttpRequestHandlerPublic(); - _ = await handler.ResolvePopKeyPublicAsync(theoryData.SignedHttpRequestToken, theoryData.ValidatedAccessToken, signedHttpRequestValidationContext, CancellationToken.None).ConfigureAwait(false); + var handler = new SignedHttpRequestHandler(); + _ = await handler.ResolvePopKeyAsync(theoryData.SignedHttpRequestToken, theoryData.ValidatedAccessToken, signedHttpRequestValidationContext, CancellationToken.None).ConfigureAwait(false); if ((bool)signedHttpRequestValidationContext.CallContext.PropertyBag[theoryData.MethodToCall] == false) context.AddDiff($"{theoryData.MethodToCall} was not called."); @@ -200,8 +200,8 @@ public void ResolvePopKeyFromJwk(ResolvePopKeyTheoryData theoryData) try { var signedHttpRequestValidationContext = theoryData.BuildSignedHttpRequestValidationContext(); - var handler = new SignedHttpRequestHandlerPublic(); - _ = handler.ResolvePopKeyFromJwkPublic(theoryData.PopKeyString, null, null, signedHttpRequestValidationContext); + var handler = new SignedHttpRequestHandler(); + _ = handler.ResolvePopKeyFromJwk(theoryData.PopKeyString, signedHttpRequestValidationContext); theoryData.ExpectedException.ProcessNoException(context); } @@ -271,8 +271,8 @@ public async Task ResolvePopKeyFromJweAsync(ResolvePopKeyTheoryData theoryData) try { var signedHttpRequestValidationContext = theoryData.BuildSignedHttpRequestValidationContext(); - var handler = new SignedHttpRequestHandlerPublic(); - _ = await handler.ResolvePopKeyFromJwePublicAsync(theoryData.PopKeyString, null, null, signedHttpRequestValidationContext, CancellationToken.None).ConfigureAwait(false); + var handler = new SignedHttpRequestHandler(); + _ = await handler.ResolvePopKeyFromJweAsync(theoryData.PopKeyString, signedHttpRequestValidationContext, CancellationToken.None).ConfigureAwait(false); theoryData.ExpectedException.ProcessNoException(context); } @@ -396,7 +396,7 @@ public async Task ResolvePopKeyFromJkuAsync(ResolvePopKeyTheoryData theoryData) { var signedHttpRequestValidationContext = theoryData.BuildSignedHttpRequestValidationContext(); var handler = new SignedHttpRequestHandlerPublic(); - var popKey = await handler.ResolvePopKeyFromJkuPublicAsync(string.Empty, null, null, null, signedHttpRequestValidationContext, CancellationToken.None).ConfigureAwait(false); + var popKey = await handler.ResolvePopKeyFromJkuAsync(string.Empty, null, signedHttpRequestValidationContext, CancellationToken.None).ConfigureAwait(false); if (popKey == null) context.AddDiff("Resolved Pop key is null."); @@ -463,8 +463,8 @@ public void GetCnfClaimValue(ResolvePopKeyTheoryData theoryData) var context = TestUtilities.WriteHeader($"{this}.GetCnfClaimValue", theoryData); try { - var handler = new SignedHttpRequestHandlerPublic(); - _ = handler.GetCnfClaimValuePublic(null, theoryData.ValidatedAccessToken, null); + var handler = new SignedHttpRequestHandler(); + _ = handler.GetCnfClaimValue(null, theoryData.ValidatedAccessToken, null); theoryData.ExpectedException.ProcessNoException(context); } catch (Exception ex) @@ -528,7 +528,7 @@ public async Task ResolvePopKeyFromJkuKidAsync(ResolvePopKeyTheoryData theoryDat { var signedHttpRequestValidationContext = theoryData.BuildSignedHttpRequestValidationContext(); var handler = new SignedHttpRequestHandlerPublic(); - var popKey = await handler.ResolvePopKeyFromJkuPublicAsync(string.Empty, JObject.Parse($@"{{""kid"": ""{theoryData.Kid}""}}"), null, null, signedHttpRequestValidationContext, CancellationToken.None).ConfigureAwait(false); + var popKey = await handler.ResolvePopKeyFromJkuAsync(string.Empty, JObject.Parse($@"{{""kid"": ""{theoryData.Kid}""}}"), signedHttpRequestValidationContext, CancellationToken.None).ConfigureAwait(false); if (popKey == null) context.AddDiff("Resolved Pop key is null."); @@ -624,8 +624,8 @@ public async Task GetPopKeysFromJkuAsync(ResolvePopKeyTheoryData theoryData) try { var signedHttpRequestValidationContext = theoryData.BuildSignedHttpRequestValidationContext(); - var handler = new SignedHttpRequestHandlerPublic(); - var popKeys = await handler.GetPopKeysFromJkuPublicAsync(theoryData.JkuSetUrl, null, null, signedHttpRequestValidationContext, CancellationToken.None).ConfigureAwait(false); + var handler = new SignedHttpRequestHandler(); + var popKeys = await handler.GetPopKeysFromJkuAsync(theoryData.JkuSetUrl, signedHttpRequestValidationContext, CancellationToken.None).ConfigureAwait(false); if (popKeys.Count != theoryData.ExpectedNumberOfPopKeysReturned) context.AddDiff($"Number of returned pop keys {popKeys.Count} is not the same as the expected: {theoryData.ExpectedNumberOfPopKeysReturned}."); @@ -739,7 +739,7 @@ public async Task ResolvePopKeyFromKeyIdentifierAsync(ResolvePopKeyTheoryData th { var signedHttpRequestValidationContext = theoryData.BuildSignedHttpRequestValidationContext(); var handler = new SignedHttpRequestHandlerPublic(); - var popKeys = await handler.ResolvePopKeyFromKeyIdentifierPublicAsync(theoryData.Kid, theoryData.SignedHttpRequestToken, theoryData.ValidatedAccessToken, signedHttpRequestValidationContext, CancellationToken.None).ConfigureAwait(false); + var popKeys = await handler.ResolvePopKeyFromKeyIdentifierAsync(theoryData.Kid, theoryData.SignedHttpRequestToken, theoryData.ValidatedAccessToken, signedHttpRequestValidationContext, CancellationToken.None).ConfigureAwait(false); theoryData.ExpectedException.ProcessNoException(context); } catch (Exception ex) diff --git a/test/Microsoft.IdentityModel.Protocols.SignedHttpRequest.Tests/SignedHttpRequestCreationTests.cs b/test/Microsoft.IdentityModel.Protocols.SignedHttpRequest.Tests/SignedHttpRequestCreationTests.cs index 24d559e5d7..7627f322ed 100644 --- a/test/Microsoft.IdentityModel.Protocols.SignedHttpRequest.Tests/SignedHttpRequestCreationTests.cs +++ b/test/Microsoft.IdentityModel.Protocols.SignedHttpRequest.Tests/SignedHttpRequestCreationTests.cs @@ -3,16 +3,16 @@ using System; using System.Collections.Generic; +using System.IO; using System.Text; +using System.Text.Encodings.Web; +using System.Text.Json; using Microsoft.IdentityModel.JsonWebTokens; using Microsoft.IdentityModel.TestUtils; using Microsoft.IdentityModel.Tokens; -using Newtonsoft.Json; using Newtonsoft.Json.Linq; using Xunit; -#pragma warning disable CS3016 // Arrays as attribute arguments is not CLS-compliant - namespace Microsoft.IdentityModel.Protocols.SignedHttpRequest.Tests { public class SignedHttpRequestCreationTests @@ -22,11 +22,20 @@ public void CreateSignedHttpRequest() { var context = TestUtilities.WriteHeader($"{this}.CreateSignedHttpRequest", "", true); - var handler = new SignedHttpRequestHandlerPublic(); + var handler = new SignedHttpRequestHandler(); + var signedHttpRequestDescriptor = + new SignedHttpRequestDescriptor( + SignedHttpRequestTestUtils.DefaultEncodedAccessToken, + new HttpRequestData(), + SignedHttpRequestTestUtils.DefaultSigningCredentials, + new SignedHttpRequestCreationParameters() + { + CreateM = false, + CreateP = false, + CreateU = false + }); - var signedHttpRequestDescriptor = new SignedHttpRequestDescriptor(SignedHttpRequestTestUtils.DefaultEncodedAccessToken, new HttpRequestData(), SignedHttpRequestTestUtils.DefaultSigningCredentials, new SignedHttpRequestCreationParameters() { CreateM = false, CreateP = false, CreateU = false }); var signedHttpRequestString = handler.CreateSignedHttpRequest(signedHttpRequestDescriptor); - var tvp = new TokenValidationParameters() { ValidateAudience = false, @@ -35,8 +44,8 @@ public void CreateSignedHttpRequest() ValidateLifetime = false, IssuerSigningKey = SignedHttpRequestTestUtils.DefaultSigningCredentials.Key }; - var result = new JsonWebTokenHandler().ValidateToken(signedHttpRequestString, tvp); + var result = new JsonWebTokenHandler().ValidateToken(signedHttpRequestString, tvp); if (result.IsValid == false) context.AddDiff($"Not able to create and validate signed http request token"); @@ -48,14 +57,14 @@ public void CreateSignedHttpRequestWithAdditionalHeaderClaims() { var context = TestUtilities.WriteHeader($"{this}.CreateSignedHttpRequestWithAdditionalHeaderClaims", "", true); - var handler = new SignedHttpRequestHandlerPublic(); - + var handler = new SignedHttpRequestHandler(); // The 'alg', 'kid', and 'x5t' claims are added by default based on the provided and SHOULD NOT be included in this dictionary as this - /// will result in an exception being thrown. + // will result in an exception being thrown. var signedHttpRequestDescriptor = new SignedHttpRequestDescriptor(SignedHttpRequestTestUtils.DefaultEncodedAccessToken, new HttpRequestData(), SignedHttpRequestTestUtils.DefaultSigningCredentials, new SignedHttpRequestCreationParameters() { CreateM = false, CreateP = false, CreateU = false }) { - AdditionalHeaderClaims = new Dictionary() { { "kid", "kid_is_not_allowd" } } + AdditionalHeaderClaims = new Dictionary() { { "kid", "kid_is_not_allowed" } } }; + Assert.Throws(() => handler.CreateSignedHttpRequest(signedHttpRequestDescriptor)); // allowed additional header claims @@ -63,9 +72,9 @@ public void CreateSignedHttpRequestWithAdditionalHeaderClaims() { AdditionalHeaderClaims = new Dictionary() { { "additionalHeaderClaim1", "val1" }, { "additionalHeaderClaim2", "val2" } } }; - var signedHttpRequestString = handler.CreateSignedHttpRequest(signedHttpRequestDescriptor); - var tvp = new TokenValidationParameters() + var signedHttpRequestString = handler.CreateSignedHttpRequest(signedHttpRequestDescriptor); + var tvp = new TokenValidationParameters { ValidateAudience = false, ValidateIssuer = false, @@ -73,6 +82,7 @@ public void CreateSignedHttpRequestWithAdditionalHeaderClaims() ValidateLifetime = false, IssuerSigningKey = SignedHttpRequestTestUtils.DefaultSigningCredentials.Key }; + var result = new JsonWebTokenHandler().ValidateToken(signedHttpRequestString, tvp); if (result.IsValid == false) @@ -88,10 +98,10 @@ public void CreateClaimCalls(CreateSignedHttpRequestTheoryData theoryData) var context = TestUtilities.WriteHeader($"{this}.CreateClaimCalls", theoryData); try { - var handler = new SignedHttpRequestHandlerPublic(); + var handler = new SignedHttpRequestHandler(); var signedHttpRequestDescriptor = theoryData.BuildSignedHttpRequestDescriptor(); - var payloadString = handler.CreateHttpRequestPayloadPublic(signedHttpRequestDescriptor, theoryData.CallContext); + var payloadString = handler.CreateHttpRequestPayload(signedHttpRequestDescriptor, theoryData.CallContext); var payload = JObject.Parse(payloadString); foreach (var payloadItem in payload) @@ -122,9 +132,8 @@ public static TheoryData CreateClaimCallsTheo { return new TheoryData { - new CreateSignedHttpRequestTheoryData + new CreateSignedHttpRequestTheoryData("NoClaimsCreated") { - First = true, ExpectedPayloadClaims = new List() { "at" }, SignedHttpRequestCreationParameters = new SignedHttpRequestCreationParameters() { @@ -137,10 +146,9 @@ public static TheoryData CreateClaimCallsTheo CreateQ = false, CreateTs = false, CreateU = false, - }, - TestId = "NoClaimsCreated", + } }, - new CreateSignedHttpRequestTheoryData + new CreateSignedHttpRequestTheoryData("AllClaimsCreated") { ExpectedPayloadClaims = new List() { "at", "b", "h", "m", "nonce", "p", "q", "ts", "u", "additionalClaim", "cnf" }, SignedHttpRequestCreationParameters = new SignedHttpRequestCreationParameters() @@ -162,15 +170,8 @@ public static TheoryData CreateClaimCallsTheo { "header1", new List() {"headerValue1"} } }, HttpRequestMethod = "GET", - HttpRequestUri = new Uri("https://www.contoso.com/path1?queryParam1=quertValue1"), - TestId = "AllClaimsCreated", - }, - new CreateSignedHttpRequestTheoryData - { - Payload = null, - ExpectedException = ExpectedException.ArgumentNullException(), - TestId = "NullPayload", - }, + HttpRequestUri = new Uri("https://www.contoso.com/path1?queryParam1=quertValue1") + } }; } } @@ -179,26 +180,22 @@ public static TheoryData CreateClaimCallsTheo public void CreateAtClaim(CreateSignedHttpRequestTheoryData theoryData) { var context = TestUtilities.WriteHeader($"{this}.CreateAtClaimTheoryData", theoryData); + Utf8JsonWriter writer = null; try { - var handler = new SignedHttpRequestHandlerPublic(); - var signedHttpRequestDescriptor = theoryData.BuildSignedHttpRequestDescriptor(); - - handler.AddAtClaimPublic(theoryData.Payload, signedHttpRequestDescriptor); - var payload = JObject.FromObject(theoryData.Payload); - - if (!payload.ContainsKey(theoryData.ExpectedClaim)) - context.AddDiff($"Payload doesn't contain the claim '{theoryData.ExpectedClaim}'"); - - if (!IdentityComparer.AreStringsEqual(payload.Value(theoryData.ExpectedClaim), theoryData.ExpectedClaimValue, context)) - context.AddDiff($"Value of '{theoryData.ExpectedClaim}' claim is '{payload.Value(theoryData.ExpectedClaim)}', but expected value was '{theoryData.ExpectedClaimValue}'"); - + writer = theoryData.GetWriter(); + theoryData.Handler.AddAtClaim(ref writer, theoryData.BuildSignedHttpRequestDescriptor()); + CheckClaimValue(ref writer, theoryData, context); theoryData.ExpectedException.ProcessNoException(context); theoryData.ExpectedException.ProcessNoException(context); } catch (Exception ex) { theoryData.ExpectedException.ProcessException(ex, context); } + finally + { + writer?.Dispose(); + } TestUtilities.AssertFailIfErrors(context); } @@ -209,19 +206,11 @@ public static TheoryData CreateAtClaimTheoryD { return new TheoryData { - new CreateSignedHttpRequestTheoryData + new CreateSignedHttpRequestTheoryData("validAt") { - First = true, ExpectedClaim = SignedHttpRequestClaimTypes.At, - ExpectedClaimValue = SignedHttpRequestTestUtils.DefaultEncodedAccessToken, - TestId = "ValidAt", - }, - new CreateSignedHttpRequestTheoryData - { - Payload = null, - ExpectedException = ExpectedException.ArgumentNullException(), - TestId = "NullPayload", - }, + ExpectedClaimValue = $"{{\"at\":\"{SignedHttpRequestTestUtils.DefaultEncodedAccessToken}\"}}", + } }; } } @@ -230,31 +219,22 @@ public static TheoryData CreateAtClaimTheoryD public void CreateTsClaim(CreateSignedHttpRequestTheoryData theoryData) { var context = TestUtilities.WriteHeader($"{this}.CreateTsClaim", theoryData); + Utf8JsonWriter writer = null; try { - var handler = new SignedHttpRequestHandlerPublic(); - var signedHttpRequestDescriptor = theoryData.BuildSignedHttpRequestDescriptor(); - - handler.AddTsClaimPublic(theoryData.Payload, signedHttpRequestDescriptor); - var payload = JObject.FromObject(theoryData.Payload); - - if (!payload.ContainsKey(theoryData.ExpectedClaim)) - context.AddDiff($"Payload doesn't contain the claim '{theoryData.ExpectedClaim}'"); - - var delta = 5; - var expectedTs = (long)theoryData.ExpectedClaimValue; - var actualTs = payload.Value(theoryData.ExpectedClaim); - if (Math.Abs(expectedTs - actualTs) > delta) - { - context.AddDiff($"Expected ts '{expectedTs}' was not the same as the actual ts '{actualTs}' within a tolerance of '{delta}' seconds."); - } - + writer = theoryData.GetWriter(); + theoryData.Handler.AddTsClaim(ref writer, theoryData.BuildSignedHttpRequestDescriptor()); + CheckClaimValue(ref writer, theoryData, context); theoryData.ExpectedException.ProcessNoException(context); } catch (Exception ex) { theoryData.ExpectedException.ProcessException(ex, context); } + finally + { + writer?.Dispose(); + } TestUtilities.AssertFailIfErrors(context); } @@ -266,33 +246,23 @@ public static TheoryData CreateTsClaimTheoryD var timeNow = DateTime.UtcNow; return new TheoryData { - new CreateSignedHttpRequestTheoryData + new CreateSignedHttpRequestTheoryData("ValidTs") { - First = true, ExpectedClaim = SignedHttpRequestClaimTypes.Ts, - ExpectedClaimValue = (long)(timeNow - EpochTime.UnixEpoch).TotalSeconds, - TestId = "ValidTs", + ExpectedClaimValue = (long)(timeNow - EpochTime.UnixEpoch).TotalSeconds }, - new CreateSignedHttpRequestTheoryData + new CreateSignedHttpRequestTheoryData("ValidTsWithTimeAdjustmentMinus") { SignedHttpRequestCreationParameters = new SignedHttpRequestCreationParameters() { TimeAdjustment = TimeSpan.FromMinutes(-1) }, ExpectedClaim = SignedHttpRequestClaimTypes.Ts, - ExpectedClaimValue = (long)(timeNow - EpochTime.UnixEpoch).TotalSeconds - 60, - TestId = "ValidTsWithTimeAdjustmentMinus", + ExpectedClaimValue = (long)(timeNow - EpochTime.UnixEpoch).TotalSeconds - 60 }, - new CreateSignedHttpRequestTheoryData + new CreateSignedHttpRequestTheoryData("ValidTsWithTimeAdjustmentPlus") { SignedHttpRequestCreationParameters = new SignedHttpRequestCreationParameters() { TimeAdjustment = TimeSpan.FromMinutes(1) }, ExpectedClaim = SignedHttpRequestClaimTypes.Ts, - ExpectedClaimValue = (long)(timeNow - EpochTime.UnixEpoch).TotalSeconds + 60, - TestId = "ValidTsWithTimeAdjustmentPlus", - }, - new CreateSignedHttpRequestTheoryData - { - Payload = null, - ExpectedException = ExpectedException.ArgumentNullException(), - TestId = "NullPayload", - }, + ExpectedClaimValue = (long)(timeNow - EpochTime.UnixEpoch).TotalSeconds + 60 + } }; } } @@ -301,26 +271,22 @@ public static TheoryData CreateTsClaimTheoryD public void CreateMClaim(CreateSignedHttpRequestTheoryData theoryData) { var context = TestUtilities.WriteHeader($"{this}.CreateMClaim", theoryData); + Utf8JsonWriter writer = null; try { - var handler = new SignedHttpRequestHandlerPublic(); - var signedHttpRequestDescriptor = theoryData.BuildSignedHttpRequestDescriptor(); - - handler.AddMClaimPublic(theoryData.Payload, signedHttpRequestDescriptor); - var payload = JObject.FromObject(theoryData.Payload); - - if (!payload.ContainsKey(theoryData.ExpectedClaim)) - context.AddDiff($"Payload doesn't contain the claim '{theoryData.ExpectedClaim}'"); - - if (!IdentityComparer.AreStringsEqual(payload.Value(theoryData.ExpectedClaim), theoryData.ExpectedClaimValue, context)) - context.AddDiff($"Value of '{theoryData.ExpectedClaim}' claim is '{payload.Value(theoryData.ExpectedClaim)}', but expected value was '{theoryData.ExpectedClaimValue}'"); - + writer = theoryData.GetWriter(); + theoryData.Handler.AddMClaim(ref writer, theoryData.BuildSignedHttpRequestDescriptor()); + CheckClaimValue(ref writer, theoryData, context); theoryData.ExpectedException.ProcessNoException(context); } catch (Exception ex) { theoryData.ExpectedException.ProcessException(ex, context); } + finally + { + writer?.Dispose(); + } TestUtilities.AssertFailIfErrors(context); } @@ -331,32 +297,22 @@ public static TheoryData CreateMClaimTheoryDa { return new TheoryData { - new CreateSignedHttpRequestTheoryData + new CreateSignedHttpRequestTheoryData("ValidM") { - First = true, ExpectedClaim = SignedHttpRequestClaimTypes.M, - ExpectedClaimValue = "GET", - HttpRequestMethod = "GET", - TestId = "ValidM", + ExpectedClaimValue = $@"{{""m"":""GET""}}", + HttpRequestMethod = "GET" }, - new CreateSignedHttpRequestTheoryData + new CreateSignedHttpRequestTheoryData("InvalidLowercaseM") { - ExpectedClaimValue = "GET", + ExpectedClaimValue = $@"{{""m"":""GET""}}", HttpRequestMethod = "get", - ExpectedException = new ExpectedException(typeof(SignedHttpRequestCreationException), "IDX23002"), - TestId = "InvalidLowercaseM", - }, - new CreateSignedHttpRequestTheoryData - { - Payload = null, - ExpectedException = ExpectedException.ArgumentNullException(), - TestId = "NullPayload", + ExpectedException = new ExpectedException(typeof(SignedHttpRequestCreationException), "IDX23002") }, - new CreateSignedHttpRequestTheoryData + new CreateSignedHttpRequestTheoryData("EmptyM") { HttpRequestMethod = "", - ExpectedException = ExpectedException.ArgumentNullException(), - TestId = "EmptyM", + ExpectedException = ExpectedException.ArgumentNullException() }, }; } @@ -366,26 +322,22 @@ public static TheoryData CreateMClaimTheoryDa public void CreateUClaim(CreateSignedHttpRequestTheoryData theoryData) { var context = TestUtilities.WriteHeader($"{this}.CreateUClaim", theoryData); + Utf8JsonWriter writer = null; try { - var handler = new SignedHttpRequestHandlerPublic(); - var signedHttpRequestDescriptor = theoryData.BuildSignedHttpRequestDescriptor(); - - handler.AddUClaimPublic(theoryData.Payload, signedHttpRequestDescriptor); - var payload = JObject.FromObject(theoryData.Payload); - - if (!theoryData.Payload.ContainsKey(theoryData.ExpectedClaim)) - context.AddDiff($"Payload doesn't contain the claim '{theoryData.ExpectedClaim}'"); - - if (!IdentityComparer.AreStringsEqual(payload.Value(theoryData.ExpectedClaim), theoryData.ExpectedClaimValue, context)) - context.AddDiff($"Value of '{theoryData.ExpectedClaim}' claim is '{payload.Value(theoryData.ExpectedClaim)}', but expected value was '{theoryData.ExpectedClaimValue}'"); - + writer = theoryData.GetWriter(); + theoryData.Handler.AddUClaim(ref writer, theoryData.BuildSignedHttpRequestDescriptor()); + CheckClaimValue(ref writer, theoryData, context); theoryData.ExpectedException.ProcessNoException(context); } catch (Exception ex) { theoryData.ExpectedException.ProcessException(ex, context); } + finally + { + writer?.Dispose(); + } TestUtilities.AssertFailIfErrors(context); } @@ -396,54 +348,41 @@ public static TheoryData CreateUClaimTheoryDa { return new TheoryData { - new CreateSignedHttpRequestTheoryData + new CreateSignedHttpRequestTheoryData("ValidU1") { - First = true, ExpectedClaim = SignedHttpRequestClaimTypes.U, - ExpectedClaimValue = "www.contoso.com", - HttpRequestUri = new Uri("https://www.contoso.com/path1?queryParam1=value1"), - TestId = "ValidU1", + ExpectedClaimValue = $@"{{""u"":""www.contoso.com""}}", + HttpRequestUri = new Uri("https://www.contoso.com/path1?queryParam1=value1") }, - new CreateSignedHttpRequestTheoryData + new CreateSignedHttpRequestTheoryData("ValidU2") { ExpectedClaim = SignedHttpRequestClaimTypes.U, - ExpectedClaimValue = "www.contoso.com", - HttpRequestUri = new Uri("http://www.Contoso.com/"), - TestId = "ValidU2", + ExpectedClaimValue = $@"{{""u"":""www.contoso.com""}}", + HttpRequestUri = new Uri("http://www.Contoso.com/") }, - new CreateSignedHttpRequestTheoryData + new CreateSignedHttpRequestTheoryData("ValidU3") { ExpectedClaim = SignedHttpRequestClaimTypes.U, - ExpectedClaimValue = "www.contoso.com", - HttpRequestUri = new Uri("https://www.contoso.com:443"), - TestId = "ValidU3", + ExpectedClaimValue = $@"{{""u"":""www.contoso.com""}}", + HttpRequestUri = new Uri("https://www.contoso.com:443") }, - new CreateSignedHttpRequestTheoryData + new CreateSignedHttpRequestTheoryData("ValidU4") { ExpectedClaim = SignedHttpRequestClaimTypes.U, - ExpectedClaimValue = "www.contoso.com:81", - HttpRequestUri = new Uri("https://www.contoso.com:81"), - TestId = "ValidU4", + ExpectedClaimValue = $@"{{""u"":""www.contoso.com:81""}}", + HttpRequestUri = new Uri("https://www.contoso.com:81") }, - new CreateSignedHttpRequestTheoryData + new CreateSignedHttpRequestTheoryData("InvalidRelativeUri") { ExpectedClaim = SignedHttpRequestClaimTypes.U, HttpRequestUri = new Uri("/relativePath", UriKind.Relative), - ExpectedException = new ExpectedException(typeof(SignedHttpRequestCreationException), "IDX23001"), - TestId = "InvalidRelativeUri", + ExpectedException = new ExpectedException(typeof(SignedHttpRequestCreationException), "IDX23001") }, - new CreateSignedHttpRequestTheoryData - { - Payload = null, - ExpectedException = ExpectedException.ArgumentNullException(), - TestId = "NullPayload", - }, - new CreateSignedHttpRequestTheoryData + new CreateSignedHttpRequestTheoryData("NullUri") { HttpRequestUri = null, - ExpectedException = ExpectedException.ArgumentNullException(), - TestId = "NullUri", - }, + ExpectedException = ExpectedException.ArgumentNullException() + } }; } } @@ -452,26 +391,22 @@ public static TheoryData CreateUClaimTheoryDa public void CreatePClaim(CreateSignedHttpRequestTheoryData theoryData) { var context = TestUtilities.WriteHeader($"{this}.CreatePClaim", theoryData); + Utf8JsonWriter writer = null; try { - var handler = new SignedHttpRequestHandlerPublic(); - var signedHttpRequestDescriptor = theoryData.BuildSignedHttpRequestDescriptor(); - - handler.AddPClaimPublic(theoryData.Payload, signedHttpRequestDescriptor); - var payload = JObject.FromObject(theoryData.Payload); - - if (!payload.ContainsKey(theoryData.ExpectedClaim)) - context.AddDiff($"Payload doesn't contain the claim '{theoryData.ExpectedClaim}'"); - - if (!IdentityComparer.AreStringsEqual(payload.Value(theoryData.ExpectedClaim), theoryData.ExpectedClaimValue, context)) - context.AddDiff($"Value of '{theoryData.ExpectedClaim}' claim is '{payload.Value(theoryData.ExpectedClaim)}', but expected value was '{theoryData.ExpectedClaimValue}'"); - + writer = theoryData.GetWriter(); + theoryData.Handler.AddPClaim(ref writer, theoryData.BuildSignedHttpRequestDescriptor()); + CheckClaimValue(ref writer, theoryData, context); theoryData.ExpectedException.ProcessNoException(context); } catch (Exception ex) { theoryData.ExpectedException.ProcessException(ex, context); } + finally + { + writer?.Dispose(); + } TestUtilities.AssertFailIfErrors(context); } @@ -482,68 +417,53 @@ public static TheoryData CreatePClaimTheoryDa { return new TheoryData { - new CreateSignedHttpRequestTheoryData + new CreateSignedHttpRequestTheoryData("ValidP1") { - First = true, ExpectedClaim = SignedHttpRequestClaimTypes.P, - ExpectedClaimValue = "/path1", - HttpRequestUri = new Uri("https://www.contoso.com/path1?queryParam1=value1"), - TestId = "ValidP1", + ExpectedClaimValue = $@"{{""p"":""/path1""}}", + HttpRequestUri = new Uri("https://www.contoso.com/path1?queryParam1=value1") }, - new CreateSignedHttpRequestTheoryData + new CreateSignedHttpRequestTheoryData("ValidP2") { ExpectedClaim = SignedHttpRequestClaimTypes.P, - ExpectedClaimValue = "/path1/", - HttpRequestUri = new Uri("https://www.contoso.com/path1/"), - TestId = "ValidP2", + ExpectedClaimValue = $@"{{""p"":""/path1/""}}", + HttpRequestUri = new Uri("https://www.contoso.com/path1/") }, - new CreateSignedHttpRequestTheoryData + new CreateSignedHttpRequestTheoryData("ValidP3") { ExpectedClaim = SignedHttpRequestClaimTypes.P, - ExpectedClaimValue = "/path1", - HttpRequestUri = new Uri("https://www.contoso.com/path1"), - TestId = "ValidP3", + ExpectedClaimValue = $@"{{""p"":""/path1""}}", + HttpRequestUri = new Uri("https://www.contoso.com/path1") }, - new CreateSignedHttpRequestTheoryData + new CreateSignedHttpRequestTheoryData("ValidP4") { ExpectedClaim = SignedHttpRequestClaimTypes.P, - ExpectedClaimValue = "/path1", - HttpRequestUri = new Uri("http://www.contoso.com:81/path1"), - TestId = "ValidP4", + ExpectedClaimValue = $@"{{""p"":""/path1""}}", + HttpRequestUri = new Uri("http://www.contoso.com:81/path1") }, - new CreateSignedHttpRequestTheoryData + new CreateSignedHttpRequestTheoryData("ValidP5") { ExpectedClaim = SignedHttpRequestClaimTypes.P, - ExpectedClaimValue = "/pa%20th1", - HttpRequestUri = new Uri("http://www.contoso.com:81/pa th1"), - TestId = "ValidP5", + ExpectedClaimValue = $@"{{""p"":""/pa%20th1""}}", + HttpRequestUri = new Uri("http://www.contoso.com:81/pa th1") }, - new CreateSignedHttpRequestTheoryData + new CreateSignedHttpRequestTheoryData("NoPath") { ExpectedClaim = SignedHttpRequestClaimTypes.P, - ExpectedClaimValue = "/", - HttpRequestUri = new Uri("http://www.contoso.com"), - TestId = "NoPath", + ExpectedClaimValue = $@"{{""p"":""/""}}", + HttpRequestUri = new Uri("http://www.contoso.com") }, - new CreateSignedHttpRequestTheoryData + new CreateSignedHttpRequestTheoryData("ValidRelativeUri") { ExpectedClaim = SignedHttpRequestClaimTypes.P, - ExpectedClaimValue = "/relativePath", - HttpRequestUri = new Uri("/relativePath", UriKind.Relative), - TestId = "ValidRelativeUri", - }, - new CreateSignedHttpRequestTheoryData - { - Payload = null, - ExpectedException = ExpectedException.ArgumentNullException(), - TestId = "NullPayload", + ExpectedClaimValue = $@"{{""p"":""/relativePath""}}", + HttpRequestUri = new Uri("/relativePath", UriKind.Relative) }, - new CreateSignedHttpRequestTheoryData + new CreateSignedHttpRequestTheoryData("NullUri") { HttpRequestUri = null, - ExpectedException = ExpectedException.ArgumentNullException(), - TestId = "NullUri", - }, + ExpectedException = ExpectedException.ArgumentNullException() + } }; } } @@ -552,26 +472,22 @@ public static TheoryData CreatePClaimTheoryDa public void CreateQClaim(CreateSignedHttpRequestTheoryData theoryData) { var context = TestUtilities.WriteHeader($"{this}.CreateQClaim", theoryData); + Utf8JsonWriter writer = null; try { - var handler = new SignedHttpRequestHandlerPublic(); - var signedHttpRequestDescriptor = theoryData.BuildSignedHttpRequestDescriptor(); - - handler.AddQClaimPublic(theoryData.Payload, signedHttpRequestDescriptor); - var payload = JObject.FromObject(theoryData.Payload); - - if (!payload.ContainsKey(theoryData.ExpectedClaim)) - context.AddDiff($"Payload doesn't contain the claim '{theoryData.ExpectedClaim}'"); - - if (!IdentityComparer.AreStringsEqual(payload.Value(theoryData.ExpectedClaim).ToString(Formatting.None), theoryData.ExpectedClaimValue, context)) - context.AddDiff($"Value of '{theoryData.ExpectedClaim}' claim is '{payload.Value(theoryData.ExpectedClaim).ToString(Formatting.None)}', but expected value was '{theoryData.ExpectedClaimValue}'"); - + writer = theoryData.GetWriter(); + theoryData.Handler.AddQClaim(ref writer, theoryData.BuildSignedHttpRequestDescriptor()); + CheckClaimValue(ref writer, theoryData, context); theoryData.ExpectedException.ProcessNoException(context); } catch (Exception ex) { theoryData.ExpectedException.ProcessException(ex, context); } + finally + { + writer?.Dispose(); + } TestUtilities.AssertFailIfErrors(context); } @@ -582,140 +498,109 @@ public static TheoryData CreateQClaimTheoryDa { return new TheoryData { - new CreateSignedHttpRequestTheoryData + new CreateSignedHttpRequestTheoryData("ValidQ1") { - First = true, ExpectedClaim = SignedHttpRequestClaimTypes.Q, - ExpectedClaimValue = $"[[\"queryParam1\"],\"{SignedHttpRequestTestUtils.CalculateBase64UrlEncodedHash("queryParam1=value1")}\"]", - HttpRequestUri = new Uri("https://www.contoso.com/path1?queryParam1=value1"), - TestId = "ValidQ1", + ExpectedClaimValue = $"{{\"q\":[[\"queryParam1\"],\"{SignedHttpRequestTestUtils.CalculateBase64UrlEncodedHash("queryParam1=value1")}\"]}}", + HttpRequestUri = new Uri("https://www.contoso.com/path1?queryParam1=value1") }, - new CreateSignedHttpRequestTheoryData + new CreateSignedHttpRequestTheoryData("ValidQ2") { ExpectedClaim = SignedHttpRequestClaimTypes.Q, - ExpectedClaimValue = $"[[\"queryParam1\",\"queryParam2\"],\"{SignedHttpRequestTestUtils.CalculateBase64UrlEncodedHash("queryParam1=value1&queryParam2=value2")}\"]", + ExpectedClaimValue = $"{{\"q\":[[\"queryParam1\",\"queryParam2\"],\"{SignedHttpRequestTestUtils.CalculateBase64UrlEncodedHash("queryParam1=value1&queryParam2=value2")}\"]}}", HttpRequestUri = new Uri("https://www.contoso.com/path1?queryParam1=value1&queryParam2=value2"), - TestId = "ValidQ2", + }, - new CreateSignedHttpRequestTheoryData + new CreateSignedHttpRequestTheoryData("ValidQ3") { ExpectedClaim = SignedHttpRequestClaimTypes.Q, - ExpectedClaimValue = $"[[\"queryParam1\",\"queryParam2\"],\"{SignedHttpRequestTestUtils.CalculateBase64UrlEncodedHash("queryParam1=value1&queryParam2=value2")}\"]", + ExpectedClaimValue = $"{{\"q\":[[\"queryParam1\",\"queryParam2\"],\"{SignedHttpRequestTestUtils.CalculateBase64UrlEncodedHash("queryParam1=value1&queryParam2=value2")}\"]}}", HttpRequestUri = new Uri("https://www.contoso.com/path1?&queryParam1=value1&queryParam2=value2"), - TestId = "ValidQ3", + }, - new CreateSignedHttpRequestTheoryData + new CreateSignedHttpRequestTheoryData("ValidQ4") { ExpectedClaim = SignedHttpRequestClaimTypes.Q, - ExpectedClaimValue = $"[[\"query%20Param1\"],\"{SignedHttpRequestTestUtils.CalculateBase64UrlEncodedHash("query%20Param1=value1")}\"]", - HttpRequestUri = new Uri("https://www.contoso.com/path1?query Param1=value1"), - TestId = "ValidQ4", + ExpectedClaimValue = $"{{\"q\":[[\"query%20Param1\"],\"{SignedHttpRequestTestUtils.CalculateBase64UrlEncodedHash("query%20Param1=value1")}\"]}}", + HttpRequestUri = new Uri("https://www.contoso.com/path1?query Param1=value1") }, - new CreateSignedHttpRequestTheoryData + new CreateSignedHttpRequestTheoryData("ValidQ5") { ExpectedClaim = SignedHttpRequestClaimTypes.Q, - ExpectedClaimValue = $"[[\"query%20Param1%20\"],\"{SignedHttpRequestTestUtils.CalculateBase64UrlEncodedHash("query%20Param1%20=value1%20")}\"]", - HttpRequestUri = new Uri("https://www.contoso.com/path1?query Param1 =value1%20"), - TestId = "ValidQ5", + ExpectedClaimValue = $"{{\"q\":[[\"query%20Param1%20\"],\"{SignedHttpRequestTestUtils.CalculateBase64UrlEncodedHash("query%20Param1%20=value1%20")}\"]}}", + HttpRequestUri = new Uri("https://www.contoso.com/path1?query Param1 =value1%20") }, - new CreateSignedHttpRequestTheoryData + new CreateSignedHttpRequestTheoryData("ValidQ6") { ExpectedClaim = SignedHttpRequestClaimTypes.Q, - ExpectedClaimValue = $"[[\"queryParam1\"],\"{SignedHttpRequestTestUtils.CalculateBase64UrlEncodedHash("queryParam1=value1")}\"]", - HttpRequestUri = new Uri("https://www.contoso.com/path1?&queryParam1=value1&query=Param2=value2"), - TestId = "ValidQ6", + ExpectedClaimValue = $"{{\"q\":[[\"queryParam1\"],\"{SignedHttpRequestTestUtils.CalculateBase64UrlEncodedHash("queryParam1=value1")}\"]}}", + HttpRequestUri = new Uri("https://www.contoso.com/path1?&queryParam1=value1&query=Param2=value2") }, - new CreateSignedHttpRequestTheoryData + new CreateSignedHttpRequestTheoryData("ValidNoQueryParams1") { ExpectedClaim = SignedHttpRequestClaimTypes.Q, - ExpectedClaimValue = $"[[],\"{SignedHttpRequestTestUtils.CalculateBase64UrlEncodedHash("")}\"]", - HttpRequestUri = new Uri("https://www.contoso.com/path1"), - TestId = "ValidNoQueryParams1", + ExpectedClaimValue = $"{{\"q\":[[],\"{SignedHttpRequestTestUtils.CalculateBase64UrlEncodedHash("")}\"]}}", + HttpRequestUri = new Uri("https://www.contoso.com/path1") }, - new CreateSignedHttpRequestTheoryData + new CreateSignedHttpRequestTheoryData("ValidNoQueryParams2") { ExpectedClaim = SignedHttpRequestClaimTypes.Q, - ExpectedClaimValue = $"[[],\"{SignedHttpRequestTestUtils.CalculateBase64UrlEncodedHash("")}\"]", - HttpRequestUri = new Uri("https://www.contoso.com/path1&"), - TestId = "ValidNoQueryParams2", + ExpectedClaimValue = $"{{\"q\":[[],\"{SignedHttpRequestTestUtils.CalculateBase64UrlEncodedHash("")}\"]}}", + HttpRequestUri = new Uri("https://www.contoso.com/path1&") }, - new CreateSignedHttpRequestTheoryData + new CreateSignedHttpRequestTheoryData("ValidNoQueryParams3") { ExpectedClaim = SignedHttpRequestClaimTypes.Q, - ExpectedClaimValue = $"[[],\"{SignedHttpRequestTestUtils.CalculateBase64UrlEncodedHash("")}\"]", - HttpRequestUri = new Uri("https://www.contoso.com/path1&t"), - TestId = "ValidNoQueryParams3", + ExpectedClaimValue = $"{{\"q\":[[],\"{SignedHttpRequestTestUtils.CalculateBase64UrlEncodedHash("")}\"]}}", + HttpRequestUri = new Uri("https://www.contoso.com/path1&t") }, - new CreateSignedHttpRequestTheoryData + new CreateSignedHttpRequestTheoryData("ValidNoQueryParams4") { ExpectedClaim = SignedHttpRequestClaimTypes.Q, - ExpectedClaimValue = $"[[],\"{SignedHttpRequestTestUtils.CalculateBase64UrlEncodedHash("")}\"]", - HttpRequestUri = new Uri("https://www.contoso.com/path1&t="), - TestId = "ValidNoQueryParams4", + ExpectedClaimValue = $"{{\"q\":[[],\"{SignedHttpRequestTestUtils.CalculateBase64UrlEncodedHash("")}\"]}}", + HttpRequestUri = new Uri("https://www.contoso.com/path1&t=") }, - new CreateSignedHttpRequestTheoryData + new CreateSignedHttpRequestTheoryData("ValidRepeatedQ1") { ExpectedClaim = SignedHttpRequestClaimTypes.Q, - ExpectedClaimValue = $"[[\"queryParam1\"],\"{SignedHttpRequestTestUtils.CalculateBase64UrlEncodedHash("queryParam1=value1")}\"]", - HttpRequestUri = new Uri("https://www.contoso.com/path1?queryParam1=value1&repeated=repeated1&repeated=repeate2"), - TestId = "ValidRepeatedQ1", + ExpectedClaimValue = $"{{\"q\":[[\"queryParam1\"],\"{SignedHttpRequestTestUtils.CalculateBase64UrlEncodedHash("queryParam1=value1")}\"]}}", + HttpRequestUri = new Uri("https://www.contoso.com/path1?queryParam1=value1&repeated=repeated1&repeated=repeate2") }, - new CreateSignedHttpRequestTheoryData + new CreateSignedHttpRequestTheoryData("ValidRepeatedQ2") { ExpectedClaim = SignedHttpRequestClaimTypes.Q, - ExpectedClaimValue = $"[[\"queryParam1\"],\"{SignedHttpRequestTestUtils.CalculateBase64UrlEncodedHash("queryParam1=value1")}\"]", - HttpRequestUri = new Uri("https://www.contoso.com/path1?&repeated=repeated1&queryParam1=value1&repeated=repeate2"), - TestId = "ValidRepeatedQ2", + ExpectedClaimValue = $"{{\"q\":[[\"queryParam1\"],\"{SignedHttpRequestTestUtils.CalculateBase64UrlEncodedHash("queryParam1=value1")}\"]}}", + HttpRequestUri = new Uri("https://www.contoso.com/path1?&repeated=repeated1&queryParam1=value1&repeated=repeate2") }, - new CreateSignedHttpRequestTheoryData + new CreateSignedHttpRequestTheoryData("ValidRepeatedQ3") { ExpectedClaim = SignedHttpRequestClaimTypes.Q, - ExpectedClaimValue = $"[[\"queryParam1\"],\"{SignedHttpRequestTestUtils.CalculateBase64UrlEncodedHash("queryParam1=value1")}\"]", - HttpRequestUri = new Uri("https://www.contoso.com/path1?&repeated=repeated1&repeated=repeate2&queryParam1=value1"), - TestId = "ValidRepeatedQ3", + ExpectedClaimValue = $"{{\"q\":[[\"queryParam1\"],\"{SignedHttpRequestTestUtils.CalculateBase64UrlEncodedHash("queryParam1=value1")}\"]}}", + HttpRequestUri = new Uri("https://www.contoso.com/path1?&repeated=repeated1&repeated=repeate2&queryParam1=value1") }, - new CreateSignedHttpRequestTheoryData + new CreateSignedHttpRequestTheoryData("ValidRepeatedQ4") { ExpectedClaim = SignedHttpRequestClaimTypes.Q, - ExpectedClaimValue = $"[[\"queryParam1\"],\"{SignedHttpRequestTestUtils.CalculateBase64UrlEncodedHash("queryParam1=value1")}\"]", - HttpRequestUri = new Uri("https://www.contoso.com/path1?&repeated=repeated1&repeated=repeate2&queryParam1=value1&repeated=repeate3"), - TestId = "ValidRepeatedQ4", + ExpectedClaimValue = $"{{\"q\":[[\"queryParam1\"],\"{SignedHttpRequestTestUtils.CalculateBase64UrlEncodedHash("queryParam1=value1")}\"]}}", + HttpRequestUri = new Uri("https://www.contoso.com/path1?&repeated=repeated1&repeated=repeate2&queryParam1=value1&repeated=repeate3") }, - new CreateSignedHttpRequestTheoryData + new CreateSignedHttpRequestTheoryData("RepeatedQEmpty") { ExpectedClaim = SignedHttpRequestClaimTypes.Q, - ExpectedClaimValue = $"[[],\"{SignedHttpRequestTestUtils.CalculateBase64UrlEncodedHash("")}\"]", - HttpRequestUri = new Uri("https://www.contoso.com/path1?&repeated=repeated1&repeated=repeate2"), - TestId = "RepeatedQEmpty", + ExpectedClaimValue = $"{{\"q\":[[],\"{SignedHttpRequestTestUtils.CalculateBase64UrlEncodedHash("")}\"]}}", + HttpRequestUri = new Uri("https://www.contoso.com/path1?&repeated=repeated1&repeated=repeate2") }, - new CreateSignedHttpRequestTheoryData + new CreateSignedHttpRequestTheoryData("ValidRelativeUri") { ExpectedClaim = SignedHttpRequestClaimTypes.Q, - ExpectedClaimValue = $"[[\"queryParam1\"],\"{SignedHttpRequestTestUtils.CalculateBase64UrlEncodedHash("queryParam1=value1")}\"]", - HttpRequestUri = new Uri("/relativePath?queryParam1=value1", UriKind.Relative), - TestId = "ValidRelativeUri", + ExpectedClaimValue = $"{{\"q\":[[\"queryParam1\"],\"{SignedHttpRequestTestUtils.CalculateBase64UrlEncodedHash("queryParam1=value1")}\"]}}", + HttpRequestUri = new Uri("/relativePath?queryParam1=value1", UriKind.Relative) }, - new CreateSignedHttpRequestTheoryData - { - Payload = null, - ExpectedException = ExpectedException.ArgumentNullException(), - TestId = "NullPayload", - }, - new CreateSignedHttpRequestTheoryData + new CreateSignedHttpRequestTheoryData("NullUri") { HttpRequestUri = null, - ExpectedException = ExpectedException.ArgumentNullException(), - TestId = "NullUri", - }, - new CreateSignedHttpRequestTheoryData - { - ExpectedClaim = SignedHttpRequestClaimTypes.Q, - ExpectedClaimValue = $"[[\"queryParam1\"],\"{SignedHttpRequestTestUtils.CalculateBase64UrlEncodedHash("queryParam1=value1")}\"]", - HttpRequestUri = new Uri("https://www.contoso.com/path1?queryParam1=value1"), - Payload = new Dictionary() { {SignedHttpRequestClaimTypes.Q, null } }, - ExpectedException = new ExpectedException(typeof(SignedHttpRequestCreationException), "IDX23008: Exception caught while creating the 'q' claim.", typeof(ArgumentException)), - TestId = "PayloadAlreadyHasQClaim" - }, + ExpectedException = ExpectedException.ArgumentNullException() + } }; } } @@ -724,26 +609,22 @@ public static TheoryData CreateQClaimTheoryDa public void CreateHClaim(CreateSignedHttpRequestTheoryData theoryData) { var context = TestUtilities.WriteHeader($"{this}.CreateHClaim", theoryData); + Utf8JsonWriter writer = null; try { - var handler = new SignedHttpRequestHandlerPublic(); - var signedHttpRequestDescriptor = theoryData.BuildSignedHttpRequestDescriptor(); - - handler.AddHClaimPublic(theoryData.Payload, signedHttpRequestDescriptor); - var payload = JObject.FromObject(theoryData.Payload); - - if (!payload.ContainsKey(theoryData.ExpectedClaim)) - context.AddDiff($"Payload doesn't contain the claim '{theoryData.ExpectedClaim}'"); - - if (!IdentityComparer.AreStringsEqual(payload.Value(theoryData.ExpectedClaim).ToString(Formatting.None), theoryData.ExpectedClaimValue, context)) - context.AddDiff($"Value of '{theoryData.ExpectedClaim}' claim is '{payload.Value(theoryData.ExpectedClaim).ToString(Formatting.None)}', but expected value was '{theoryData.ExpectedClaimValue}'"); - + writer = theoryData.GetWriter(); + theoryData.Handler.AddHClaim(ref writer, theoryData.BuildSignedHttpRequestDescriptor()); + CheckClaimValue(ref writer, theoryData, context); theoryData.ExpectedException.ProcessNoException(context); } catch (Exception ex) { theoryData.ExpectedException.ProcessException(ex, context); } + finally + { + writer?.Dispose(); + } TestUtilities.AssertFailIfErrors(context); } @@ -754,183 +635,149 @@ public static TheoryData CreateHClaimTheoryDa { return new TheoryData { - new CreateSignedHttpRequestTheoryData + new CreateSignedHttpRequestTheoryData("ValidH1") { - First = true, ExpectedClaim = SignedHttpRequestClaimTypes.H, - ExpectedClaimValue = $"[[\"headername1\"],\"{SignedHttpRequestTestUtils.CalculateBase64UrlEncodedHash("headername1: headerValue1")}\"]", + ExpectedClaimValue = $"{{\"h\":[[\"headername1\"],\"{SignedHttpRequestTestUtils.CalculateBase64UrlEncodedHash("headername1: headerValue1")}\"]}}", HttpRequestHeaders = new Dictionary>() { { "headerName1" , new List { "headerValue1" } } - }, - TestId = "ValidH1", + } }, - new CreateSignedHttpRequestTheoryData + new CreateSignedHttpRequestTheoryData("ValidH2") { ExpectedClaim = SignedHttpRequestClaimTypes.H, - ExpectedClaimValue = $"[[\"headername1\",\"headername2\"],\"{SignedHttpRequestTestUtils.CalculateBase64UrlEncodedHash("headername1: headerValue1\nheadername2: headerValue2")}\"]", + ExpectedClaimValue = $"{{\"h\":[[\"headername1\",\"headername2\"],\"{SignedHttpRequestTestUtils.CalculateBase64UrlEncodedHash("headername1: headerValue1\nheadername2: headerValue2")}\"]}}", HttpRequestHeaders = new Dictionary>() { { "headerName1" , new List { "headerValue1" } }, { "headerName2" , new List { "headerValue2" } }, - }, - TestId = "ValidH2", + } }, - new CreateSignedHttpRequestTheoryData + new CreateSignedHttpRequestTheoryData("ValidH3") { ExpectedClaim = SignedHttpRequestClaimTypes.H, - ExpectedClaimValue = $"[[\"headername1\",\"headername2\",\"headername3\"],\"{SignedHttpRequestTestUtils.CalculateBase64UrlEncodedHash("headername1: headerValue1\nheadername2: headerValue2\nheadername3: headerValue3")}\"]", + ExpectedClaimValue = $"{{\"h\":[[\"headername1\",\"headername2\",\"headername3\"],\"{SignedHttpRequestTestUtils.CalculateBase64UrlEncodedHash("headername1: headerValue1\nheadername2: headerValue2\nheadername3: headerValue3")}\"]}}", HttpRequestHeaders = new Dictionary>() { { "headerName1" , new List { "headerValue1" } }, { "headerName2" , new List { "headerValue2" } }, { "headerName3" , new List { "headerValue3" } }, - }, - TestId = "ValidH3", + } }, - new CreateSignedHttpRequestTheoryData + new CreateSignedHttpRequestTheoryData("ValidH4") { ExpectedClaim = SignedHttpRequestClaimTypes.H, - ExpectedClaimValue = $"[[\"header name1\"],\"{SignedHttpRequestTestUtils.CalculateBase64UrlEncodedHash("header name1: headerValue1")}\"]", + ExpectedClaimValue = $"{{\"h\":[[\"header name1\"],\"{SignedHttpRequestTestUtils.CalculateBase64UrlEncodedHash("header name1: headerValue1")}\"]}}", HttpRequestHeaders = new Dictionary>() { { "header Name1" , new List { "headerValue1" } } - }, - TestId = "ValidH4", + } }, - new CreateSignedHttpRequestTheoryData + new CreateSignedHttpRequestTheoryData("ValidH5") { ExpectedClaim = SignedHttpRequestClaimTypes.H, - ExpectedClaimValue = $"[[],\"{SignedHttpRequestTestUtils.CalculateBase64UrlEncodedHash("")}\"]", + ExpectedClaimValue = $"{{\"h\":[[],\"{SignedHttpRequestTestUtils.CalculateBase64UrlEncodedHash("")}\"]}}", HttpRequestHeaders = new Dictionary>() { { "" , new List { "headerValue1" } } - }, - TestId = "ValidH5", + } }, - new CreateSignedHttpRequestTheoryData + new CreateSignedHttpRequestTheoryData("ValidH6") { ExpectedClaim = SignedHttpRequestClaimTypes.H, - ExpectedClaimValue = $"[[],\"{SignedHttpRequestTestUtils.CalculateBase64UrlEncodedHash("")}\"]", + ExpectedClaimValue = $"{{\"h\":[[],\"{SignedHttpRequestTestUtils.CalculateBase64UrlEncodedHash("")}\"]}}", HttpRequestHeaders = new Dictionary>() { { "h1" , new List { "" } } - }, - TestId = "ValidH6", + } }, - new CreateSignedHttpRequestTheoryData + new CreateSignedHttpRequestTheoryData("ValidH7") { ExpectedClaim = SignedHttpRequestClaimTypes.H, - ExpectedClaimValue = $"[[\"headername1\"],\"{SignedHttpRequestTestUtils.CalculateBase64UrlEncodedHash("headername1: headerValue1")}\"]", + ExpectedClaimValue = $"{{\"h\":[[\"headername1\"],\"{SignedHttpRequestTestUtils.CalculateBase64UrlEncodedHash("headername1: headerValue1")}\"]}}", HttpRequestHeaders = new Dictionary>() { { "headerName1" , new List { "headerValue1" } }, { SignedHttpRequestConstants.AuthorizationHeader , new List { "exyxz..." } }, - }, - TestId = "ValidH7", + } }, - new CreateSignedHttpRequestTheoryData + new CreateSignedHttpRequestTheoryData("NoHeaders") { ExpectedClaim = SignedHttpRequestClaimTypes.H, - ExpectedClaimValue = $"[[\"headername1\"],\"{SignedHttpRequestTestUtils.CalculateBase64UrlEncodedHash("headername1: headerValue1")}\"]", + ExpectedClaimValue = $"{{\"h\":[[\"headername1\"],\"{SignedHttpRequestTestUtils.CalculateBase64UrlEncodedHash("headername1: headerValue1")}\"]}}", HttpRequestHeaders = new Dictionary>() { { "headerName1" , new List { "headerValue1" } } - }, - TestId = "NoHeaders", + } }, - new CreateSignedHttpRequestTheoryData + new CreateSignedHttpRequestTheoryData("ValidRepeatedH1") { ExpectedClaim = SignedHttpRequestClaimTypes.H, - ExpectedClaimValue = $"[[\"headername1\"],\"{SignedHttpRequestTestUtils.CalculateBase64UrlEncodedHash("headername1: headerValue1")}\"]", + ExpectedClaimValue = $"{{\"h\":[[\"headername1\"],\"{SignedHttpRequestTestUtils.CalculateBase64UrlEncodedHash("headername1: headerValue1")}\"]}}", HttpRequestHeaders = new Dictionary>() { { "headerName1" , new List { "headerValue1" } }, { "headerName2" , new List { "headerValue2", "headerValue10" } }, - }, - TestId = "ValidRepeatedH1", + } }, - new CreateSignedHttpRequestTheoryData + new CreateSignedHttpRequestTheoryData("ValidRepeatedH2") { ExpectedClaim = SignedHttpRequestClaimTypes.H, - ExpectedClaimValue = $"[[\"headername1\"],\"{SignedHttpRequestTestUtils.CalculateBase64UrlEncodedHash("headername1: headerValue1")}\"]", + ExpectedClaimValue = $"{{\"h\":[[\"headername1\"],\"{SignedHttpRequestTestUtils.CalculateBase64UrlEncodedHash("headername1: headerValue1")}\"]}}", HttpRequestHeaders = new Dictionary>() { { "headerName2" , new List { "headerValue2", "headerValue10" } }, { "headerName1" , new List { "headerValue1" } }, - }, - TestId = "ValidRepeatedH2", + } }, - new CreateSignedHttpRequestTheoryData + new CreateSignedHttpRequestTheoryData("ValidRepeatedH3") { ExpectedClaim = SignedHttpRequestClaimTypes.H, - ExpectedClaimValue = $"[[],\"{SignedHttpRequestTestUtils.CalculateBase64UrlEncodedHash("")}\"]", + ExpectedClaimValue = $"{{\"h\":[[],\"{SignedHttpRequestTestUtils.CalculateBase64UrlEncodedHash("")}\"]}}", HttpRequestHeaders = new Dictionary>() { { "headerName2" , new List { "headerValue2", "headerValue10" } }, - }, - TestId = "ValidRepeatedH3", + } }, - new CreateSignedHttpRequestTheoryData + new CreateSignedHttpRequestTheoryData("ValidRepeatedH4") { ExpectedClaim = SignedHttpRequestClaimTypes.H, - ExpectedClaimValue = $"[[\"headername1\"],\"{SignedHttpRequestTestUtils.CalculateBase64UrlEncodedHash("headername1: headerValue1")}\"]", + ExpectedClaimValue = $"{{\"h\":[[\"headername1\"],\"{SignedHttpRequestTestUtils.CalculateBase64UrlEncodedHash("headername1: headerValue1")}\"]}}", HttpRequestHeaders = new Dictionary>() { { "HeaDerName2" , new List { "headerValue2" } }, { "headername2" , new List { "headerValue10" } }, { "headerName1" , new List { "headerValue1" } }, - }, - TestId = "ValidRepeatedH4", + } }, - new CreateSignedHttpRequestTheoryData + new CreateSignedHttpRequestTheoryData("ValidRepeatedH5") { ExpectedClaim = SignedHttpRequestClaimTypes.H, - ExpectedClaimValue = $"[[\"headername1\"],\"{SignedHttpRequestTestUtils.CalculateBase64UrlEncodedHash("headername1: headerValue1")}\"]", + ExpectedClaimValue = $"{{\"h\":[[\"headername1\"],\"{SignedHttpRequestTestUtils.CalculateBase64UrlEncodedHash("headername1: headerValue1")}\"]}}", HttpRequestHeaders = new Dictionary>() { { "HeaDerName2" , new List { "headerValue2" } }, { "headername2" , new List { "headerValue10" } }, { "headerName1" , new List { "headerValue1" } }, { "HEADERNAME2" , new List { "headerValue22" } }, - }, - TestId = "ValidRepeatedH5", + } }, - new CreateSignedHttpRequestTheoryData + new CreateSignedHttpRequestTheoryData("ValidRepeatedH6") { ExpectedClaim = SignedHttpRequestClaimTypes.H, - ExpectedClaimValue = $"[[],\"{SignedHttpRequestTestUtils.CalculateBase64UrlEncodedHash("")}\"]", + ExpectedClaimValue = $"{{\"h\":[[],\"{SignedHttpRequestTestUtils.CalculateBase64UrlEncodedHash("")}\"]}}", HttpRequestHeaders = new Dictionary>() { { "headerName1" , new List { "headerValue1", "headerValue10" } }, - }, - TestId = "ValidRepeatedH6", + } }, - new CreateSignedHttpRequestTheoryData + new CreateSignedHttpRequestTheoryData("EmptyHeaders") { ExpectedClaim = SignedHttpRequestClaimTypes.H, - ExpectedClaimValue = $"[[],\"{SignedHttpRequestTestUtils.CalculateBase64UrlEncodedHash("")}\"]", - HttpRequestHeaders = new Dictionary>(), - TestId = "EmptyHeaders", - }, - new CreateSignedHttpRequestTheoryData - { - Payload = null, - ExpectedException = ExpectedException.ArgumentNullException(), - TestId = "NullPayload", - }, - new CreateSignedHttpRequestTheoryData - { - ExpectedClaim = SignedHttpRequestClaimTypes.H, - ExpectedClaimValue = $"[[\"headername1\"],\"{SignedHttpRequestTestUtils.CalculateBase64UrlEncodedHash("headername1: headerValue1")}\"]", + ExpectedClaimValue = $"{{\"h\":[[],\"{SignedHttpRequestTestUtils.CalculateBase64UrlEncodedHash("")}\"]}}", HttpRequestHeaders = new Dictionary>() - { - { "headerName1" , new List { "headerValue1" } } - }, - Payload = new Dictionary() { {SignedHttpRequestClaimTypes.H, null } }, - ExpectedException = new ExpectedException(typeof(SignedHttpRequestCreationException), "IDX23008: Exception caught while creating the 'h' claim.", typeof(ArgumentException)), - TestId = "PayloadAlreadyHasHClaim" - }, + } }; } } @@ -939,26 +786,22 @@ public static TheoryData CreateHClaimTheoryDa public void CreateBClaim(CreateSignedHttpRequestTheoryData theoryData) { var context = TestUtilities.WriteHeader($"{this}.CreateBClaim", theoryData); + Utf8JsonWriter writer = null; try { - var handler = new SignedHttpRequestHandlerPublic(); - var signedHttpRequestDescriptor = theoryData.BuildSignedHttpRequestDescriptor(); - - handler.AddBClaimPublic(theoryData.Payload, signedHttpRequestDescriptor); - var payload = JObject.FromObject(theoryData.Payload); - - if (!payload.ContainsKey(theoryData.ExpectedClaim)) - context.AddDiff($"Payload doesn't contain the claim '{theoryData.ExpectedClaim}'"); - - if (!IdentityComparer.AreStringsEqual(payload.Value(theoryData.ExpectedClaim), theoryData.ExpectedClaimValue, context)) - context.AddDiff($"Value of '{theoryData.ExpectedClaim}' claim is '{payload.Value(theoryData.ExpectedClaim)}', but expected value was '{theoryData.ExpectedClaimValue}'"); - + writer = theoryData.GetWriter(); + theoryData.Handler.AddBClaim(ref writer, theoryData.BuildSignedHttpRequestDescriptor()); + CheckClaimValue(ref writer, theoryData, context); theoryData.ExpectedException.ProcessNoException(context); } catch (Exception ex) { theoryData.ExpectedException.ProcessException(ex, context); } + finally + { + writer?.Dispose(); + } TestUtilities.AssertFailIfErrors(context); } @@ -969,43 +812,24 @@ public static TheoryData CreateBClaimTheoryDa { return new TheoryData { - new CreateSignedHttpRequestTheoryData - { - First = true, - ExpectedClaim = SignedHttpRequestClaimTypes.B, - ExpectedClaimValue = SignedHttpRequestTestUtils.CalculateBase64UrlEncodedHash(Encoding.UTF8.GetBytes("abcd")), - HttpRequestBody = Encoding.UTF8.GetBytes("abcd"), - TestId = "ValidB1", - }, - new CreateSignedHttpRequestTheoryData + new CreateSignedHttpRequestTheoryData("ValidB1") { ExpectedClaim = SignedHttpRequestClaimTypes.B, - ExpectedClaimValue = SignedHttpRequestTestUtils.CalculateBase64UrlEncodedHash(Encoding.UTF8.GetBytes("")), - HttpRequestBody = new byte[0], - TestId = "ValidB2", + ExpectedClaimValue = $"{{\"b\":\"{SignedHttpRequestTestUtils.CalculateBase64UrlEncodedHash(Encoding.UTF8.GetBytes("abcd"))}\"}}", + HttpRequestBody = Encoding.UTF8.GetBytes("abcd") }, - new CreateSignedHttpRequestTheoryData + new CreateSignedHttpRequestTheoryData("ValidB2") { ExpectedClaim = SignedHttpRequestClaimTypes.B, - ExpectedClaimValue = SignedHttpRequestTestUtils.CalculateBase64UrlEncodedHash(Encoding.UTF8.GetBytes("")), - HttpRequestBody = null, - TestId = "NullBytes", + ExpectedClaimValue = $"{{\"b\":\"{SignedHttpRequestTestUtils.CalculateBase64UrlEncodedHash(Encoding.UTF8.GetBytes(""))}\"}}", + HttpRequestBody = new byte[0] }, - new CreateSignedHttpRequestTheoryData - { - Payload = null, - ExpectedException = ExpectedException.ArgumentNullException(), - TestId = "NullPayload", - }, - new CreateSignedHttpRequestTheoryData + new CreateSignedHttpRequestTheoryData("NullBytes") { ExpectedClaim = SignedHttpRequestClaimTypes.B, - ExpectedClaimValue = SignedHttpRequestTestUtils.CalculateBase64UrlEncodedHash(Encoding.UTF8.GetBytes("abcd")), - HttpRequestBody = Encoding.UTF8.GetBytes("abcd"), - Payload = new Dictionary() { {SignedHttpRequestClaimTypes.B, null } }, - ExpectedException = new ExpectedException(typeof(SignedHttpRequestCreationException), "IDX23008: Exception caught while creating the 'b' claim.", typeof(ArgumentException)), - TestId = "PayloadAlreadyHasBClaim" - }, + ExpectedClaimValue = $"{{\"b\":\"{SignedHttpRequestTestUtils.CalculateBase64UrlEncodedHash(Encoding.UTF8.GetBytes(""))}\"}}", + HttpRequestBody = null + } }; } } @@ -1014,26 +838,22 @@ public static TheoryData CreateBClaimTheoryDa public void CreateCnfClaim(CreateSignedHttpRequestTheoryData theoryData) { var context = TestUtilities.WriteHeader($"{this}.CreateCnfClaim", theoryData); + Utf8JsonWriter writer = null; try { - var handler = new SignedHttpRequestHandlerPublic(); - var signedHttpRequestDescriptor = theoryData.BuildSignedHttpRequestDescriptor(); - - handler.AddCnfClaimPublic(theoryData.Payload, signedHttpRequestDescriptor); - var payload = JObject.FromObject(theoryData.Payload); - - if (!payload.ContainsKey(theoryData.ExpectedClaim)) - context.AddDiff($"Payload doesn't contain the claim '{theoryData.ExpectedClaim}'"); - - if (!IdentityComparer.AreStringsEqual(payload[theoryData.ExpectedClaim].ToString(Formatting.None), theoryData.ExpectedClaimValue, context)) - context.AddDiff($"Value of '{theoryData.ExpectedClaim}' claim is '{payload[theoryData.ExpectedClaim].ToString(Formatting.None)}', but expected value was '{theoryData.ExpectedClaimValue}'"); - + writer = theoryData.GetWriter(); + theoryData.Handler.AddCnfClaim(ref writer, theoryData.BuildSignedHttpRequestDescriptor()); + CheckClaimValue(ref writer, theoryData, context); theoryData.ExpectedException.ProcessNoException(context); } catch (Exception ex) { theoryData.ExpectedException.ProcessException(ex, context); } + finally + { + writer?.Dispose(); + } TestUtilities.AssertFailIfErrors(context); } @@ -1046,88 +866,63 @@ public static TheoryData CreateCnfClaimTheory var rsaJwkFromX509Key = JsonWebKeyConverter.ConvertFromX509SecurityKey(KeyingMaterial.X509SecurityKey1, true); return new TheoryData { - new CreateSignedHttpRequestTheoryData - { - First = true, - ExpectedClaim = ConfirmationClaimTypes.Cnf, - Payload = null, - ExpectedException = ExpectedException.ArgumentNullException(), - TestId = "InvalidNullPayload", - }, - new CreateSignedHttpRequestTheoryData + new CreateSignedHttpRequestTheoryData("ValidManualCnfClaim") { ExpectedClaim = ConfirmationClaimTypes.Cnf, Cnf = testCnf, - ExpectedClaimValue = testCnf, - TestId = "ValidManualCnfClaim", + ExpectedClaimValue = $@"{{""cnf"":{testCnf}}}" }, - new CreateSignedHttpRequestTheoryData + new CreateSignedHttpRequestTheoryData("ValidJwkRsaKey") { ExpectedClaim = ConfirmationClaimTypes.Cnf, SigningCredentials = new SigningCredentials(KeyingMaterial.JsonWebKeyRsa_1024, SecurityAlgorithms.RsaSha256, SecurityAlgorithms.Sha256), - ExpectedClaimValue = $@"{{""{ConfirmationClaimTypes.Jwk}"":{{""{JsonWebKeyParameterNames.Kid}"":""{Base64UrlEncoder.Encode(KeyingMaterial.JsonWebKeyRsa_1024.ComputeJwkThumbprint())}"",""{JsonWebKeyParameterNames.E}"":""{KeyingMaterial.JsonWebKeyRsa_1024.E}"",""{JsonWebKeyParameterNames.Kty}"":""{JsonWebAlgorithmsKeyTypes.RSA}"",""{JsonWebKeyParameterNames.N}"":""{KeyingMaterial.JsonWebKeyRsa_1024.N}""}}}}", - TestId = "ValidJwkRsaKey", + ExpectedClaimValue = $@"{{""cnf"":{{""{ConfirmationClaimTypes.Jwk}"":{{""{JsonWebKeyParameterNames.Kid}"":""{Base64UrlEncoder.Encode(KeyingMaterial.JsonWebKeyRsa_1024.ComputeJwkThumbprint())}"",""{JsonWebKeyParameterNames.E}"":""{KeyingMaterial.JsonWebKeyRsa_1024.E}"",""{JsonWebKeyParameterNames.Kty}"":""{JsonWebAlgorithmsKeyTypes.RSA}"",""{JsonWebKeyParameterNames.N}"":""{KeyingMaterial.JsonWebKeyRsa_1024.N}""}}}}}}" }, - new CreateSignedHttpRequestTheoryData + new CreateSignedHttpRequestTheoryData("ValidJwkECKey") { ExpectedClaim = ConfirmationClaimTypes.Cnf, SigningCredentials = new SigningCredentials(KeyingMaterial.JsonWebKeyP256, SecurityAlgorithms.EcdsaSha256, SecurityAlgorithms.Sha256), - ExpectedClaimValue = $@"{{""{ConfirmationClaimTypes.Jwk}"":{{""{JsonWebKeyParameterNames.Kid}"":""{Base64UrlEncoder.Encode(KeyingMaterial.JsonWebKeyP256.ComputeJwkThumbprint())}"",""{JsonWebKeyParameterNames.Crv}"":""{KeyingMaterial.JsonWebKeyP256.Crv}"",""{JsonWebKeyParameterNames.Kty}"":""{JsonWebAlgorithmsKeyTypes.EllipticCurve}"",""{JsonWebKeyParameterNames.X}"":""{KeyingMaterial.JsonWebKeyP256.X}"",""{JsonWebKeyParameterNames.Y}"":""{KeyingMaterial.JsonWebKeyP256.Y}""}}}}", - TestId = "ValidJwkECKey", + ExpectedClaimValue = $@"{{""cnf"":{{""{ConfirmationClaimTypes.Jwk}"":{{""{JsonWebKeyParameterNames.Kid}"":""{Base64UrlEncoder.Encode(KeyingMaterial.JsonWebKeyP256.ComputeJwkThumbprint())}"",""{JsonWebKeyParameterNames.Crv}"":""{KeyingMaterial.JsonWebKeyP256.Crv}"",""{JsonWebKeyParameterNames.Kty}"":""{JsonWebAlgorithmsKeyTypes.EllipticCurve}"",""{JsonWebKeyParameterNames.X}"":""{KeyingMaterial.JsonWebKeyP256.X}"",""{JsonWebKeyParameterNames.Y}"":""{KeyingMaterial.JsonWebKeyP256.Y}""}}}}}}" }, - new CreateSignedHttpRequestTheoryData + new CreateSignedHttpRequestTheoryData("InvalidJwkSymmetricKey") { ExpectedClaim = ConfirmationClaimTypes.Cnf, SigningCredentials = new SigningCredentials(KeyingMaterial.SymmetricSecurityKey2_1024, SecurityAlgorithms.Aes128CbcHmacSha256, SecurityAlgorithms.Sha256), - ExpectedException = new ExpectedException(typeof(SignedHttpRequestCreationException), "IDX23008", typeof(SignedHttpRequestCreationException)), - TestId = "InvalidJwkSymmetricKey", + ExpectedException = new ExpectedException(typeof(SignedHttpRequestCreationException), "IDX23008", typeof(SignedHttpRequestCreationException)) }, - new CreateSignedHttpRequestTheoryData + new CreateSignedHttpRequestTheoryData("InvalidJwkSymmetricKey") { ExpectedClaim = ConfirmationClaimTypes.Cnf, SigningCredentials = new SigningCredentials(KeyingMaterial.JsonWebKeySymmetric128, SecurityAlgorithms.Aes128CbcHmacSha256, SecurityAlgorithms.Sha256), - ExpectedException = new ExpectedException(typeof(SignedHttpRequestCreationException), "IDX23008", typeof(ArgumentException)), - TestId = "InvalidJwkSymmetricKey", + ExpectedException = new ExpectedException(typeof(SignedHttpRequestCreationException), "IDX23008", typeof(ArgumentException)) }, - new CreateSignedHttpRequestTheoryData + new CreateSignedHttpRequestTheoryData("ValidRsaKey") { ExpectedClaim = ConfirmationClaimTypes.Cnf, SigningCredentials = new SigningCredentials(KeyingMaterial.RsaSecurityKey1, SecurityAlgorithms.RsaSha256, SecurityAlgorithms.Sha256), - ExpectedClaimValue = $@"{{""{ConfirmationClaimTypes.Jwk}"":{{""{JsonWebKeyParameterNames.Kid}"":""{Base64UrlEncoder.Encode(JsonWebKeyConverter.ConvertFromRSASecurityKey(KeyingMaterial.RsaSecurityKey1).ComputeJwkThumbprint())}"",""{JsonWebKeyParameterNames.E}"":""{Base64UrlEncoder.Encode(KeyingMaterial.RsaParameters1.Exponent)}"",""{JsonWebKeyParameterNames.Kty}"":""{JsonWebAlgorithmsKeyTypes.RSA}"",""{JsonWebKeyParameterNames.N}"":""{Base64UrlEncoder.Encode(KeyingMaterial.RsaParameters1.Modulus)}""}}}}", - TestId = "ValidRsaKey", + ExpectedClaimValue = $@"{{""cnf"":{{""{ConfirmationClaimTypes.Jwk}"":{{""{JsonWebKeyParameterNames.Kid}"":""{Base64UrlEncoder.Encode(JsonWebKeyConverter.ConvertFromRSASecurityKey(KeyingMaterial.RsaSecurityKey1).ComputeJwkThumbprint())}"",""{JsonWebKeyParameterNames.E}"":""{Base64UrlEncoder.Encode(KeyingMaterial.RsaParameters1.Exponent)}"",""{JsonWebKeyParameterNames.Kty}"":""{JsonWebAlgorithmsKeyTypes.RSA}"",""{JsonWebKeyParameterNames.N}"":""{Base64UrlEncoder.Encode(KeyingMaterial.RsaParameters1.Modulus)}""}}}}}}" }, - new CreateSignedHttpRequestTheoryData + new CreateSignedHttpRequestTheoryData("ValidX509Key") { ExpectedClaim = ConfirmationClaimTypes.Cnf, SigningCredentials = new SigningCredentials(KeyingMaterial.X509SecurityKey1, SecurityAlgorithms.RsaSha256, SecurityAlgorithms.Sha256), - ExpectedClaimValue = $@"{{""{ConfirmationClaimTypes.Jwk}"":{{""{JsonWebKeyParameterNames.Kid}"":""{Base64UrlEncoder.Encode(rsaJwkFromX509Key.ComputeJwkThumbprint())}"",""{JsonWebKeyParameterNames.E}"":""{rsaJwkFromX509Key.E}"",""{JsonWebKeyParameterNames.Kty}"":""{JsonWebAlgorithmsKeyTypes.RSA}"",""{JsonWebKeyParameterNames.N}"":""{rsaJwkFromX509Key.N}""}}}}", - TestId = "ValidX509Key", + ExpectedClaimValue = $@"{{""cnf"":{{""{ConfirmationClaimTypes.Jwk}"":{{""{JsonWebKeyParameterNames.Kid}"":""{Base64UrlEncoder.Encode(rsaJwkFromX509Key.ComputeJwkThumbprint())}"",""{JsonWebKeyParameterNames.E}"":""{rsaJwkFromX509Key.E}"",""{JsonWebKeyParameterNames.Kty}"":""{JsonWebAlgorithmsKeyTypes.RSA}"",""{JsonWebKeyParameterNames.N}"":""{rsaJwkFromX509Key.N}""}}}}}}" }, #if NET472 || NET_CORE - new CreateSignedHttpRequestTheoryData + new CreateSignedHttpRequestTheoryData("ValidEcdsaKey") { ExpectedClaim = ConfirmationClaimTypes.Cnf, SigningCredentials = new SigningCredentials(KeyingMaterial.Ecdsa256Key, SecurityAlgorithms.EcdsaSha256, SecurityAlgorithms.Sha256), - ExpectedClaimValue = $@"{{""{ConfirmationClaimTypes.Jwk}"":{{""{JsonWebKeyParameterNames.Kid}"":""{Base64UrlEncoder.Encode(JsonWebKeyConverter.ConvertFromECDsaSecurityKey(KeyingMaterial.Ecdsa256Key).ComputeJwkThumbprint())}"",""{JsonWebKeyParameterNames.Crv}"":""{ECDsaAdapter.GetCrvParameterValue(KeyingMaterial.Ecdsa256Parameters.Curve)}"",""{JsonWebKeyParameterNames.Kty}"":""{JsonWebAlgorithmsKeyTypes.EllipticCurve}"",""{JsonWebKeyParameterNames.X}"":""{Base64UrlEncoder.Encode(KeyingMaterial.Ecdsa256Parameters.Q.X)}"",""{JsonWebKeyParameterNames.Y}"":""{Base64UrlEncoder.Encode(KeyingMaterial.Ecdsa256Parameters.Q.Y)}""}}}}", - TestId = "ValidEcdsaKey", + ExpectedClaimValue = $@"{{""cnf"":{{""{ConfirmationClaimTypes.Jwk}"":{{""{JsonWebKeyParameterNames.Kid}"":""{Base64UrlEncoder.Encode(JsonWebKeyConverter.ConvertFromECDsaSecurityKey(KeyingMaterial.Ecdsa256Key).ComputeJwkThumbprint())}"",""{JsonWebKeyParameterNames.Crv}"":""{ECDsaAdapter.GetCrvParameterValue(KeyingMaterial.Ecdsa256Parameters.Curve)}"",""{JsonWebKeyParameterNames.Kty}"":""{JsonWebAlgorithmsKeyTypes.EllipticCurve}"",""{JsonWebKeyParameterNames.X}"":""{Base64UrlEncoder.Encode(KeyingMaterial.Ecdsa256Parameters.Q.X)}"",""{JsonWebKeyParameterNames.Y}"":""{Base64UrlEncoder.Encode(KeyingMaterial.Ecdsa256Parameters.Q.Y)}""}}}}}}" }, #else - new CreateSignedHttpRequestTheoryData + new CreateSignedHttpRequestTheoryData("InvalidEcdsaKeyDesktop") { ExpectedClaim = ConfirmationClaimTypes.Cnf, SigningCredentials = new SigningCredentials(KeyingMaterial.Ecdsa256Key, SecurityAlgorithms.EcdsaSha256, SecurityAlgorithms.Sha256), - ExpectedException = new ExpectedException(typeof(SignedHttpRequestCreationException), "IDX10674", typeof(NotSupportedException)), - TestId = "InvalidEcdsaKeyDesktop", + ExpectedException = new ExpectedException(typeof(SignedHttpRequestCreationException), "IDX10674", typeof(NotSupportedException)) }, #endif - new CreateSignedHttpRequestTheoryData - { - ExpectedClaim = ConfirmationClaimTypes.Cnf, - Cnf = testCnf, - Payload = new Dictionary() { {ConfirmationClaimTypes.Cnf, null } }, - ExpectedException = new ExpectedException(typeof(SignedHttpRequestCreationException), "IDX23008: Exception caught while creating the 'cnf' claim.", typeof(ArgumentException)), - TestId = "InvalidPayloadAlreadyHasCnfClaim" - }, }; } } @@ -1136,29 +931,22 @@ public static TheoryData CreateCnfClaimTheory public void CreateNonceClaim(CreateSignedHttpRequestTheoryData theoryData) { var context = TestUtilities.WriteHeader($"{this}.CreateNonceClaim", theoryData); + Utf8JsonWriter writer = null; try { - var handler = new SignedHttpRequestHandlerPublic(); - var signedHttpRequestDescriptor = theoryData.BuildSignedHttpRequestDescriptor(); - - handler.AddNonceClaimPublic(theoryData.Payload, signedHttpRequestDescriptor); - var payload = JObject.FromObject(theoryData.Payload); - - if (!payload.ContainsKey(theoryData.ExpectedClaim)) - context.AddDiff($"Payload doesn't contain the claim '{theoryData.ExpectedClaim}'"); - - if (!string.IsNullOrEmpty(signedHttpRequestDescriptor.CustomNonceValue)) - { - if (!IdentityComparer.AreStringsEqual(payload.Value(theoryData.ExpectedClaim), theoryData.ExpectedClaimValue, context)) - context.AddDiff($"Value of '{theoryData.ExpectedClaim}' claim is '{payload.Value(theoryData.ExpectedClaim)}', but expected value was '{theoryData.ExpectedClaimValue}'"); - } - + writer = theoryData.GetWriter(); + theoryData.Handler.AddNonceClaim(ref writer, theoryData.BuildSignedHttpRequestDescriptor()); + CheckClaimValue(ref writer, theoryData, context); theoryData.ExpectedException.ProcessNoException(context); } catch (Exception ex) { theoryData.ExpectedException.ProcessException(ex, context); } + finally + { + writer?.Dispose(); + } TestUtilities.AssertFailIfErrors(context); } @@ -1169,26 +957,17 @@ public static TheoryData CreateNonceClaimTheo { return new TheoryData { - new CreateSignedHttpRequestTheoryData + new CreateSignedHttpRequestTheoryData("ValidDefaultNonce") { - First = true, ExpectedClaim = SignedHttpRequestClaimTypes.Nonce, - TestId = "ValidDefaultNonce", }, - new CreateSignedHttpRequestTheoryData + new CreateSignedHttpRequestTheoryData("ValidCustomNonce") { ExpectedClaim = "nonce", - ExpectedClaimValue = "nonce1", + ExpectedClaimValue = $@"{{""nonce"":""nonce1""}}", SignedHttpRequestCreationParameters = new SignedHttpRequestCreationParameters(), - CustomNonceValue = "nonce1", - TestId = "ValidCustomNonce", - }, - new CreateSignedHttpRequestTheoryData - { - Payload = null, - ExpectedException = ExpectedException.ArgumentNullException(), - TestId = "NullPayload", - }, + CustomNonceValue = "nonce1" + } }; } } @@ -1199,10 +978,10 @@ public void CreateAdditionalClaim(CreateSignedHttpRequestTheoryData theoryData) var context = TestUtilities.WriteHeader($"{this}.CreateAdditionalClaim", theoryData); try { - var handler = new SignedHttpRequestHandlerPublic(); + var handler = new SignedHttpRequestHandler(); var signedHttpRequestDescriptor = theoryData.BuildSignedHttpRequestDescriptor(); - var payloadString = handler.CreateHttpRequestPayloadPublic(signedHttpRequestDescriptor, theoryData.CallContext); + var payloadString = handler.CreateHttpRequestPayload(signedHttpRequestDescriptor, theoryData.CallContext); var payload = JObject.Parse(payloadString); if (signedHttpRequestDescriptor.AdditionalPayloadClaims != null) @@ -1230,9 +1009,8 @@ public static TheoryData CreateAdditionalClai { return new TheoryData { - new CreateSignedHttpRequestTheoryData + new CreateSignedHttpRequestTheoryData("ValidAdditionalClaim") { - First = true, ExpectedClaim = "customClaim", ExpectedClaimValue = "customClaimValue", SignedHttpRequestCreationParameters = new SignedHttpRequestCreationParameters() @@ -1241,10 +1019,9 @@ public static TheoryData CreateAdditionalClai CreateM = false, CreateP = false, }, - AdditionalPayloadClaims = new Dictionary() { { "customClaim", "customClaimValue" } }, - TestId = "ValidAdditionalClaim", + AdditionalPayloadClaims = new Dictionary() { { "customClaim", "customClaimValue" } } }, - new CreateSignedHttpRequestTheoryData + new CreateSignedHttpRequestTheoryData("ValidAdditionalClaims") { ExpectedClaim = "customClaim", ExpectedClaimValue = "customClaimValue", @@ -1254,32 +1031,37 @@ public static TheoryData CreateAdditionalClai CreateM = false, CreateP = false, }, - AdditionalPayloadClaims = new Dictionary() { { SignedHttpRequestClaimTypes.M, "will_not_be_overwritten" }, {"customClaim", "customClaimValue" } }, - TestId = "ValidAdditionalClaims", + AdditionalPayloadClaims = new Dictionary() { { SignedHttpRequestClaimTypes.M, "will_not_be_overwritten" }, {"customClaim", "customClaimValue" } } }, - new CreateSignedHttpRequestTheoryData + new CreateSignedHttpRequestTheoryData("AdditionalCustomClaimsNotSet") { SignedHttpRequestCreationParameters = new SignedHttpRequestCreationParameters() { CreateU = false, CreateM = false, CreateP = false, - }, - TestId = "AdditionalCustomClaimsNotSet", - }, - new CreateSignedHttpRequestTheoryData - { - Payload = null, - ExpectedException = ExpectedException.ArgumentNullException(), - TestId = "NullPayload", - }, + } + } }; } } + + internal static void CheckClaimValue(ref Utf8JsonWriter writer, CreateSignedHttpRequestTheoryData theoryData, CompareContext context) + { + writer.WriteEndObject(); + writer.Flush(); + string claim = Encoding.UTF8.GetString(theoryData.MemoryStream.ToArray()); + if (theoryData.ExpectedClaimValue != null) + IdentityComparer.AreEqual(claim, theoryData.ExpectedClaimValue as string, context); + } } public class CreateSignedHttpRequestTheoryData : TheoryDataBase { + public CreateSignedHttpRequestTheoryData() { } + + public CreateSignedHttpRequestTheoryData(string testId) : base(testId) { } + public SignedHttpRequestDescriptor BuildSignedHttpRequestDescriptor() { var httpRequestData = new HttpRequestData() @@ -1331,6 +1113,8 @@ public SignedHttpRequestDescriptor BuildSignedHttpRequestDescriptor() CreateU = true }; + public SignedHttpRequestHandler Handler { get; set; } = new SignedHttpRequestHandler(); + public Dictionary Payload { get; set; } = new Dictionary(); public SigningCredentials SigningCredentials { get; set; } = SignedHttpRequestTestUtils.DefaultSigningCredentials; @@ -1342,7 +1126,16 @@ public SignedHttpRequestDescriptor BuildSignedHttpRequestDescriptor() public string PayloadString { get; set; } public string Cnf { get; set; } + + public MemoryStream MemoryStream { get; set; } + + public Utf8JsonWriter GetWriter() + { + MemoryStream = new MemoryStream(); + Utf8JsonWriter writer = new Utf8JsonWriter(MemoryStream, new JsonWriterOptions { Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping }); + writer.WriteStartObject(); + + return writer; + } } } - -#pragma warning restore CS3016 // Arrays as attribute arguments is not CLS-compliant diff --git a/test/Microsoft.IdentityModel.Protocols.SignedHttpRequest.Tests/SignedHttpRequestHandlerPublic.cs b/test/Microsoft.IdentityModel.Protocols.SignedHttpRequest.Tests/SignedHttpRequestHandlerPublic.cs index 814fd86817..161f038d32 100644 --- a/test/Microsoft.IdentityModel.Protocols.SignedHttpRequest.Tests/SignedHttpRequestHandlerPublic.cs +++ b/test/Microsoft.IdentityModel.Protocols.SignedHttpRequest.Tests/SignedHttpRequestHandlerPublic.cs @@ -16,61 +16,6 @@ namespace Microsoft.IdentityModel.Protocols.SignedHttpRequest.Tests /// public class SignedHttpRequestHandlerPublic : SignedHttpRequestHandler { - public string CreateHttpRequestPayloadPublic(SignedHttpRequestDescriptor signedHttpRequestDescriptor, CallContext callContext) - { - return CreateHttpRequestPayload(signedHttpRequestDescriptor, callContext); - } - - public void AddAtClaimPublic(Dictionary payload, SignedHttpRequestDescriptor signedHttpRequestDescriptor) - { - AddAtClaim(payload, signedHttpRequestDescriptor); - } - - public void AddTsClaimPublic(Dictionary payload, SignedHttpRequestDescriptor signedHttpRequestDescriptor) - { - AddTsClaim(payload, signedHttpRequestDescriptor); - } - - public void AddMClaimPublic(Dictionary payload, SignedHttpRequestDescriptor signedHttpRequestDescriptor) - { - AddMClaim(payload, signedHttpRequestDescriptor); - } - - public void AddUClaimPublic(Dictionary payload, SignedHttpRequestDescriptor signedHttpRequestDescriptor) - { - AddUClaim(payload, signedHttpRequestDescriptor); - } - - public void AddPClaimPublic(Dictionary payload, SignedHttpRequestDescriptor signedHttpRequestDescriptor) - { - AddPClaim(payload, signedHttpRequestDescriptor); - } - - public void AddQClaimPublic(Dictionary payload, SignedHttpRequestDescriptor signedHttpRequestDescriptor) - { - AddQClaim(payload, signedHttpRequestDescriptor); - } - - public void AddHClaimPublic(Dictionary payload, SignedHttpRequestDescriptor signedHttpRequestDescriptor) - { - AddHClaim(payload, signedHttpRequestDescriptor); - } - - public void AddBClaimPublic(Dictionary payload, SignedHttpRequestDescriptor signedHttpRequestDescriptor) - { - AddBClaim(payload, signedHttpRequestDescriptor); - } - - public void AddNonceClaimPublic(Dictionary payload, SignedHttpRequestDescriptor signedHttpRequestDescriptor) - { - AddNonceClaim(payload, signedHttpRequestDescriptor); - } - - public void AddCnfClaimPublic(Dictionary payload, SignedHttpRequestDescriptor signedHttpRequestDescriptor) - { - AddCnfClaim(payload, signedHttpRequestDescriptor); - } - public async Task ValidateSignedHttpRequestPayloadPublicAsync(SecurityToken signedHttpRequest, SignedHttpRequestValidationContext signedHttpRequestValidationContext, CancellationToken cancellationToken) { return await ValidateSignedHttpRequestPayloadAsync(signedHttpRequest, signedHttpRequestValidationContext, cancellationToken).ConfigureAwait(false); diff --git a/test/Microsoft.IdentityModel.Protocols.SignedHttpRequest.Tests/SignedHttpRequestValidationTests.cs b/test/Microsoft.IdentityModel.Protocols.SignedHttpRequest.Tests/SignedHttpRequestValidationTests.cs index f825be6ff0..632d5922a1 100644 --- a/test/Microsoft.IdentityModel.Protocols.SignedHttpRequest.Tests/SignedHttpRequestValidationTests.cs +++ b/test/Microsoft.IdentityModel.Protocols.SignedHttpRequest.Tests/SignedHttpRequestValidationTests.cs @@ -90,9 +90,9 @@ public void ValidateTsClaim(ValidateSignedHttpRequestTheoryData theoryData) var context = TestUtilities.WriteHeader($"{this}.ValidateTsClaim", theoryData); try { - var handler = new SignedHttpRequestHandlerPublic(); + var handler = new SignedHttpRequestHandler(); var signedHttpRequestValidationContext = theoryData.BuildSignedHttpRequestValidationContext(); - handler.ValidateTsClaimPublic(theoryData.SignedHttpRequestToken, signedHttpRequestValidationContext); + handler.ValidateTsClaim(theoryData.SignedHttpRequestToken, signedHttpRequestValidationContext); theoryData.ExpectedException.ProcessNoException(context); } catch (Exception ex) @@ -148,9 +148,9 @@ public void ValidateMClaim(ValidateSignedHttpRequestTheoryData theoryData) var context = TestUtilities.WriteHeader($"{this}.ValidateMClaim", theoryData); try { - var handler = new SignedHttpRequestHandlerPublic(); + var handler = new SignedHttpRequestHandler(); var signedHttpRequestValidationContext = theoryData.BuildSignedHttpRequestValidationContext(); - handler.ValidateMClaimPublic(theoryData.SignedHttpRequestToken, signedHttpRequestValidationContext); + handler.ValidateMClaim(theoryData.SignedHttpRequestToken, signedHttpRequestValidationContext); theoryData.ExpectedException.ProcessNoException(context); } catch (Exception ex) @@ -230,9 +230,9 @@ public void ValidateUClaim(ValidateSignedHttpRequestTheoryData theoryData) var context = TestUtilities.WriteHeader($"{this}.ValidateUClaim", theoryData); try { - var handler = new SignedHttpRequestHandlerPublic(); + var handler = new SignedHttpRequestHandler(); var signedHttpRequestValidationContext = theoryData.BuildSignedHttpRequestValidationContext(); - handler.ValidateUClaimPublic(theoryData.SignedHttpRequestToken, signedHttpRequestValidationContext); + handler.ValidateUClaim(theoryData.SignedHttpRequestToken, signedHttpRequestValidationContext); theoryData.ExpectedException.ProcessNoException(context); } catch (Exception ex) @@ -324,9 +324,9 @@ public void ValidatePClaim(ValidateSignedHttpRequestTheoryData theoryData) var context = TestUtilities.WriteHeader($"{this}.ValidatePClaim", theoryData); try { - var handler = new SignedHttpRequestHandlerPublic(); + var handler = new SignedHttpRequestHandler(); var signedHttpRequestValidationContext = theoryData.BuildSignedHttpRequestValidationContext(); - handler.ValidatePClaimPublic(theoryData.SignedHttpRequestToken, signedHttpRequestValidationContext); + handler.ValidatePClaim(theoryData.SignedHttpRequestToken, signedHttpRequestValidationContext); theoryData.ExpectedException.ProcessNoException(context); } catch (Exception ex) @@ -435,9 +435,9 @@ public void ValidateHClaim(ValidateSignedHttpRequestTheoryData theoryData) var context = TestUtilities.WriteHeader($"{this}.ValidateHClaim", theoryData); try { - var handler = new SignedHttpRequestHandlerPublic(); + var handler = new SignedHttpRequestHandler(); var signedHttpRequestValidationContext = theoryData.BuildSignedHttpRequestValidationContext(); - handler.ValidateHClaimPublic(theoryData.SignedHttpRequestToken, signedHttpRequestValidationContext); + handler.ValidateHClaim(theoryData.SignedHttpRequestToken, signedHttpRequestValidationContext); theoryData.ExpectedException.ProcessNoException(context); } catch (Exception ex) @@ -686,9 +686,9 @@ public void ValidateQClaim(ValidateSignedHttpRequestTheoryData theoryData) var context = TestUtilities.WriteHeader($"{this}.ValidateQClaim", theoryData); try { - var handler = new SignedHttpRequestHandlerPublic(); + var handler = new SignedHttpRequestHandler(); var signedHttpRequestValidationContext = theoryData.BuildSignedHttpRequestValidationContext(); - handler.ValidateQClaimPublic(theoryData.SignedHttpRequestToken, signedHttpRequestValidationContext); + handler.ValidateQClaim(theoryData.SignedHttpRequestToken, signedHttpRequestValidationContext); theoryData.ExpectedException.ProcessNoException(context); } catch (Exception ex) @@ -861,9 +861,9 @@ public void ValidateBClaim(ValidateSignedHttpRequestTheoryData theoryData) var context = TestUtilities.WriteHeader($"{this}.ValidateBClaim", theoryData); try { - var handler = new SignedHttpRequestHandlerPublic(); + var handler = new SignedHttpRequestHandler(); var signedHttpRequestValidationContext = theoryData.BuildSignedHttpRequestValidationContext(); - handler.ValidateBClaimPublic(theoryData.SignedHttpRequestToken, signedHttpRequestValidationContext); + handler.ValidateBClaim(theoryData.SignedHttpRequestToken, signedHttpRequestValidationContext); theoryData.ExpectedException.ProcessNoException(context); } catch (Exception ex) @@ -925,7 +925,7 @@ public async Task ValidateSignedHttpRequestCalls(ValidateSignedHttpRequestTheory var signedHttpRequestValidationContext = theoryData.BuildSignedHttpRequestValidationContext(); var handler = new SignedHttpRequestHandlerPublic(); - var signedHttpRequest = await handler.ValidateSignedHttpRequestPayloadPublicAsync(theoryData.SignedHttpRequestToken, signedHttpRequestValidationContext, CancellationToken.None).ConfigureAwait(false); + var signedHttpRequest = await handler.ValidateSignedHttpRequestPayloadAsync(theoryData.SignedHttpRequestToken, signedHttpRequestValidationContext, CancellationToken.None).ConfigureAwait(false); var methodCalledStatus = (bool)signedHttpRequestValidationContext.CallContext.PropertyBag["onlyTrack_ValidateTsClaimCall"]; if (methodCalledStatus != signedHttpRequestValidationContext.SignedHttpRequestValidationParameters.ValidateTs && @@ -1265,9 +1265,9 @@ public async Task ValidateSignedHttpRequestSignature(ValidateSignedHttpRequestTh var context = TestUtilities.WriteHeader($"{this}.ValidateSignedHttpRequestSignature", theoryData); try { - var handler = new SignedHttpRequestHandlerPublic(); + var handler = new SignedHttpRequestHandler(); var signedHttpRequestValidationContext = theoryData.BuildSignedHttpRequestValidationContext(); - var signingKey = await handler.ValidateSignaturePublicAsync(theoryData.SignedHttpRequestToken, theoryData.PopKey, signedHttpRequestValidationContext, CancellationToken.None).ConfigureAwait(false); + var signingKey = await handler.ValidateSignatureAsync(theoryData.SignedHttpRequestToken, theoryData.PopKey, signedHttpRequestValidationContext, CancellationToken.None).ConfigureAwait(false); IdentityComparer.AreSecurityKeysEqual(signingKey, theoryData.ExpectedPopKey, context); theoryData.ExpectedException.ProcessNoException(context); } diff --git a/test/Microsoft.IdentityModel.Protocols.Tests/Json/AuthenticationProtocolMessage6x.cs b/test/Microsoft.IdentityModel.Protocols.Tests/Json/AuthenticationProtocolMessage6x.cs new file mode 100644 index 0000000000..ffc0e9cb10 --- /dev/null +++ b/test/Microsoft.IdentityModel.Protocols.Tests/Json/AuthenticationProtocolMessage6x.cs @@ -0,0 +1,278 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System; +using System.Collections.Generic; +using System.Collections.Specialized; +using System.Text; +using Microsoft.IdentityModel.Logging; +using static System.Net.WebUtility; + +namespace Microsoft.IdentityModel.Protocols.Json.Tests +{ + /// + /// base class for authentication protocol messages. + /// This is the original AuthenticationProtocolMessage in the 6x branch. + /// Used for ensuring backcompat. + /// + public abstract class AuthenticationProtocolMessage6x + { + private string _postTitle = "Working..."; + private string _script = ""; + private string _scriptButtonText = "Submit"; + private string _scriptDisabledText = "Script is disabled. Click Submit to continue."; + + private Dictionary _parameters = new Dictionary(); + private string _issuerAddress = string.Empty; + + /// + /// Initializes a default instance of the class. + /// + protected AuthenticationProtocolMessage6x() + { + } + + /// + /// Builds a form post using the current IssuerAddress and the parameters that have been set. + /// + /// html with head set to 'Title', body containing a hiden from with action = IssuerAddress. + public virtual string BuildFormPost() + { + var strBuilder = new StringBuilder(); + strBuilder.Append(""); + strBuilder.Append(HtmlEncode(PostTitle)); + strBuilder.Append("
"); + foreach (KeyValuePair parameter in _parameters) + { + strBuilder.Append(""); + } + + strBuilder.Append(""); + strBuilder.Append(""); + strBuilder.Append(Script); + strBuilder.Append(""); + return strBuilder.ToString(); + } + + /// + /// Builds a URL using the current IssuerAddress and the parameters that have been set. + /// + /// UrlEncoded string. + /// Each parameter <Key, Value> is first transformed using . + public virtual string BuildRedirectUrl() + { + StringBuilder strBuilder = new StringBuilder(_issuerAddress); + bool issuerAddressHasQuery = _issuerAddress.Contains("?"); + foreach (KeyValuePair parameter in _parameters) + { + if (parameter.Value == null) + { + continue; + } + + if (!issuerAddressHasQuery) + { + strBuilder.Append('?'); + issuerAddressHasQuery = true; + } + else + { + strBuilder.Append('&'); + } + + strBuilder.Append(Uri.EscapeDataString(parameter.Key)); + strBuilder.Append('='); + strBuilder.Append(Uri.EscapeDataString(parameter.Value)); + } + + return strBuilder.ToString(); + } + + /// + /// Returns a parameter. + /// + /// The parameter name. + /// The value of the parameter or null if the parameter does not exists. + /// If parameter is null + public virtual string GetParameter(string parameter) + { + if (string.IsNullOrEmpty(parameter)) + throw LogHelper.LogArgumentNullException(nameof(parameter)); + + string value = null; + _parameters.TryGetValue(parameter, out value); + return value; + } + + /// + /// Gets or sets the issuer address. + /// + /// If the 'value' is null. + public string IssuerAddress + { + get + { + return _issuerAddress; + } + set + { + if (value == null) + throw LogHelper.LogArgumentNullException(nameof(IssuerAddress)); + + _issuerAddress = value; + } + } + + /// + /// Gets the message parameters as a Dictionary. + /// + public IDictionary Parameters + { + get + { + return _parameters; + } + } + + /// + /// Gets or sets the title used when constructing the post string. + /// + /// If the 'value' is null. + public string PostTitle + { + get + { + return _postTitle; + } + + set + { + if (value == null) + throw LogHelper.LogArgumentNullException(nameof(PostTitle)); + + _postTitle = value; + } + } + + /// + /// Removes a parameter. + /// + /// The parameter name. + /// If 'parameter' is null or empty. + public virtual void RemoveParameter(string parameter) + { + if (string.IsNullOrEmpty(parameter)) + throw LogHelper.LogArgumentNullException(nameof(parameter)); + + if (_parameters.ContainsKey(parameter)) + _parameters.Remove(parameter); + } + + /// + /// Sets a parameter to the Parameters Dictionary. + /// + /// The parameter name. + /// The value to be assigned to parameter. + /// If 'parameterName' is null or empty. + /// If null is passed as value and the parameter exists, that parameter is removed. + public void SetParameter(string parameter, string value) + { + if (string.IsNullOrEmpty(parameter)) + throw LogHelper.LogArgumentNullException(nameof(parameter)); + + if (value == null) + { + RemoveParameter(parameter); + } + else + { + _parameters[parameter] = value; + } + } + + /// + /// Sets a collection parameters. + /// + /// + public virtual void SetParameters(NameValueCollection nameValueCollection) + { + if (nameValueCollection == null) + return; + + foreach (string key in nameValueCollection.AllKeys) + { + SetParameter(key, nameValueCollection[key]); + }; + } + + /// + /// Gets the script used when constructing the post string. + /// + /// If the 'value' is null. + public string Script + { + get + { + return _script; + } + + set + { + if (value == null) + throw LogHelper.LogArgumentNullException(nameof(Script)); + + _script = value; + } + } + + /// + /// Gets or sets the script button text used when constructing the post string. + /// + /// If the 'value' is null. + public string ScriptButtonText + { + get + { + return _scriptButtonText; + } + + set + { + if (value == null) + throw LogHelper.LogArgumentNullException(nameof(ScriptButtonText)); + + _scriptButtonText = value; + } + } + + /// + /// Gets or sets the text used when constructing the post string that will be displayed to used if script is disabled. + /// + /// If the 'value' is null. + public string ScriptDisabledText + { + get + { + return _scriptDisabledText; + } + + set + { + if (value == null) + throw LogHelper.LogArgumentNullException(nameof(ScriptDisabledText)); + + _scriptDisabledText = value; + } + } + } +}