Browse Source

Update OpenIddictMessage to ignore parameters with empty names

pull/1158/head
Kévin Chalet 5 years ago
parent
commit
c44a633567
  1. 42
      src/OpenIddict.Abstractions/Primitives/OpenIddictMessage.cs
  2. 50
      src/OpenIddict.Abstractions/Primitives/OpenIddictParameter.cs
  3. 5
      src/OpenIddict.Abstractions/Primitives/OpenIddictRequest.cs
  4. 5
      src/OpenIddict.Abstractions/Primitives/OpenIddictResponse.cs
  5. 29
      src/OpenIddict.Server.AspNetCore/OpenIddictServerAspNetCoreHandlers.cs
  6. 44
      src/OpenIddict.Server.DataProtection/OpenIddictServerDataProtectionFormatter.cs
  7. 17
      src/OpenIddict.Server.Owin/OpenIddictServerOwinHandlers.cs
  8. 76
      src/OpenIddict.Server/OpenIddictServerHandlers.Discovery.cs
  9. 2
      src/OpenIddict.Server/OpenIddictServerHandlers.Exchange.cs
  10. 29
      src/OpenIddict.Server/OpenIddictServerHandlers.Introspection.cs
  11. 217
      src/OpenIddict.Server/OpenIddictServerHandlers.cs
  12. 14
      src/OpenIddict.Validation.AspNetCore/OpenIddictValidationAspNetCoreHandlers.cs
  13. 14
      src/OpenIddict.Validation.Owin/OpenIddictValidationOwinHandlers.cs
  14. 130
      src/OpenIddict.Validation/OpenIddictValidationHandlers.Introspection.cs
  15. 9
      src/OpenIddict.Validation/OpenIddictValidationHandlers.cs
  16. 35
      test/OpenIddict.Abstractions.Tests/Primitives/OpenIddictMessageTests.cs

42
src/OpenIddict.Abstractions/Primitives/OpenIddictMessage.cs

@ -42,6 +42,7 @@ namespace OpenIddict.Abstractions
/// Initializes a new OpenIddict message.
/// </summary>
/// <param name="parameters">The message parameters.</param>
/// <remarks>Parameters with a null or empty key are always ignored.</remarks>
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.
/// </summary>
/// <param name="parameters">The message parameters.</param>
/// <remarks>Parameters with a null or empty key are always ignored.</remarks>
public OpenIddictMessage(IEnumerable<KeyValuePair<string, OpenIddictParameter>> 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.
/// </summary>
/// <param name="parameters">The message parameters.</param>
/// <remarks>Parameters with a null or empty key are always ignored.</remarks>
public OpenIddictMessage(IEnumerable<KeyValuePair<string, string?>> 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.
/// </summary>
/// <param name="parameters">The message parameters.</param>
/// <remarks>Parameters with a null or empty key are always ignored.</remarks>
public OpenIddictMessage(IEnumerable<KeyValuePair<string, string?[]?>> 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.
/// </summary>
/// <param name="parameters">The message parameters.</param>
/// <remarks>Parameters with a null or empty key are always ignored.</remarks>
public OpenIddictMessage(IEnumerable<KeyValuePair<string, StringValues>> 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

50
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
/// <returns>The converted value.</returns>
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
/// <returns>The converted value.</returns>
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
/// <returns>The converted value.</returns>
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
};

5
src/OpenIddict.Abstractions/Primitives/OpenIddictRequest.cs

@ -36,6 +36,7 @@ namespace OpenIddict.Abstractions
/// Initializes a new OpenIddict request.
/// </summary>
/// <param name="parameters">The request parameters.</param>
/// <remarks>Parameters with a null or empty key are always ignored.</remarks>
public OpenIddictRequest(JsonElement parameters)
: base(parameters)
{
@ -45,6 +46,7 @@ namespace OpenIddict.Abstractions
/// Initializes a new OpenIddict request.
/// </summary>
/// <param name="parameters">The request parameters.</param>
/// <remarks>Parameters with a null or empty key are always ignored.</remarks>
public OpenIddictRequest(IEnumerable<KeyValuePair<string, OpenIddictParameter>> parameters)
: base(parameters)
{
@ -54,6 +56,7 @@ namespace OpenIddict.Abstractions
/// Initializes a new OpenIddict request.
/// </summary>
/// <param name="parameters">The request parameters.</param>
/// <remarks>Parameters with a null or empty key are always ignored.</remarks>
public OpenIddictRequest(IEnumerable<KeyValuePair<string, string?>> parameters)
: base(parameters)
{
@ -63,6 +66,7 @@ namespace OpenIddict.Abstractions
/// Initializes a new OpenIddict request.
/// </summary>
/// <param name="parameters">The request parameters.</param>
/// <remarks>Parameters with a null or empty key are always ignored.</remarks>
public OpenIddictRequest(IEnumerable<KeyValuePair<string, string?[]?>> parameters)
: base(parameters)
{
@ -72,6 +76,7 @@ namespace OpenIddict.Abstractions
/// Initializes a new OpenIddict request.
/// </summary>
/// <param name="parameters">The request parameters.</param>
/// <remarks>Parameters with a null or empty key are always ignored.</remarks>
public OpenIddictRequest(IEnumerable<KeyValuePair<string, StringValues>> parameters)
: base(parameters)
{

5
src/OpenIddict.Abstractions/Primitives/OpenIddictResponse.cs

@ -36,6 +36,7 @@ namespace OpenIddict.Abstractions
/// Initializes a new OpenIddict response.
/// </summary>
/// <param name="parameters">The response parameters.</param>
/// <remarks>Parameters with a null or empty key are always ignored.</remarks>
public OpenIddictResponse(JsonElement parameters)
: base(parameters)
{
@ -45,6 +46,7 @@ namespace OpenIddict.Abstractions
/// Initializes a new OpenIddict response.
/// </summary>
/// <param name="parameters">The response parameters.</param>
/// <remarks>Parameters with a null or empty key are always ignored.</remarks>
public OpenIddictResponse(IEnumerable<KeyValuePair<string, OpenIddictParameter>> parameters)
: base(parameters)
{
@ -54,6 +56,7 @@ namespace OpenIddict.Abstractions
/// Initializes a new OpenIddict response.
/// </summary>
/// <param name="parameters">The response parameters.</param>
/// <remarks>Parameters with a null or empty key are always ignored.</remarks>
public OpenIddictResponse(IEnumerable<KeyValuePair<string, string?>> parameters)
: base(parameters)
{
@ -63,6 +66,7 @@ namespace OpenIddict.Abstractions
/// Initializes a new OpenIddict response.
/// </summary>
/// <param name="parameters">The response parameters.</param>
/// <remarks>Parameters with a null or empty key are always ignored.</remarks>
public OpenIddictResponse(IEnumerable<KeyValuePair<string, string?[]?>> parameters)
: base(parameters)
{
@ -72,6 +76,7 @@ namespace OpenIddict.Abstractions
/// Initializes a new OpenIddict response.
/// </summary>
/// <param name="parameters">The response parameters.</param>
/// <remarks>Parameters with a null or empty key are always ignored.</remarks>
public OpenIddictResponse(IEnumerable<KeyValuePair<string, StringValues>> parameters)
: base(parameters)
{

29
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
};

44
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();

17
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
};

76
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
},

2
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()))
{

29
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)
};

217
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)
{

14
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
};

14
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
};

130
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
};
}
}

9
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))
});

35
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<ArgumentException>(delegate
{
return new OpenIddictMessage(new[]
{
new KeyValuePair<string, OpenIddictParameter>(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<string, OpenIddictParameter>(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[]

Loading…
Cancel
Save