Browse Source

Update the ValidateAsync() methods to throw ArgumentNullException synchronously

pull/1832/head
Kévin Chalet 3 years ago
parent
commit
2fd207b59d
  1. 143
      src/OpenIddict.Core/Managers/OpenIddictApplicationManager.cs
  2. 55
      src/OpenIddict.Core/Managers/OpenIddictAuthorizationManager.cs
  3. 50
      src/OpenIddict.Core/Managers/OpenIddictScopeManager.cs
  4. 52
      src/OpenIddict.Core/Managers/OpenIddictTokenManager.cs

143
src/OpenIddict.Core/Managers/OpenIddictApplicationManager.cs

@ -1133,106 +1133,111 @@ public class OpenIddictApplicationManager<TApplication> : IOpenIddictApplication
/// <param name="application">The application.</param> /// <param name="application">The application.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param> /// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>The validation error encountered when validating the application.</returns> /// <returns>The validation error encountered when validating the application.</returns>
public virtual async IAsyncEnumerable<ValidationResult> ValidateAsync( public virtual IAsyncEnumerable<ValidationResult> ValidateAsync(
TApplication application, [EnumeratorCancellation] CancellationToken cancellationToken = default) TApplication application, CancellationToken cancellationToken = default)
{ {
if (application is null) if (application is null)
{ {
throw new ArgumentNullException(nameof(application)); throw new ArgumentNullException(nameof(application));
} }
// Ensure the client_id is not null or empty and is not already used for a different application. return ExecuteAsync(cancellationToken);
var identifier = await Store.GetClientIdAsync(application, cancellationToken);
if (string.IsNullOrEmpty(identifier))
{
yield return new ValidationResult(SR.GetResourceString(SR.ID2036));
}
else async IAsyncEnumerable<ValidationResult> ExecuteAsync([EnumeratorCancellation] CancellationToken cancellationToken)
{ {
// Note: depending on the database/table/query collation used by the store, an application // Ensure the client_id is not null or empty and is not already used for a different application.
// whose client_id doesn't exactly match the specified value may be returned (e.g because var identifier = await Store.GetClientIdAsync(application, cancellationToken);
// the casing is different). To avoid issues when the client identifier is part of an index if (string.IsNullOrEmpty(identifier))
// using the same collation, an error is added even if the two identifiers don't exactly match.
var other = await Store.FindByClientIdAsync(identifier, cancellationToken);
if (other is not null && !string.Equals(
await Store.GetIdAsync(other, cancellationToken),
await Store.GetIdAsync(application, cancellationToken), StringComparison.Ordinal))
{ {
yield return new ValidationResult(SR.GetResourceString(SR.ID2111)); yield return new ValidationResult(SR.GetResourceString(SR.ID2036));
} }
}
var type = await Store.GetClientTypeAsync(application, cancellationToken);
if (string.IsNullOrEmpty(type))
{
yield return new ValidationResult(SR.GetResourceString(SR.ID2050));
}
else else
{
// Ensure the application type is supported by the manager.
if (!string.Equals(type, ClientTypes.Confidential, StringComparison.OrdinalIgnoreCase) &&
!string.Equals(type, ClientTypes.Public, StringComparison.OrdinalIgnoreCase))
{ {
yield return new ValidationResult(SR.GetResourceString(SR.ID2112)); // Note: depending on the database/table/query collation used by the store, an application
// whose client_id doesn't exactly match the specified value may be returned (e.g because
// the casing is different). To avoid issues when the client identifier is part of an index
// using the same collation, an error is added even if the two identifiers don't exactly match.
var other = await Store.FindByClientIdAsync(identifier, cancellationToken);
if (other is not null && !string.Equals(
await Store.GetIdAsync(other, cancellationToken),
await Store.GetIdAsync(application, cancellationToken), StringComparison.Ordinal))
{
yield return new ValidationResult(SR.GetResourceString(SR.ID2111));
}
} }
// Ensure a client secret was specified if the client is a confidential application. var type = await Store.GetClientTypeAsync(application, cancellationToken);
var secret = await Store.GetClientSecretAsync(application, cancellationToken); if (string.IsNullOrEmpty(type))
if (string.IsNullOrEmpty(secret) && string.Equals(type, ClientTypes.Confidential, StringComparison.OrdinalIgnoreCase))
{ {
yield return new ValidationResult(SR.GetResourceString(SR.ID2113)); yield return new ValidationResult(SR.GetResourceString(SR.ID2050));
} }
// Ensure no client secret was specified if the client is a public application. else
else if (!string.IsNullOrEmpty(secret) && string.Equals(type, ClientTypes.Public, StringComparison.OrdinalIgnoreCase))
{ {
yield return new ValidationResult(SR.GetResourceString(SR.ID2114)); // Ensure the application type is supported by the manager.
} if (!string.Equals(type, ClientTypes.Confidential, StringComparison.OrdinalIgnoreCase) &&
} !string.Equals(type, ClientTypes.Public, StringComparison.OrdinalIgnoreCase))
{
yield return new ValidationResult(SR.GetResourceString(SR.ID2112));
}
// When callback URIs are specified, ensure they are valid and spec-compliant. // Ensure a client secret was specified if the client is a confidential application.
// See https://tools.ietf.org/html/rfc6749#section-3.1 for more information. var secret = await Store.GetClientSecretAsync(application, cancellationToken);
foreach (var uri in ImmutableArray.Create<string>() if (string.IsNullOrEmpty(secret) && string.Equals(type, ClientTypes.Confidential, StringComparison.OrdinalIgnoreCase))
.AddRange(await Store.GetPostLogoutRedirectUrisAsync(application, cancellationToken)) {
.AddRange(await Store.GetRedirectUrisAsync(application, cancellationToken))) yield return new ValidationResult(SR.GetResourceString(SR.ID2113));
{ }
// Ensure the URI is not null or empty.
if (string.IsNullOrEmpty(uri))
{
yield return new ValidationResult(SR.GetResourceString(SR.ID2061));
break; // Ensure no client secret was specified if the client is a public application.
else if (!string.IsNullOrEmpty(secret) && string.Equals(type, ClientTypes.Public, StringComparison.OrdinalIgnoreCase))
{
yield return new ValidationResult(SR.GetResourceString(SR.ID2114));
}
} }
// Ensure the URI is a valid absolute URI. // When callback URIs are specified, ensure they are valid and spec-compliant.
if (!Uri.TryCreate(uri, UriKind.Absolute, out Uri? value) || !value.IsWellFormedOriginalString()) // See https://tools.ietf.org/html/rfc6749#section-3.1 for more information.
foreach (var uri in ImmutableArray.Create<string>()
.AddRange(await Store.GetPostLogoutRedirectUrisAsync(application, cancellationToken))
.AddRange(await Store.GetRedirectUrisAsync(application, cancellationToken)))
{ {
yield return new ValidationResult(SR.GetResourceString(SR.ID2062)); // Ensure the URI is not null or empty.
if (string.IsNullOrEmpty(uri))
{
yield return new ValidationResult(SR.GetResourceString(SR.ID2061));
break; break;
} }
// Ensure the URI doesn't contain a fragment. // Ensure the URI is a valid absolute URI.
if (!string.IsNullOrEmpty(value.Fragment)) if (!Uri.TryCreate(uri, UriKind.Absolute, out Uri? value) || !value.IsWellFormedOriginalString())
{ {
yield return new ValidationResult(SR.GetResourceString(SR.ID2115)); yield return new ValidationResult(SR.GetResourceString(SR.ID2062));
break; break;
} }
// To prevent issuer fixation attacks where a malicious client would specify an "iss" parameter // Ensure the URI doesn't contain a fragment.
// in the callback URI, ensure the query - if present - doesn't include an "iss" parameter. if (!string.IsNullOrEmpty(value.Fragment))
if (!string.IsNullOrEmpty(value.Query))
{
var parameters = OpenIddictHelpers.ParseQuery(value.Query);
if (parameters.ContainsKey(Parameters.Iss))
{ {
yield return new ValidationResult(SR.FormatID2134(Parameters.Iss)); yield return new ValidationResult(SR.GetResourceString(SR.ID2115));
break; break;
} }
// To prevent issuer fixation attacks where a malicious client would specify an "iss" parameter
// in the callback URI, ensure the query - if present - doesn't include an "iss" parameter.
if (!string.IsNullOrEmpty(value.Query))
{
var parameters = OpenIddictHelpers.ParseQuery(value.Query);
if (parameters.ContainsKey(Parameters.Iss))
{
yield return new ValidationResult(SR.FormatID2134(Parameters.Iss));
break;
}
}
} }
} }
} }

