From c44a633567828cdad912bde05637b2e509b885d0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A9vin=20Chalet?= Date: Fri, 6 Nov 2020 16:41:43 +0100 Subject: [PATCH] Update OpenIddictMessage to ignore parameters with empty names --- .../Primitives/OpenIddictMessage.cs | 42 +++- .../Primitives/OpenIddictParameter.cs | 50 ++-- .../Primitives/OpenIddictRequest.cs | 5 + .../Primitives/OpenIddictResponse.cs | 5 + .../OpenIddictServerAspNetCoreHandlers.cs | 29 ++- ...OpenIddictServerDataProtectionFormatter.cs | 44 ++-- .../OpenIddictServerOwinHandlers.cs | 17 +- .../OpenIddictServerHandlers.Discovery.cs | 76 +++--- .../OpenIddictServerHandlers.Exchange.cs | 2 +- .../OpenIddictServerHandlers.Introspection.cs | 29 +-- .../OpenIddictServerHandlers.cs | 217 ++++++++---------- .../OpenIddictValidationAspNetCoreHandlers.cs | 14 +- .../OpenIddictValidationOwinHandlers.cs | 14 +- ...nIddictValidationHandlers.Introspection.cs | 130 ++++------- .../OpenIddictValidationHandlers.cs | 9 +- .../Primitives/OpenIddictMessageTests.cs | 35 ++- 16 files changed, 324 insertions(+), 394 deletions(-) diff --git a/src/OpenIddict.Abstractions/Primitives/OpenIddictMessage.cs b/src/OpenIddict.Abstractions/Primitives/OpenIddictMessage.cs index fa14dadd..f8b7ad76 100644 --- a/src/OpenIddict.Abstractions/Primitives/OpenIddictMessage.cs +++ b/src/OpenIddict.Abstractions/Primitives/OpenIddictMessage.cs @@ -42,6 +42,7 @@ namespace OpenIddict.Abstractions /// Initializes a new OpenIddict message. /// /// The message parameters. + /// Parameters with a null or empty key are always ignored. public OpenIddictMessage(JsonElement parameters) { if (parameters.ValueKind != JsonValueKind.Object) @@ -51,6 +52,12 @@ namespace OpenIddict.Abstractions foreach (var parameter in parameters.EnumerateObject()) { + // Ignore parameters whose name is null or empty. + if (string.IsNullOrEmpty(parameter.Name)) + { + continue; + } + // While generally discouraged, JSON objects can contain multiple properties with // the same name. In this case, the last occurrence replaces the previous ones. if (HasParameter(parameter.Name)) @@ -66,6 +73,7 @@ namespace OpenIddict.Abstractions /// Initializes a new OpenIddict message. /// /// The message parameters. + /// Parameters with a null or empty key are always ignored. public OpenIddictMessage(IEnumerable> parameters) { if (parameters is null) @@ -75,6 +83,12 @@ namespace OpenIddict.Abstractions foreach (var parameter in parameters) { + // Ignore parameters whose name is null or empty. + if (string.IsNullOrEmpty(parameter.Key)) + { + continue; + } + AddParameter(parameter.Key, parameter.Value); } } @@ -83,6 +97,7 @@ namespace OpenIddict.Abstractions /// Initializes a new OpenIddict message. /// /// The message parameters. + /// Parameters with a null or empty key are always ignored. public OpenIddictMessage(IEnumerable> parameters) { if (parameters is null) @@ -92,6 +107,12 @@ namespace OpenIddict.Abstractions foreach (var parameter in parameters.GroupBy(parameter => parameter.Key)) { + // Ignore parameters whose name is null or empty. + if (string.IsNullOrEmpty(parameter.Key)) + { + continue; + } + var values = parameter.Select(parameter => parameter.Value).ToArray(); // Note: the core OAuth 2.0 specification requires that request parameters @@ -111,6 +132,7 @@ namespace OpenIddict.Abstractions /// Initializes a new OpenIddict message. /// /// The message parameters. + /// Parameters with a null or empty key are always ignored. public OpenIddictMessage(IEnumerable> parameters) { if (parameters is null) @@ -120,16 +142,21 @@ namespace OpenIddict.Abstractions foreach (var parameter in parameters) { + // Ignore parameters whose name is null or empty. + if (string.IsNullOrEmpty(parameter.Key)) + { + continue; + } + // Note: the core OAuth 2.0 specification requires that request parameters // not be present more than once but derived specifications like the // token exchange specification deliberately allow specifying multiple // parameters with the same name to represent a multi-valued parameter. AddParameter(parameter.Key, parameter.Value?.Length switch { - null => default, - 0 => default, - 1 => parameter.Value[0], - _ => parameter.Value + null or 0 => default, + 1 => parameter.Value[0], + _ => parameter.Value }); } } @@ -138,6 +165,7 @@ namespace OpenIddict.Abstractions /// Initializes a new OpenIddict message. /// /// The message parameters. + /// Parameters with a null or empty key are always ignored. public OpenIddictMessage(IEnumerable> parameters) { if (parameters is null) @@ -147,6 +175,12 @@ namespace OpenIddict.Abstractions foreach (var parameter in parameters) { + // Ignore parameters whose name is null or empty. + if (string.IsNullOrEmpty(parameter.Key)) + { + continue; + } + // Note: the core OAuth 2.0 specification requires that request parameters // not be present more than once but derived specifications like the // token exchange specification deliberately allow specifying multiple diff --git a/src/OpenIddict.Abstractions/Primitives/OpenIddictParameter.cs b/src/OpenIddict.Abstractions/Primitives/OpenIddictParameter.cs index d6a05640..020d84bd 100644 --- a/src/OpenIddict.Abstractions/Primitives/OpenIddictParameter.cs +++ b/src/OpenIddict.Abstractions/Primitives/OpenIddictParameter.cs @@ -118,8 +118,7 @@ namespace OpenIddict.Abstractions var (left, right) when ReferenceEquals(left, right) => true, // If one of the two parameters is null, return false. - (null, _) => false, - (_, null) => false, + (null, _) or (_, null) => false, // If the two parameters are string arrays, use SequenceEqual(). (string?[] left, string?[] right) => left.SequenceEqual(right), @@ -516,8 +515,7 @@ namespace OpenIddict.Abstractions { // Note: undefined JsonElement values are assimilated to null values. case null: - case JsonElement { ValueKind: JsonValueKind.Undefined }: - case JsonElement { ValueKind: JsonValueKind.Null }: + case JsonElement { ValueKind: JsonValueKind.Null or JsonValueKind.Undefined }: writer.WriteNullValue(); break; @@ -583,8 +581,8 @@ namespace OpenIddict.Abstractions /// The converted value. public static explicit operator bool?(OpenIddictParameter? parameter) => parameter?.Value switch { - // When the parameter is a null value, return null. - null => null, + // When the parameter is a null value or a JsonElement representing null, return null. + null or JsonElement { ValueKind: JsonValueKind.Null or JsonValueKind.Undefined } => null, // When the parameter is a boolean value, return it as-is. bool value => value, @@ -592,10 +590,6 @@ namespace OpenIddict.Abstractions // When the parameter is a string value, try to parse it. string value => bool.TryParse(value, out var result) ? (bool?) result : null, - // When the parameter is a JsonElement representing null, return null. - JsonElement { ValueKind: JsonValueKind.Undefined } => null, - JsonElement { ValueKind: JsonValueKind.Null } => null, - // When the parameter is a JsonElement representing a boolean, return it as-is. JsonElement { ValueKind: JsonValueKind.False } => false, JsonElement { ValueKind: JsonValueKind.True } => true, @@ -625,7 +619,7 @@ namespace OpenIddict.Abstractions // When the parameter is a string starting with '{' or '[' (which would correspond // to a JSON object or array), try to deserialize it to get a JsonElement instance. - string { Length: > 0 } value when value[0] == '{' || value[0] == '[' => + string { Length: > 0 } value when value[0] is '{' or '[' => DeserializeElement(value) ?? DeserializeElement(SerializeObject(value)) ?? default, @@ -705,8 +699,8 @@ namespace OpenIddict.Abstractions /// The converted value. public static explicit operator long?(OpenIddictParameter? parameter) => parameter?.Value switch { - // When the parameter is a null value, return null. - null => null, + // When the parameter is a null value or a JsonElement representing null, return null. + null or JsonElement { ValueKind: JsonValueKind.Null or JsonValueKind.Undefined } => null, // When the parameter is an integer, return it as-is. long value => value, @@ -715,10 +709,6 @@ namespace OpenIddict.Abstractions string value => long.TryParse(value, NumberStyles.Integer, CultureInfo.InvariantCulture, out var result) ? (long?) result : null, - // When the parameter is a JsonElement representing null, return null. - JsonElement { ValueKind: JsonValueKind.Undefined } => null, - JsonElement { ValueKind: JsonValueKind.Null } => null, - // When the parameter is a JsonElement representing a number, return it as-is. JsonElement { ValueKind: JsonValueKind.Number } value => value.TryGetInt64(out var result) ? (long?) result : null, @@ -738,8 +728,8 @@ namespace OpenIddict.Abstractions /// The converted value. public static explicit operator string?(OpenIddictParameter? parameter) => parameter?.Value switch { - // When the parameter is a null value, return null. - null => null, + // When the parameter is a null value or a JsonElement representing null, return null. + null or JsonElement { ValueKind: JsonValueKind.Null or JsonValueKind.Undefined } => null, // When the parameter is a string value, return it as-is. string value => value, @@ -750,10 +740,6 @@ namespace OpenIddict.Abstractions // When the parameter is an integer, use its string representation. long value => value.ToString(CultureInfo.InvariantCulture), - // When the parameter is a JsonElement representing null, return null. - JsonElement { ValueKind: JsonValueKind.Undefined } => null, - JsonElement { ValueKind: JsonValueKind.Null } => null, - // When the parameter is a JsonElement representing a string, return it as-is. JsonElement { ValueKind: JsonValueKind.String } value => value.GetString(), @@ -778,8 +764,8 @@ namespace OpenIddict.Abstractions { return parameter?.Value switch { - // When the parameter is a null value, return a null array. - null => null, + // When the parameter is a null value or a JsonElement representing null, return null. + null or JsonElement { ValueKind: JsonValueKind.Null or JsonValueKind.Undefined } => null, // When the parameter is already an array of strings, return it as-is. string?[] value => value, @@ -793,10 +779,6 @@ namespace OpenIddict.Abstractions // When the parameter is an integer, return an array with its string representation. long value => new string?[] { value.ToString(CultureInfo.InvariantCulture) }, - // When the parameter is a JsonElement representing null, return null. - JsonElement { ValueKind: JsonValueKind.Undefined } => null, - JsonElement { ValueKind: JsonValueKind.Null } => null, - // When the parameter is a JsonElement representing a string, return an array with a single entry. JsonElement { ValueKind: JsonValueKind.String } value => new string?[] { value.GetString() }, @@ -893,16 +875,14 @@ namespace OpenIddict.Abstractions { return parameter.Value switch { - null => true, + null or JsonElement { ValueKind: JsonValueKind.Null or JsonValueKind.Undefined } => true, string value => string.IsNullOrEmpty(value), string?[] value => value.Length == 0, - JsonElement { ValueKind: JsonValueKind.Undefined } => true, - JsonElement { ValueKind: JsonValueKind.Null } => true, - JsonElement { ValueKind: JsonValueKind.String } value => string.IsNullOrEmpty(value.GetString()), - JsonElement { ValueKind: JsonValueKind.Array } value => value.GetArrayLength() == 0, - JsonElement { ValueKind: JsonValueKind.Object } value => IsEmptyNode(value), + JsonElement { ValueKind: JsonValueKind.String } value => string.IsNullOrEmpty(value.GetString()), + JsonElement { ValueKind: JsonValueKind.Array } value => value.GetArrayLength() == 0, + JsonElement { ValueKind: JsonValueKind.Object } value => IsEmptyNode(value), _ => false }; diff --git a/src/OpenIddict.Abstractions/Primitives/OpenIddictRequest.cs b/src/OpenIddict.Abstractions/Primitives/OpenIddictRequest.cs index 9bdc44a2..cd2e222d 100644 --- a/src/OpenIddict.Abstractions/Primitives/OpenIddictRequest.cs +++ b/src/OpenIddict.Abstractions/Primitives/OpenIddictRequest.cs @@ -36,6 +36,7 @@ namespace OpenIddict.Abstractions /// Initializes a new OpenIddict request. /// /// The request parameters. + /// Parameters with a null or empty key are always ignored. public OpenIddictRequest(JsonElement parameters) : base(parameters) { @@ -45,6 +46,7 @@ namespace OpenIddict.Abstractions /// Initializes a new OpenIddict request. /// /// The request parameters. + /// Parameters with a null or empty key are always ignored. public OpenIddictRequest(IEnumerable> parameters) : base(parameters) { @@ -54,6 +56,7 @@ namespace OpenIddict.Abstractions /// Initializes a new OpenIddict request. /// /// The request parameters. + /// Parameters with a null or empty key are always ignored. public OpenIddictRequest(IEnumerable> parameters) : base(parameters) { @@ -63,6 +66,7 @@ namespace OpenIddict.Abstractions /// Initializes a new OpenIddict request. /// /// The request parameters. + /// Parameters with a null or empty key are always ignored. public OpenIddictRequest(IEnumerable> parameters) : base(parameters) { @@ -72,6 +76,7 @@ namespace OpenIddict.Abstractions /// Initializes a new OpenIddict request. /// /// The request parameters. + /// Parameters with a null or empty key are always ignored. public OpenIddictRequest(IEnumerable> parameters) : base(parameters) { diff --git a/src/OpenIddict.Abstractions/Primitives/OpenIddictResponse.cs b/src/OpenIddict.Abstractions/Primitives/OpenIddictResponse.cs index 1d242c86..bdf1beda 100644 --- a/src/OpenIddict.Abstractions/Primitives/OpenIddictResponse.cs +++ b/src/OpenIddict.Abstractions/Primitives/OpenIddictResponse.cs @@ -36,6 +36,7 @@ namespace OpenIddict.Abstractions /// Initializes a new OpenIddict response. /// /// The response parameters. + /// Parameters with a null or empty key are always ignored. public OpenIddictResponse(JsonElement parameters) : base(parameters) { @@ -45,6 +46,7 @@ namespace OpenIddict.Abstractions /// Initializes a new OpenIddict response. /// /// The response parameters. + /// Parameters with a null or empty key are always ignored. public OpenIddictResponse(IEnumerable> parameters) : base(parameters) { @@ -54,6 +56,7 @@ namespace OpenIddict.Abstractions /// Initializes a new OpenIddict response. /// /// The response parameters. + /// Parameters with a null or empty key are always ignored. public OpenIddictResponse(IEnumerable> parameters) : base(parameters) { @@ -63,6 +66,7 @@ namespace OpenIddict.Abstractions /// Initializes a new OpenIddict response. /// /// The response parameters. + /// Parameters with a null or empty key are always ignored. public OpenIddictResponse(IEnumerable> parameters) : base(parameters) { @@ -72,6 +76,7 @@ namespace OpenIddict.Abstractions /// Initializes a new OpenIddict response. /// /// The response parameters. + /// Parameters with a null or empty key are always ignored. public OpenIddictResponse(IEnumerable> parameters) : base(parameters) { diff --git a/src/OpenIddict.Server.AspNetCore/OpenIddictServerAspNetCoreHandlers.cs b/src/OpenIddict.Server.AspNetCore/OpenIddictServerAspNetCoreHandlers.cs index 985b5a50..ac5b97a1 100644 --- a/src/OpenIddict.Server.AspNetCore/OpenIddictServerAspNetCoreHandlers.cs +++ b/src/OpenIddict.Server.AspNetCore/OpenIddictServerAspNetCoreHandlers.cs @@ -365,12 +365,12 @@ namespace OpenIddict.Server.AspNetCore context.Transaction.Response.AddParameter(parameter.Key, parameter.Value switch { OpenIddictParameter value => value, - JsonElement value => value, - bool value => value, - int value => value, - long value => value, - string value => value, - string[] value => value, + JsonElement value => new OpenIddictParameter(value), + bool value => new OpenIddictParameter(value), + int value => new OpenIddictParameter(value), + long value => new OpenIddictParameter(value), + string value => new OpenIddictParameter(value), + string[] value => new OpenIddictParameter(value), _ => throw new InvalidOperationException(SR.GetResourceString(SR.ID0115)) }); @@ -816,12 +816,9 @@ namespace OpenIddict.Server.AspNetCore { null => 200, // Note: the default code may be replaced by another handler (e.g when doing redirects). - Errors.InvalidClient => 401, - Errors.InvalidToken => 401, - Errors.MissingToken => 401, + Errors.InvalidClient or Errors.InvalidToken or Errors.MissingToken => 401, - Errors.InsufficientAccess => 403, - Errors.InsufficientScope => 403, + Errors.InsufficientAccess or Errors.InsufficientScope => 403, _ => 400 }; @@ -921,12 +918,12 @@ namespace OpenIddict.Server.AspNetCore // were specified in the request form instead of the HTTP headers, as allowed by the specification. var scheme = context.Transaction.Response.Error switch { - Errors.InvalidClient => Schemes.Basic, + Errors.InvalidClient => Schemes.Basic, - Errors.InvalidToken => Schemes.Bearer, - Errors.MissingToken => Schemes.Bearer, - Errors.InsufficientAccess => Schemes.Bearer, - Errors.InsufficientScope => Schemes.Bearer, + Errors.InvalidToken or + Errors.MissingToken or + Errors.InsufficientAccess or + Errors.InsufficientScope => Schemes.Bearer, _ => null }; diff --git a/src/OpenIddict.Server.DataProtection/OpenIddictServerDataProtectionFormatter.cs b/src/OpenIddict.Server.DataProtection/OpenIddictServerDataProtectionFormatter.cs index 94b6ad14..9113ce8c 100644 --- a/src/OpenIddict.Server.DataProtection/OpenIddictServerDataProtectionFormatter.cs +++ b/src/OpenIddict.Server.DataProtection/OpenIddictServerDataProtectionFormatter.cs @@ -243,30 +243,26 @@ namespace OpenIddict.Server.DataProtection SetArrayProperty(properties, Properties.Scopes, principal.GetScopes()); // Copy the principal and exclude the claim that were mapped to authentication properties. - principal = principal.Clone(claim => claim.Type switch - { - Claims.Private.AccessTokenLifetime => false, - Claims.Private.Audience => false, - Claims.Private.AuthorizationCodeLifetime => false, - Claims.Private.AuthorizationId => false, - Claims.Private.CodeChallenge => false, - Claims.Private.CodeChallengeMethod => false, - Claims.Private.CreationDate => false, - Claims.Private.DeviceCodeId => false, - Claims.Private.DeviceCodeLifetime => false, - Claims.Private.ExpirationDate => false, - Claims.Private.IdentityTokenLifetime => false, - Claims.Private.Nonce => false, - Claims.Private.Presenter => false, - Claims.Private.RedirectUri => false, - Claims.Private.RefreshTokenLifetime => false, - Claims.Private.Resource => false, - Claims.Private.Scope => false, - Claims.Private.TokenId => false, - Claims.Private.UserCodeLifetime => false, - - _ => true - }); + principal = principal.Clone(claim => claim.Type is not ( + Claims.Private.AccessTokenLifetime or + Claims.Private.Audience or + Claims.Private.AuthorizationCodeLifetime or + Claims.Private.AuthorizationId or + Claims.Private.CodeChallenge or + Claims.Private.CodeChallengeMethod or + Claims.Private.CreationDate or + Claims.Private.DeviceCodeId or + Claims.Private.DeviceCodeLifetime or + Claims.Private.ExpirationDate or + Claims.Private.IdentityTokenLifetime or + Claims.Private.Nonce or + Claims.Private.Presenter or + Claims.Private.RedirectUri or + Claims.Private.RefreshTokenLifetime or + Claims.Private.Resource or + Claims.Private.Scope or + Claims.Private.TokenId or + Claims.Private.UserCodeLifetime)); Write(writer, principal.Identity.AuthenticationType, principal, properties); writer.Flush(); diff --git a/src/OpenIddict.Server.Owin/OpenIddictServerOwinHandlers.cs b/src/OpenIddict.Server.Owin/OpenIddictServerOwinHandlers.cs index b0ec2f95..749df1b1 100644 --- a/src/OpenIddict.Server.Owin/OpenIddictServerOwinHandlers.cs +++ b/src/OpenIddict.Server.Owin/OpenIddictServerOwinHandlers.cs @@ -753,12 +753,9 @@ namespace OpenIddict.Server.Owin { null => 200, // Note: the default code may be replaced by another handler (e.g when doing redirects). - Errors.InvalidClient => 401, - Errors.InvalidToken => 401, - Errors.MissingToken => 401, + Errors.InvalidClient or Errors.InvalidToken or Errors.MissingToken => 401, - Errors.InsufficientAccess => 403, - Errors.InsufficientScope => 403, + Errors.InsufficientAccess or Errors.InsufficientScope => 403, _ => 400 }; @@ -858,12 +855,12 @@ namespace OpenIddict.Server.Owin // were specified in the request form instead of the HTTP headers, as allowed by the specification. var scheme = context.Transaction.Response.Error switch { - Errors.InvalidClient => Schemes.Basic, + Errors.InvalidClient => Schemes.Basic, - Errors.InvalidToken => Schemes.Bearer, - Errors.MissingToken => Schemes.Bearer, - Errors.InsufficientAccess => Schemes.Bearer, - Errors.InsufficientScope => Schemes.Bearer, + Errors.InvalidToken or + Errors.MissingToken or + Errors.InsufficientAccess or + Errors.InsufficientScope => Schemes.Bearer, _ => null }; diff --git a/src/OpenIddict.Server/OpenIddictServerHandlers.Discovery.cs b/src/OpenIddict.Server/OpenIddictServerHandlers.Discovery.cs index c8c03e4d..649aabd3 100644 --- a/src/OpenIddict.Server/OpenIddictServerHandlers.Discovery.cs +++ b/src/OpenIddict.Server/OpenIddictServerHandlers.Discovery.cs @@ -689,26 +689,26 @@ namespace OpenIddict.Server var algorithm = credentials.Algorithm switch { #if SUPPORTS_ECDSA - SecurityAlgorithms.EcdsaSha256 => SecurityAlgorithms.EcdsaSha256, - SecurityAlgorithms.EcdsaSha384 => SecurityAlgorithms.EcdsaSha384, - SecurityAlgorithms.EcdsaSha512 => SecurityAlgorithms.EcdsaSha512, - SecurityAlgorithms.EcdsaSha256Signature => SecurityAlgorithms.EcdsaSha256, - SecurityAlgorithms.EcdsaSha384Signature => SecurityAlgorithms.EcdsaSha384, - SecurityAlgorithms.EcdsaSha512Signature => SecurityAlgorithms.EcdsaSha512, + SecurityAlgorithms.EcdsaSha256 or SecurityAlgorithms.EcdsaSha256Signature + => SecurityAlgorithms.EcdsaSha256, + SecurityAlgorithms.EcdsaSha384 or SecurityAlgorithms.EcdsaSha384Signature + => SecurityAlgorithms.EcdsaSha384, + SecurityAlgorithms.EcdsaSha512 or SecurityAlgorithms.EcdsaSha512Signature + => SecurityAlgorithms.EcdsaSha512, #endif - SecurityAlgorithms.RsaSha256 => SecurityAlgorithms.RsaSha256, - SecurityAlgorithms.RsaSha384 => SecurityAlgorithms.RsaSha384, - SecurityAlgorithms.RsaSha512 => SecurityAlgorithms.RsaSha512, - SecurityAlgorithms.RsaSha256Signature => SecurityAlgorithms.RsaSha256, - SecurityAlgorithms.RsaSha384Signature => SecurityAlgorithms.RsaSha384, - SecurityAlgorithms.RsaSha512Signature => SecurityAlgorithms.RsaSha512, - - SecurityAlgorithms.RsaSsaPssSha256 => SecurityAlgorithms.RsaSsaPssSha256, - SecurityAlgorithms.RsaSsaPssSha384 => SecurityAlgorithms.RsaSsaPssSha384, - SecurityAlgorithms.RsaSsaPssSha512 => SecurityAlgorithms.RsaSsaPssSha512, - SecurityAlgorithms.RsaSsaPssSha256Signature => SecurityAlgorithms.RsaSsaPssSha256, - SecurityAlgorithms.RsaSsaPssSha384Signature => SecurityAlgorithms.RsaSsaPssSha384, - SecurityAlgorithms.RsaSsaPssSha512Signature => SecurityAlgorithms.RsaSsaPssSha512, + SecurityAlgorithms.RsaSha256 or SecurityAlgorithms.RsaSha256Signature + => SecurityAlgorithms.RsaSha256, + SecurityAlgorithms.RsaSha384 or SecurityAlgorithms.RsaSha384Signature + => SecurityAlgorithms.RsaSha384, + SecurityAlgorithms.RsaSha512 or SecurityAlgorithms.RsaSha512Signature + => SecurityAlgorithms.RsaSha512, + + SecurityAlgorithms.RsaSsaPssSha256 or SecurityAlgorithms.RsaSsaPssSha256Signature + => SecurityAlgorithms.RsaSsaPssSha256, + SecurityAlgorithms.RsaSsaPssSha384 or SecurityAlgorithms.RsaSsaPssSha384Signature + => SecurityAlgorithms.RsaSsaPssSha384, + SecurityAlgorithms.RsaSsaPssSha512 or SecurityAlgorithms.RsaSsaPssSha512Signature + => SecurityAlgorithms.RsaSsaPssSha512, _ => null }; @@ -1108,26 +1108,26 @@ namespace OpenIddict.Server Alg = credentials.Algorithm switch { #if SUPPORTS_ECDSA - SecurityAlgorithms.EcdsaSha256 => SecurityAlgorithms.EcdsaSha256, - SecurityAlgorithms.EcdsaSha384 => SecurityAlgorithms.EcdsaSha384, - SecurityAlgorithms.EcdsaSha512 => SecurityAlgorithms.EcdsaSha512, - SecurityAlgorithms.EcdsaSha256Signature => SecurityAlgorithms.EcdsaSha256, - SecurityAlgorithms.EcdsaSha384Signature => SecurityAlgorithms.EcdsaSha384, - SecurityAlgorithms.EcdsaSha512Signature => SecurityAlgorithms.EcdsaSha512, + SecurityAlgorithms.EcdsaSha256 or SecurityAlgorithms.EcdsaSha256Signature + => SecurityAlgorithms.EcdsaSha256, + SecurityAlgorithms.EcdsaSha384 or SecurityAlgorithms.EcdsaSha384Signature + => SecurityAlgorithms.EcdsaSha384, + SecurityAlgorithms.EcdsaSha512 or SecurityAlgorithms.EcdsaSha512Signature + => SecurityAlgorithms.EcdsaSha512, #endif - SecurityAlgorithms.RsaSha256 => SecurityAlgorithms.RsaSha256, - SecurityAlgorithms.RsaSha384 => SecurityAlgorithms.RsaSha384, - SecurityAlgorithms.RsaSha512 => SecurityAlgorithms.RsaSha512, - SecurityAlgorithms.RsaSha256Signature => SecurityAlgorithms.RsaSha256, - SecurityAlgorithms.RsaSha384Signature => SecurityAlgorithms.RsaSha384, - SecurityAlgorithms.RsaSha512Signature => SecurityAlgorithms.RsaSha512, - - SecurityAlgorithms.RsaSsaPssSha256 => SecurityAlgorithms.RsaSsaPssSha256, - SecurityAlgorithms.RsaSsaPssSha384 => SecurityAlgorithms.RsaSsaPssSha384, - SecurityAlgorithms.RsaSsaPssSha512 => SecurityAlgorithms.RsaSsaPssSha512, - SecurityAlgorithms.RsaSsaPssSha256Signature => SecurityAlgorithms.RsaSsaPssSha256, - SecurityAlgorithms.RsaSsaPssSha384Signature => SecurityAlgorithms.RsaSsaPssSha384, - SecurityAlgorithms.RsaSsaPssSha512Signature => SecurityAlgorithms.RsaSsaPssSha512, + SecurityAlgorithms.RsaSha256 or SecurityAlgorithms.RsaSha256Signature + => SecurityAlgorithms.RsaSha256, + SecurityAlgorithms.RsaSha384 or SecurityAlgorithms.RsaSha384Signature + => SecurityAlgorithms.RsaSha384, + SecurityAlgorithms.RsaSha512 or SecurityAlgorithms.RsaSha512Signature + => SecurityAlgorithms.RsaSha512, + + SecurityAlgorithms.RsaSsaPssSha256 or SecurityAlgorithms.RsaSsaPssSha256Signature + => SecurityAlgorithms.RsaSsaPssSha256, + SecurityAlgorithms.RsaSsaPssSha384 or SecurityAlgorithms.RsaSsaPssSha384Signature + => SecurityAlgorithms.RsaSsaPssSha384, + SecurityAlgorithms.RsaSsaPssSha512 or SecurityAlgorithms.RsaSsaPssSha512Signature + => SecurityAlgorithms.RsaSsaPssSha512, _ => null }, diff --git a/src/OpenIddict.Server/OpenIddictServerHandlers.Exchange.cs b/src/OpenIddict.Server/OpenIddictServerHandlers.Exchange.cs index e3c8e451..d80e691a 100644 --- a/src/OpenIddict.Server/OpenIddictServerHandlers.Exchange.cs +++ b/src/OpenIddict.Server/OpenIddictServerHandlers.Exchange.cs @@ -1628,7 +1628,7 @@ namespace OpenIddict.Server // When an explicit scope parameter has been included in the token request, // the authorization server MUST ensure that it doesn't contain scopes - // that were not allowed during the initial authorization/token request. + // that were not granted during the initial authorization/token request. // See https://tools.ietf.org/html/rfc6749#section-6 for more information. else if (!scopes.IsSupersetOf(context.Request.GetScopes())) { diff --git a/src/OpenIddict.Server/OpenIddictServerHandlers.Introspection.cs b/src/OpenIddict.Server/OpenIddictServerHandlers.Introspection.cs index 4bfff7c7..4d821633 100644 --- a/src/OpenIddict.Server/OpenIddictServerHandlers.Introspection.cs +++ b/src/OpenIddict.Server/OpenIddictServerHandlers.Introspection.cs @@ -976,23 +976,16 @@ namespace OpenIddict.Server context.Username = context.Principal.Identity.Name; context.Scopes.UnionWith(context.Principal.GetScopes()); - foreach (var grouping in context.Principal.Claims.GroupBy(claim => claim.Type)) + foreach (var group in context.Principal.Claims.GroupBy(claim => claim.Type)) { // Exclude standard claims, that are already handled via strongly-typed properties. // Make sure to always update this list when adding new built-in claim properties. - var type = grouping.Key; - switch (type) + var type = group.Key; + if (type is Claims.Audience or Claims.ExpiresAt or Claims.IssuedAt or + Claims.Issuer or Claims.NotBefore or Claims.Scope or + Claims.Subject or Claims.TokenType or Claims.TokenUsage) { - case Claims.Audience: - case Claims.ExpiresAt: - case Claims.IssuedAt: - case Claims.Issuer: - case Claims.NotBefore: - case Claims.Scope: - case Claims.Subject: - case Claims.TokenType: - case Claims.TokenUsage: - continue; + continue; } // Exclude OpenIddict-specific metadata claims, that are always considered private. @@ -1001,7 +994,7 @@ namespace OpenIddict.Server continue; } - var claims = grouping.ToList(); + var claims = group.ToList(); context.Claims[type] = claims.Count switch { // When there's only one claim with the same type, directly @@ -1018,12 +1011,12 @@ namespace OpenIddict.Server { ClaimValueTypes.Boolean => bool.Parse(claim.Value), - ClaimValueTypes.Integer => int.Parse(claim.Value, CultureInfo.InvariantCulture), - ClaimValueTypes.Integer32 => int.Parse(claim.Value, CultureInfo.InvariantCulture), + ClaimValueTypes.Integer or ClaimValueTypes.Integer32 + => int.Parse(claim.Value, CultureInfo.InvariantCulture), + ClaimValueTypes.Integer64 => long.Parse(claim.Value, CultureInfo.InvariantCulture), - JsonClaimValueTypes.Json => DeserializeElement(claim.Value), - JsonClaimValueTypes.JsonArray => DeserializeElement(claim.Value), + JsonClaimValueTypes.Json or JsonClaimValueTypes.JsonArray => DeserializeElement(claim.Value), _ => new OpenIddictParameter(claim.Value) }; diff --git a/src/OpenIddict.Server/OpenIddictServerHandlers.cs b/src/OpenIddict.Server/OpenIddictServerHandlers.cs index 2b1fa782..6aa6a4c3 100644 --- a/src/OpenIddict.Server/OpenIddictServerHandlers.cs +++ b/src/OpenIddict.Server/OpenIddictServerHandlers.cs @@ -187,13 +187,13 @@ namespace OpenIddict.Server var (token, type) = context.EndpointType switch { - OpenIddictServerEndpointType.Authorization => (context.Request.IdTokenHint, TokenTypeHints.IdToken), - OpenIddictServerEndpointType.Logout => (context.Request.IdTokenHint, TokenTypeHints.IdToken), + OpenIddictServerEndpointType.Authorization or OpenIddictServerEndpointType.Logout + => (context.Request.IdTokenHint, TokenTypeHints.IdToken), // Tokens received by the introspection and revocation endpoints can be of any type. // Additional token type filtering is made by the endpoint themselves, if needed. - OpenIddictServerEndpointType.Introspection => (context.Request.Token, null), - OpenIddictServerEndpointType.Revocation => (context.Request.Token, null), + OpenIddictServerEndpointType.Introspection or OpenIddictServerEndpointType.Revocation + => (context.Request.Token, null), OpenIddictServerEndpointType.Token when context.Request.IsAuthorizationCodeGrantType() => (context.Request.Code, TokenTypeHints.AuthorizationCode), @@ -407,7 +407,7 @@ namespace OpenIddict.Server { // If no specific token type is expected, accept all token types at this stage. // Additional filtering can be made based on the resolved/actual token type. - var type when string.IsNullOrEmpty(type) => null, + null or { Length: 0 } => null, // For access tokens, both "at+jwt" and "application/at+jwt" are valid. TokenTypeHints.AccessToken => new[] @@ -481,13 +481,15 @@ namespace OpenIddict.Server // Store the token type (resolved from "typ" or "token_usage") as a special private claim. context.Principal.SetTokenType(result.TokenType switch { - var type when string.IsNullOrEmpty(type) => throw new InvalidOperationException(SR.GetResourceString(SR.ID0025)), + null or { Length: 0 } => throw new InvalidOperationException(SR.GetResourceString(SR.ID0025)), - JsonWebTokenTypes.AccessToken => TokenTypeHints.AccessToken, - JsonWebTokenTypes.Prefixes.Application + JsonWebTokenTypes.AccessToken => TokenTypeHints.AccessToken, + // Both at+jwt and application/at+jwt are supported for access tokens. + JsonWebTokenTypes.AccessToken or JsonWebTokenTypes.Prefixes.Application + JsonWebTokenTypes.AccessToken + => TokenTypeHints.AccessToken, - JsonWebTokenTypes.IdentityToken => TokenTypeHints.IdToken, - JsonWebTokenTypes.Prefixes.Application + JsonWebTokenTypes.IdentityToken => TokenTypeHints.IdToken, + // Both JWT and application/JWT are supported for identity tokens. + JsonWebTokenTypes.IdentityToken or JsonWebTokenTypes.Prefixes.Application + JsonWebTokenTypes.IdentityToken + => TokenTypeHints.IdToken, JsonWebTokenTypes.Private.AuthorizationCode => TokenTypeHints.AuthorizationCode, JsonWebTokenTypes.Private.DeviceCode => TokenTypeHints.DeviceCode, @@ -749,8 +751,8 @@ namespace OpenIddict.Server }, description: context.EndpointType switch { - OpenIddictServerEndpointType.Authorization => context.Localizer[SR.ID2009], - OpenIddictServerEndpointType.Logout => context.Localizer[SR.ID2009], + OpenIddictServerEndpointType.Authorization or OpenIddictServerEndpointType.Logout + => context.Localizer[SR.ID2009], OpenIddictServerEndpointType.Token when context.Request.IsAuthorizationCodeGrantType() => context.Localizer[SR.ID2001], @@ -1090,11 +1092,9 @@ namespace OpenIddict.Server Debug.Assert(context.Principal is not null, SR.GetResourceString(SR.ID4006)); // Don't validate the lifetime of id_tokens used as id_token_hints. - switch (context.EndpointType) + if (context.EndpointType is OpenIddictServerEndpointType.Authorization or OpenIddictServerEndpointType.Logout) { - case OpenIddictServerEndpointType.Authorization: - case OpenIddictServerEndpointType.Logout: - return default; + return default; } var date = context.Principal.GetExpirationDate(); @@ -1152,16 +1152,15 @@ namespace OpenIddict.Server throw new ArgumentNullException(nameof(context)); } - return context.EndpointType switch + if (context.EndpointType is not (OpenIddictServerEndpointType.Authorization or + OpenIddictServerEndpointType.Token or + OpenIddictServerEndpointType.Userinfo or + OpenIddictServerEndpointType.Verification)) { - OpenIddictServerEndpointType.Authorization or - OpenIddictServerEndpointType.Token or - OpenIddictServerEndpointType.Userinfo or - OpenIddictServerEndpointType.Verification - => default, + throw new InvalidOperationException(SR.GetResourceString(SR.ID0006)); + } - _ => throw new InvalidOperationException(SR.GetResourceString(SR.ID0006)), - }; + return default; } } @@ -1190,20 +1189,22 @@ namespace OpenIddict.Server context.Response.Error ??= context.EndpointType switch { - OpenIddictServerEndpointType.Authorization => Errors.AccessDenied, - OpenIddictServerEndpointType.Token => Errors.InvalidGrant, - OpenIddictServerEndpointType.Userinfo => Errors.InsufficientAccess, - OpenIddictServerEndpointType.Verification => Errors.AccessDenied, + OpenIddictServerEndpointType.Authorization or OpenIddictServerEndpointType.Verification + => Errors.AccessDenied, + + OpenIddictServerEndpointType.Token => Errors.InvalidGrant, + OpenIddictServerEndpointType.Userinfo => Errors.InsufficientAccess, _ => throw new InvalidOperationException(SR.GetResourceString(SR.ID0006)) }; context.Response.ErrorDescription ??= context.EndpointType switch { - OpenIddictServerEndpointType.Authorization => context.Localizer[SR.ID2015], - OpenIddictServerEndpointType.Verification => context.Localizer[SR.ID2015], - OpenIddictServerEndpointType.Token => context.Localizer[SR.ID2024], - OpenIddictServerEndpointType.Userinfo => context.Localizer[SR.ID2025], + OpenIddictServerEndpointType.Authorization or OpenIddictServerEndpointType.Verification + => context.Localizer[SR.ID2015], + + OpenIddictServerEndpointType.Token => context.Localizer[SR.ID2024], + OpenIddictServerEndpointType.Userinfo => context.Localizer[SR.ID2025], _ => throw new InvalidOperationException(SR.GetResourceString(SR.ID0006)) }; @@ -1356,15 +1357,12 @@ namespace OpenIddict.Server Debug.Assert(context.Principal is not null, SR.GetResourceString(SR.ID4006)); - switch (context.EndpointType) + if (context.EndpointType is not (OpenIddictServerEndpointType.Authorization or + OpenIddictServerEndpointType.Device or + OpenIddictServerEndpointType.Token or + OpenIddictServerEndpointType.Verification)) { - case OpenIddictServerEndpointType.Authorization: - case OpenIddictServerEndpointType.Device: - case OpenIddictServerEndpointType.Token: - case OpenIddictServerEndpointType.Verification: - break; - - default: throw new InvalidOperationException(SR.GetResourceString(SR.ID0010)); + throw new InvalidOperationException(SR.GetResourceString(SR.ID0010)); } if (context.Principal.Identity is null) @@ -2521,16 +2519,12 @@ namespace OpenIddict.Server } // Clone the principal and exclude the private claims mapped to standard JWT claims. - var principal = context.AccessTokenPrincipal.Clone(claim => claim.Type switch - { - Claims.Private.Audience => false, - Claims.Private.CreationDate => false, - Claims.Private.ExpirationDate => false, - Claims.Private.Scope => false, - Claims.Private.TokenType => false, - - _ => true - }); + var principal = context.AccessTokenPrincipal.Clone(claim => claim.Type is not ( + Claims.Private.Audience or + Claims.Private.CreationDate or + Claims.Private.ExpirationDate or + Claims.Private.Scope or + Claims.Private.TokenType)); if (principal is null) { @@ -2786,14 +2780,10 @@ namespace OpenIddict.Server } // Clone the principal and exclude the claim mapped to standard JWT claims. - var principal = context.AuthorizationCodePrincipal.Clone(claim => claim.Type switch - { - Claims.Private.CreationDate => false, - Claims.Private.ExpirationDate => false, - Claims.Private.TokenType => false, - - _ => true - }); + var principal = context.AuthorizationCodePrincipal.Clone(claim => claim.Type is not ( + Claims.Private.CreationDate or + Claims.Private.ExpirationDate or + Claims.Private.TokenType)); if (principal is null) { @@ -3036,14 +3026,10 @@ namespace OpenIddict.Server } // Clone the principal and exclude the claim mapped to standard JWT claims. - var principal = context.DeviceCodePrincipal.Clone(claim => claim.Type switch - { - Claims.Private.CreationDate => false, - Claims.Private.ExpirationDate => false, - Claims.Private.TokenType => false, - - _ => true - }); + var principal = context.DeviceCodePrincipal.Clone(claim => claim.Type is not ( + Claims.Private.CreationDate or + Claims.Private.ExpirationDate or + Claims.Private.TokenType)); if (principal is null) { @@ -3370,14 +3356,10 @@ namespace OpenIddict.Server } // Clone the principal and exclude the claim mapped to standard JWT claims. - var principal = context.RefreshTokenPrincipal.Clone(claim => claim.Type switch - { - Claims.Private.CreationDate => false, - Claims.Private.ExpirationDate => false, - Claims.Private.TokenType => false, - - _ => true - }); + var principal = context.RefreshTokenPrincipal.Clone(claim => claim.Type is not ( + Claims.Private.CreationDate or + Claims.Private.ExpirationDate or + Claims.Private.TokenType)); if (principal is null) { @@ -3657,14 +3639,10 @@ namespace OpenIddict.Server } // Clone the principal and exclude the claim mapped to standard JWT claims. - var principal = context.UserCodePrincipal.Clone(claim => claim.Type switch - { - Claims.Private.CreationDate => false, - Claims.Private.ExpirationDate => false, - Claims.Private.TokenType => false, - - _ => true - }); + var principal = context.UserCodePrincipal.Clone(claim => claim.Type is not ( + Claims.Private.CreationDate or + Claims.Private.ExpirationDate or + Claims.Private.TokenType)); if (principal is null) { @@ -3883,43 +3861,40 @@ namespace OpenIddict.Server { var algorithm = credentials.Digest switch { - SecurityAlgorithms.Sha256 => HashAlgorithmName.SHA256, - SecurityAlgorithms.Sha384 => HashAlgorithmName.SHA384, - SecurityAlgorithms.Sha512 => HashAlgorithmName.SHA512, - SecurityAlgorithms.Sha256Digest => HashAlgorithmName.SHA256, - SecurityAlgorithms.Sha384Digest => HashAlgorithmName.SHA384, - SecurityAlgorithms.Sha512Digest => HashAlgorithmName.SHA512, + SecurityAlgorithms.Sha256 or SecurityAlgorithms.Sha256Digest => HashAlgorithmName.SHA256, + SecurityAlgorithms.Sha384 or SecurityAlgorithms.Sha384Digest => HashAlgorithmName.SHA384, + SecurityAlgorithms.Sha512 or SecurityAlgorithms.Sha512Digest => HashAlgorithmName.SHA512, _ => credentials.Algorithm switch { #if SUPPORTS_ECDSA - SecurityAlgorithms.EcdsaSha256 => HashAlgorithmName.SHA256, - SecurityAlgorithms.EcdsaSha384 => HashAlgorithmName.SHA384, - SecurityAlgorithms.EcdsaSha512 => HashAlgorithmName.SHA512, - SecurityAlgorithms.EcdsaSha256Signature => HashAlgorithmName.SHA256, - SecurityAlgorithms.EcdsaSha384Signature => HashAlgorithmName.SHA384, - SecurityAlgorithms.EcdsaSha512Signature => HashAlgorithmName.SHA512, + SecurityAlgorithms.EcdsaSha256 or SecurityAlgorithms.EcdsaSha256Signature + => HashAlgorithmName.SHA256, + SecurityAlgorithms.EcdsaSha384 or SecurityAlgorithms.EcdsaSha384Signature + => HashAlgorithmName.SHA384, + SecurityAlgorithms.EcdsaSha512 or SecurityAlgorithms.EcdsaSha512Signature + => HashAlgorithmName.SHA512, #endif - SecurityAlgorithms.HmacSha256 => HashAlgorithmName.SHA256, - SecurityAlgorithms.HmacSha384 => HashAlgorithmName.SHA384, - SecurityAlgorithms.HmacSha512 => HashAlgorithmName.SHA512, - SecurityAlgorithms.HmacSha256Signature => HashAlgorithmName.SHA256, - SecurityAlgorithms.HmacSha384Signature => HashAlgorithmName.SHA384, - SecurityAlgorithms.HmacSha512Signature => HashAlgorithmName.SHA512, - - SecurityAlgorithms.RsaSha256 => HashAlgorithmName.SHA256, - SecurityAlgorithms.RsaSha384 => HashAlgorithmName.SHA384, - SecurityAlgorithms.RsaSha512 => HashAlgorithmName.SHA512, - SecurityAlgorithms.RsaSha256Signature => HashAlgorithmName.SHA256, - SecurityAlgorithms.RsaSha384Signature => HashAlgorithmName.SHA384, - SecurityAlgorithms.RsaSha512Signature => HashAlgorithmName.SHA512, - - SecurityAlgorithms.RsaSsaPssSha256 => HashAlgorithmName.SHA256, - SecurityAlgorithms.RsaSsaPssSha384 => HashAlgorithmName.SHA384, - SecurityAlgorithms.RsaSsaPssSha512 => HashAlgorithmName.SHA512, - SecurityAlgorithms.RsaSsaPssSha256Signature => HashAlgorithmName.SHA256, - SecurityAlgorithms.RsaSsaPssSha384Signature => HashAlgorithmName.SHA384, - SecurityAlgorithms.RsaSsaPssSha512Signature => HashAlgorithmName.SHA512, + SecurityAlgorithms.HmacSha256 or SecurityAlgorithms.HmacSha256Signature + => HashAlgorithmName.SHA256, + SecurityAlgorithms.HmacSha384 or SecurityAlgorithms.HmacSha384Signature + => HashAlgorithmName.SHA384, + SecurityAlgorithms.HmacSha512 or SecurityAlgorithms.HmacSha512Signature + => HashAlgorithmName.SHA512, + + SecurityAlgorithms.RsaSha256 or SecurityAlgorithms.RsaSha256Signature + => HashAlgorithmName.SHA256, + SecurityAlgorithms.RsaSha384 or SecurityAlgorithms.RsaSha384Signature + => HashAlgorithmName.SHA384, + SecurityAlgorithms.RsaSha512 or SecurityAlgorithms.RsaSha512Signature + => HashAlgorithmName.SHA512, + + SecurityAlgorithms.RsaSsaPssSha256 or SecurityAlgorithms.RsaSsaPssSha256Signature + => HashAlgorithmName.SHA256, + SecurityAlgorithms.RsaSsaPssSha384 or SecurityAlgorithms.RsaSsaPssSha384Signature + => HashAlgorithmName.SHA384, + SecurityAlgorithms.RsaSsaPssSha512 or SecurityAlgorithms.RsaSsaPssSha512Signature + => HashAlgorithmName.SHA512, _ => throw new InvalidOperationException(SR.GetResourceString(SR.ID0267)) } @@ -4053,15 +4028,11 @@ namespace OpenIddict.Server } // Clone the principal and exclude the claim mapped to standard JWT claims. - var principal = context.IdentityTokenPrincipal.Clone(claim => claim.Type switch - { - Claims.Private.Audience => false, - Claims.Private.CreationDate => false, - Claims.Private.ExpirationDate => false, - Claims.Private.TokenType => false, - - _ => true - }); + var principal = context.IdentityTokenPrincipal.Clone(claim => claim.Type is not ( + Claims.Private.Audience or + Claims.Private.CreationDate or + Claims.Private.ExpirationDate or + Claims.Private.TokenType)); if (principal is null) { diff --git a/src/OpenIddict.Validation.AspNetCore/OpenIddictValidationAspNetCoreHandlers.cs b/src/OpenIddict.Validation.AspNetCore/OpenIddictValidationAspNetCoreHandlers.cs index 1fed4b76..ba56ccd6 100644 --- a/src/OpenIddict.Validation.AspNetCore/OpenIddictValidationAspNetCoreHandlers.cs +++ b/src/OpenIddict.Validation.AspNetCore/OpenIddictValidationAspNetCoreHandlers.cs @@ -372,11 +372,9 @@ namespace OpenIddict.Validation.AspNetCore { null => 200, - Errors.InvalidToken => 401, - Errors.MissingToken => 401, + Errors.InvalidToken or Errors.MissingToken => 401, - Errors.InsufficientAccess => 403, - Errors.InsufficientScope => 403, + Errors.InsufficientAccess or Errors.InsufficientScope => 403, _ => 400 }; @@ -469,10 +467,10 @@ namespace OpenIddict.Validation.AspNetCore var scheme = context.Transaction.Response.Error switch { - Errors.InvalidToken => Schemes.Bearer, - Errors.MissingToken => Schemes.Bearer, - Errors.InsufficientAccess => Schemes.Bearer, - Errors.InsufficientScope => Schemes.Bearer, + Errors.InvalidToken or + Errors.MissingToken or + Errors.InsufficientAccess or + Errors.InsufficientScope => Schemes.Bearer, _ => null }; diff --git a/src/OpenIddict.Validation.Owin/OpenIddictValidationOwinHandlers.cs b/src/OpenIddict.Validation.Owin/OpenIddictValidationOwinHandlers.cs index ae91c96c..c63bbad8 100644 --- a/src/OpenIddict.Validation.Owin/OpenIddictValidationOwinHandlers.cs +++ b/src/OpenIddict.Validation.Owin/OpenIddictValidationOwinHandlers.cs @@ -374,11 +374,9 @@ namespace OpenIddict.Validation.Owin { null => 200, - Errors.InvalidToken => 401, - Errors.MissingToken => 401, + Errors.InvalidToken or Errors.MissingToken => 401, - Errors.InsufficientAccess => 403, - Errors.InsufficientScope => 403, + Errors.InsufficientAccess or Errors.InsufficientScope => 403, _ => 400 }; @@ -476,10 +474,10 @@ namespace OpenIddict.Validation.Owin var scheme = context.Transaction.Response.Error switch { - Errors.InvalidToken => Schemes.Bearer, - Errors.MissingToken => Schemes.Bearer, - Errors.InsufficientAccess => Schemes.Bearer, - Errors.InsufficientScope => Schemes.Bearer, + Errors.InvalidToken or + Errors.MissingToken or + Errors.InsufficientAccess or + Errors.InsufficientScope => Schemes.Bearer, _ => null }; diff --git a/src/OpenIddict.Validation/OpenIddictValidationHandlers.Introspection.cs b/src/OpenIddict.Validation/OpenIddictValidationHandlers.Introspection.cs index 51c63305..96c77331 100644 --- a/src/OpenIddict.Validation/OpenIddictValidationHandlers.Introspection.cs +++ b/src/OpenIddict.Validation/OpenIddictValidationHandlers.Introspection.cs @@ -176,7 +176,7 @@ namespace OpenIddict.Validation foreach (var parameter in context.Response.GetParameters()) { - if (ValidateClaimType(parameter.Key, parameter.Value)) + if (ValidateClaimType(parameter.Key, parameter.Value.Value)) { continue; } @@ -190,66 +190,26 @@ namespace OpenIddict.Validation return default; - static bool ValidateClaimType(string name, OpenIddictParameter value) + static bool ValidateClaimType(string name, object? value) => name switch { - switch ((name, value.Value)) - { - // The 'aud' claim CAN be represented either as a unique string or as an array of multiple strings. - case (Claims.Audience, string _): - case (Claims.Audience, string[] _): - case (Claims.Audience, JsonElement element) when element.ValueKind == JsonValueKind.String || - (element.ValueKind == JsonValueKind.Array && ValidateArrayChildren(element, JsonValueKind.String)): - return true; - - // The 'exp', 'iat' and 'nbf' claims MUST be formatted as numeric date values. - case (Claims.ExpiresAt, long _): - case (Claims.ExpiresAt, JsonElement element) when element.ValueKind == JsonValueKind.Number: - return true; - - case (Claims.IssuedAt, long _): - case (Claims.IssuedAt, JsonElement element) when element.ValueKind == JsonValueKind.Number: - return true; - - case (Claims.NotBefore, long _): - case (Claims.NotBefore, JsonElement element) when element.ValueKind == JsonValueKind.Number: - return true; - - // The 'jti' claim MUST be formatted as a unique string. - case (Claims.JwtId, string _): - case (Claims.JwtId, JsonElement element) when element.ValueKind == JsonValueKind.String: - return true; - - // The 'iss' claim MUST be formatted as a unique string. - case (Claims.Issuer, string _): - case (Claims.Issuer, JsonElement element) when element.ValueKind == JsonValueKind.String: - return true; - - // The 'scope' claim MUST be formatted as a unique string. - case (Claims.Scope, string _): - case (Claims.Scope, JsonElement element) when element.ValueKind == JsonValueKind.String: - return true; - - // The 'token_usage' claim MUST be formatted as a unique string. - case (Claims.TokenUsage, string _): - case (Claims.TokenUsage, JsonElement element) when element.ValueKind == JsonValueKind.String: - return true; - - // If the previously listed claims are represented differently, - // return false to indicate the claims validation logic failed. - case (Claims.Audience, _): - case (Claims.ExpiresAt, _): - case (Claims.IssuedAt, _): - case (Claims.Issuer, _): - case (Claims.NotBefore, _): - case (Claims.JwtId, _): - case (Claims.Scope, _): - case (Claims.TokenUsage, _): - return false; - - // Claims that are not in the well-known list can be of any type. - default: return true; - } - } + // The 'aud' claim MUST be represented either as a unique string or as an array of multiple strings. + Claims.Audience when value is string or string[] => true, + Claims.Audience when value is JsonElement { ValueKind: JsonValueKind.String } => true, + Claims.Audience when value is JsonElement { ValueKind: JsonValueKind.Array } element && + ValidateArrayChildren(element, JsonValueKind.String) => true, + Claims.Audience => false, + + // The 'exp', 'iat' and 'nbf' claims MUST be formatted as numeric date values. + Claims.ExpiresAt or Claims.IssuedAt or Claims.NotBefore + => value is long or JsonElement { ValueKind: JsonValueKind.Number }, + + // The 'jti', 'iss', 'scope' and 'token_usage' claims MUST be formatted as a unique string. + Claims.JwtId or Claims.Issuer or Claims.Scope or Claims.TokenUsage + => value is string or JsonElement { ValueKind: JsonValueKind.String }, + + // Claims that are not in the well-known list can be of any type. + _ => true + }; static bool ValidateArrayChildren(JsonElement element, JsonValueKind kind) { @@ -411,27 +371,26 @@ namespace OpenIddict.Validation continue; } - switch ((name: parameter.Key, value: parameter.Value.Value)) + // Ignore all protocol claims that shouldn't be mapped to CLR claims. + if (parameter.Key is Claims.Active or Claims.Issuer or Claims.NotBefore or + Claims.TokenType or Claims.TokenUsage) { - // Ignore all protocol claims that are not mapped to CLR claims. - case (Claims.Active, _): - case (Claims.Issuer, _): - case (Claims.NotBefore, _): - case (Claims.TokenType, _): - case (Claims.TokenUsage, _): - continue; + continue; + } + switch (parameter.Value.Value) + { // Claims represented as arrays are split and mapped to multiple CLR claims. - case (var name, JsonElement value) when value.ValueKind == JsonValueKind.Array: + case JsonElement { ValueKind: JsonValueKind.Array } value: foreach (var element in value.EnumerateArray()) { - identity.AddClaim(new Claim(name, element.ToString(), + identity.AddClaim(new Claim(parameter.Key, element.ToString(), GetClaimValueType(value.ValueKind), issuer, issuer, identity)); } break; - case (var name, JsonElement value): - identity.AddClaim(new Claim(name, value.ToString(), + case JsonElement value: + identity.AddClaim(new Claim(parameter.Key, value.ToString(), GetClaimValueType(value.ValueKind), issuer, issuer, identity)); break; @@ -440,24 +399,25 @@ namespace OpenIddict.Validation // However, to support responses resolved from custom locations and parameters manually added // by the application using the events model, the CLR primitive types are also supported. - case (var name, bool value): - identity.AddClaim(new Claim(name, value.ToString(), ClaimValueTypes.Boolean, issuer, issuer, identity)); + case bool value: + identity.AddClaim(new Claim(parameter.Key, value.ToString(), + ClaimValueTypes.Boolean, issuer, issuer, identity)); break; - case (var name, long value): - identity.AddClaim(new Claim(name, value.ToString(CultureInfo.InvariantCulture), + case long value: + identity.AddClaim(new Claim(parameter.Key, value.ToString(CultureInfo.InvariantCulture), ClaimValueTypes.Integer64, issuer, issuer, identity)); break; - case (var name, string value): - identity.AddClaim(new Claim(name, value, ClaimValueTypes.String, issuer, issuer, identity)); + case string value: + identity.AddClaim(new Claim(parameter.Key, value, ClaimValueTypes.String, issuer, issuer, identity)); break; // Claims represented as arrays are split and mapped to multiple CLR claims. - case (var name, string[] value): + case string[] value: for (var index = 0; index < value.Length; index++) { - identity.AddClaim(new Claim(name, value[index], ClaimValueTypes.String, issuer, issuer, identity)); + identity.AddClaim(new Claim(parameter.Key, value[index], ClaimValueTypes.String, issuer, issuer, identity)); } break; } @@ -469,15 +429,13 @@ namespace OpenIddict.Validation static string GetClaimValueType(JsonValueKind kind) => kind switch { - JsonValueKind.True => ClaimValueTypes.Boolean, - JsonValueKind.False => ClaimValueTypes.Boolean, + JsonValueKind.True or JsonValueKind.False => ClaimValueTypes.Boolean, + JsonValueKind.String => ClaimValueTypes.String, JsonValueKind.Number => ClaimValueTypes.Integer64, - JsonValueKind.Array => JsonClaimValueTypes.JsonArray, - JsonValueKind.Object => JsonClaimValueTypes.Json, - - _ => JsonClaimValueTypes.Json + JsonValueKind.Array => JsonClaimValueTypes.JsonArray, + JsonValueKind.Object or _ => JsonClaimValueTypes.Json }; } } diff --git a/src/OpenIddict.Validation/OpenIddictValidationHandlers.cs b/src/OpenIddict.Validation/OpenIddictValidationHandlers.cs index d7c3c350..9cbe75a1 100644 --- a/src/OpenIddict.Validation/OpenIddictValidationHandlers.cs +++ b/src/OpenIddict.Validation/OpenIddictValidationHandlers.cs @@ -220,7 +220,7 @@ namespace OpenIddict.Validation { // If no specific token type is expected, accept all token types at this stage. // Additional filtering can be made based on the resolved/actual token type. - var type when string.IsNullOrEmpty(type) => null, + null or { Length: 0 } => null, // For access tokens, both "at+jwt" and "application/at+jwt" are valid. TokenTypeHints.AccessToken => new[] @@ -265,10 +265,11 @@ namespace OpenIddict.Validation // Store the token type (resolved from "typ" or "token_usage") as a special private claim. context.Principal.SetTokenType(result.TokenType switch { - var type when string.IsNullOrEmpty(type) => throw new InvalidOperationException(SR.GetResourceString(SR.ID0025)), + null or { Length: 0 } => throw new InvalidOperationException(SR.GetResourceString(SR.ID0025)), - JsonWebTokenTypes.AccessToken => TokenTypeHints.AccessToken, - JsonWebTokenTypes.Prefixes.Application + JsonWebTokenTypes.AccessToken => TokenTypeHints.AccessToken, + // Both at+jwt and application/at+jwt are supported for access tokens. + JsonWebTokenTypes.AccessToken or JsonWebTokenTypes.Prefixes.Application + JsonWebTokenTypes.AccessToken + => TokenTypeHints.AccessToken, _ => throw new InvalidOperationException(SR.GetResourceString(SR.ID0003)) }); diff --git a/test/OpenIddict.Abstractions.Tests/Primitives/OpenIddictMessageTests.cs b/test/OpenIddict.Abstractions.Tests/Primitives/OpenIddictMessageTests.cs index 008f910b..d192f982 100644 --- a/test/OpenIddict.Abstractions.Tests/Primitives/OpenIddictMessageTests.cs +++ b/test/OpenIddict.Abstractions.Tests/Primitives/OpenIddictMessageTests.cs @@ -18,24 +18,6 @@ namespace OpenIddict.Abstractions.Tests.Primitives { public class OpenIddictMessageTests { - [Theory] - [InlineData(null)] - [InlineData("")] - public void Constructor_ThrowsAnExceptionForNullOrEmptyParameterNames(string name) - { - // Arrange, act and assert - var exception = Assert.Throws(delegate - { - return new OpenIddictMessage(new[] - { - new KeyValuePair(name, "Fabrikam") - }); - }); - - Assert.Equal("name", exception.ParamName); - Assert.StartsWith(SR.GetResourceString(SR.ID0190), exception.Message); - } - [Fact] public void Constructor_ThrowsAnExceptionForInvalidJsonElement() { @@ -79,6 +61,21 @@ namespace OpenIddict.Abstractions.Tests.Primitives Assert.Equal(42, (long) message.GetParameter("parameter")); } + [Theory] + [InlineData(null)] + [InlineData("")] + public void Constructor_IgnoresNullOrEmptyParameterNames(string name) + { + // Arrange and act + var message = new OpenIddictMessage(new[] + { + new KeyValuePair(name, "Fabrikam") + }); + + // Assert + Assert.Equal(0, message.Count); + } + [Fact] public void Constructor_PreservesEmptyParameters() { @@ -94,7 +91,7 @@ namespace OpenIddict.Abstractions.Tests.Primitives } [Fact] - public void Constructor_AllowsDuplicateParameters() + public void Constructor_CombinesDuplicateParameters() { // Arrange and act var message = new OpenIddictMessage(new[]