55
src/OpenIddict.Core/Managers/OpenIddictAuthorizationManager.cs

@ -1155,46 +1155,51 @@ public class OpenIddictAuthorizationManager<TAuthorization> : IOpenIddictAuthori
/// <param name="authorization">The authorization.</param> /// <param name="authorization">The authorization.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param> /// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>The validation error encountered when validating the authorization.</returns> /// <returns>The validation error encountered when validating the authorization.</returns>
public virtual async IAsyncEnumerable<ValidationResult> ValidateAsync( public virtual IAsyncEnumerable<ValidationResult> ValidateAsync(
TAuthorization authorization, [EnumeratorCancellation] CancellationToken cancellationToken = default) TAuthorization authorization, CancellationToken cancellationToken = default)
{ {
if (authorization is null) if (authorization is null)
{ {
throw new ArgumentNullException(nameof(authorization)); throw new ArgumentNullException(nameof(authorization));
} }
var type = await Store.GetTypeAsync(authorization, cancellationToken); return ExecuteAsync(cancellationToken);
if (string.IsNullOrEmpty(type))
{
yield return new ValidationResult(SR.GetResourceString(SR.ID2116));
}
else if (!string.Equals(type, AuthorizationTypes.AdHoc, StringComparison.OrdinalIgnoreCase) &&
!string.Equals(type, AuthorizationTypes.Permanent, StringComparison.OrdinalIgnoreCase))
{
yield return new ValidationResult(SR.GetResourceString(SR.ID2117));
}
if (string.IsNullOrEmpty(await Store.GetStatusAsync(authorization, cancellationToken))) async IAsyncEnumerable<ValidationResult> ExecuteAsync([EnumeratorCancellation] CancellationToken cancellationToken)
{ {
yield return new ValidationResult(SR.GetResourceString(SR.ID2038)); var type = await Store.GetTypeAsync(authorization, cancellationToken);
} if (string.IsNullOrEmpty(type))
{
yield return new ValidationResult(SR.GetResourceString(SR.ID2116));
}
// Ensure that the scopes are not null or empty and do not contain spaces. else if (!string.Equals(type, AuthorizationTypes.AdHoc, StringComparison.OrdinalIgnoreCase) &&
foreach (var scope in await Store.GetScopesAsync(authorization, cancellationToken)) !string.Equals(type, AuthorizationTypes.Permanent, StringComparison.OrdinalIgnoreCase))
{
if (string.IsNullOrEmpty(scope))
{ {
yield return new ValidationResult(SR.GetResourceString(SR.ID2039)); yield return new ValidationResult(SR.GetResourceString(SR.ID2117));
}
break; if (string.IsNullOrEmpty(await Store.GetStatusAsync(authorization, cancellationToken)))
{
yield return new ValidationResult(SR.GetResourceString(SR.ID2038));
} }
if (scope.Contains(Separators.Space[0])) // Ensure that the scopes are not null or empty and do not contain spaces.
foreach (var scope in await Store.GetScopesAsync(authorization, cancellationToken))
{ {
yield return new ValidationResult(SR.GetResourceString(SR.ID2042)); if (string.IsNullOrEmpty(scope))
{
yield return new ValidationResult(SR.GetResourceString(SR.ID2039));
break; break;
}
if (scope.Contains(Separators.Space[0]))
{
yield return new ValidationResult(SR.GetResourceString(SR.ID2042));
break;
}
} }
} }
} }

50
src/OpenIddict.Core/Managers/OpenIddictScopeManager.cs

@ -918,39 +918,43 @@ public class OpenIddictScopeManager<TScope> : IOpenIddictScopeManager where TSco
/// <param name="scope">The scope.</param> /// <param name="scope">The scope.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param> /// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>The validation error encountered when validating the scope.</returns> /// <returns>The validation error encountered when validating the scope.</returns>
public virtual async IAsyncEnumerable<ValidationResult> ValidateAsync( public virtual IAsyncEnumerable<ValidationResult> ValidateAsync(TScope scope, CancellationToken cancellationToken = default)
TScope scope, [EnumeratorCancellation] CancellationToken cancellationToken = default)
{ {
if (scope is null) if (scope is null)
{ {
throw new ArgumentNullException(nameof(scope)); throw new ArgumentNullException(nameof(scope));
} }
// Ensure the name is not null or empty, does not contain a return ExecuteAsync(cancellationToken);
// space and is not already used for a different scope entity.
var name = await Store.GetNameAsync(scope, cancellationToken);
if (string.IsNullOrEmpty(name))
{
yield return new ValidationResult(SR.GetResourceString(SR.ID2044));
}
else if (name!.Contains(Separators.Space[0])) async IAsyncEnumerable<ValidationResult> ExecuteAsync([EnumeratorCancellation] CancellationToken cancellationToken)
{ {
yield return new ValidationResult(SR.GetResourceString(SR.ID2045)); // Ensure the name is not null or empty, does not contain a
} // space and is not already used for a different scope entity.
var name = await Store.GetNameAsync(scope, cancellationToken);
if (string.IsNullOrEmpty(name))
{
yield return new ValidationResult(SR.GetResourceString(SR.ID2044));
}
else else if (name!.Contains(Separators.Space[0]))
{ {
// Note: depending on the database/table/query collation used by the store, a scope yield return new ValidationResult(SR.GetResourceString(SR.ID2045));
// whose name doesn't exactly match the specified value may be returned (e.g because }
// the casing is different). To avoid issues when the scope name is part of an index
// using the same collation, an error is added even if the two names don't exactly match. else
var other = await Store.FindByNameAsync(name, cancellationToken);
if (other is not null && !string.Equals(
await Store.GetIdAsync(other, cancellationToken),
await Store.GetIdAsync(scope, cancellationToken), StringComparison.Ordinal))
{ {
yield return new ValidationResult(SR.GetResourceString(SR.ID2060)); // Note: depending on the database/table/query collation used by the store, a scope
// whose name doesn't exactly match the specified value may be returned (e.g because
// the casing is different). To avoid issues when the scope name is part of an index
// using the same collation, an error is added even if the two names don't exactly match.
var other = await Store.FindByNameAsync(name, cancellationToken);
if (other is not null && !string.Equals(
await Store.GetIdAsync(other, cancellationToken),
await Store.GetIdAsync(scope, cancellationToken), StringComparison.Ordinal))
{
yield return new ValidationResult(SR.GetResourceString(SR.ID2060));
}
} }
} }
} }

52
src/OpenIddict.Core/Managers/OpenIddictTokenManager.cs

@ -1275,41 +1275,45 @@ public class OpenIddictTokenManager<TToken> : IOpenIddictTokenManager where TTok
/// <param name="token">The token.</param> /// <param name="token">The token.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param> /// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>The validation error encountered when validating the token.</returns> /// <returns>The validation error encountered when validating the token.</returns>
public virtual async IAsyncEnumerable<ValidationResult> ValidateAsync( public virtual IAsyncEnumerable<ValidationResult> ValidateAsync(TToken token, CancellationToken cancellationToken = default)
TToken token, [EnumeratorCancellation] CancellationToken cancellationToken = default)
{ {
if (token is null) if (token is null)
{ {
throw new ArgumentNullException(nameof(token)); throw new ArgumentNullException(nameof(token));
} }
// If a reference identifier was associated with the token, return ExecuteAsync(cancellationToken);
// ensure it's not already used for a different token.
var identifier = await Store.GetReferenceIdAsync(token, cancellationToken); async IAsyncEnumerable<ValidationResult> ExecuteAsync([EnumeratorCancellation] CancellationToken cancellationToken)
if (!string.IsNullOrEmpty(identifier))
{ {
// Note: depending on the database/table/query collation used by the store, a reference token // If a reference identifier was associated with the token,
// whose identifier doesn't exactly match the specified value may be returned (e.g because // ensure it's not already used for a different token.
// the casing is different). To avoid issues when the reference identifier is part of an index var identifier = await Store.GetReferenceIdAsync(token, cancellationToken);
// using the same collation, an error is added even if the two identifiers don't exactly match. if (!string.IsNullOrEmpty(identifier))
var other = await Store.FindByReferenceIdAsync(identifier, cancellationToken);
if (other is not null && !string.Equals(
await Store.GetIdAsync(other, cancellationToken),
await Store.GetIdAsync(token, cancellationToken), StringComparison.Ordinal))
{ {
yield return new ValidationResult(SR.GetResourceString(SR.ID2085)); // Note: depending on the database/table/query collation used by the store, a reference token
// whose identifier doesn't exactly match the specified value may be returned (e.g because
// the casing is different). To avoid issues when the reference identifier is part of an index
// using the same collation, an error is added even if the two identifiers don't exactly match.
var other = await Store.FindByReferenceIdAsync(identifier, cancellationToken);
if (other is not null && !string.Equals(
await Store.GetIdAsync(other, cancellationToken),
await Store.GetIdAsync(token, cancellationToken), StringComparison.Ordinal))
{
yield return new ValidationResult(SR.GetResourceString(SR.ID2085));
}
} }
}
var type = await Store.GetTypeAsync(token, cancellationToken); var type = await Store.GetTypeAsync(token, cancellationToken);
if (string.IsNullOrEmpty(type)) if (string.IsNullOrEmpty(type))
{ {
yield return new ValidationResult(SR.GetResourceString(SR.ID2086)); yield return new ValidationResult(SR.GetResourceString(SR.ID2086));
} }
if (string.IsNullOrEmpty(await Store.GetStatusAsync(token, cancellationToken))) if (string.IsNullOrEmpty(await Store.GetStatusAsync(token, cancellationToken)))
{ {
yield return new ValidationResult(SR.GetResourceString(SR.ID2038)); yield return new ValidationResult(SR.GetResourceString(SR.ID2038));
}
} }
} }

Loading…
Cancel
Save