Browse Source

Validate prompt values specified in authorization requests and update the configuration endpoint to return "prompt_values_supported"

pull/2204/head 6.0.0-preview1
Kévin Chalet 1 year ago
parent
commit
e1f729ba0b
  1. 1
      .editorconfig
  2. 6
      sandbox/OpenIddict.Sandbox.AspNet.Server/Controllers/AuthorizationController.cs
  3. 12
      sandbox/OpenIddict.Sandbox.AspNetCore.Server/Controllers/AuthorizationController.cs
  4. 4
      src/OpenIddict.Abstractions/OpenIddictConstants.cs
  5. 14
      src/OpenIddict.Abstractions/OpenIddictResources.resx
  6. 25
      src/OpenIddict.Server/OpenIddictServerBuilder.cs
  7. 5
      src/OpenIddict.Server/OpenIddictServerEvents.Discovery.cs
  8. 29
      src/OpenIddict.Server/OpenIddictServerHandlers.Authentication.cs
  9. 33
      src/OpenIddict.Server/OpenIddictServerHandlers.Discovery.cs
  10. 13
      src/OpenIddict.Server/OpenIddictServerOptions.cs
  11. 4
      test/OpenIddict.Abstractions.Tests/Primitives/OpenIddictExtensionsTests.cs
  12. 21
      test/OpenIddict.Server.IntegrationTests/OpenIddictServerIntegrationTests.Authentication.cs
  13. 24
      test/OpenIddict.Server.IntegrationTests/OpenIddictServerIntegrationTests.Discovery.cs
  14. 59
      test/OpenIddict.Server.Tests/OpenIddictServerBuilderTests.cs

1
.editorconfig

@ -97,6 +97,7 @@ csharp_using_directive_placement = outside_namespace
dotnet_code_quality_unused_parameters = all dotnet_code_quality_unused_parameters = all
dotnet_diagnostic.CA1510.severity = none dotnet_diagnostic.CA1510.severity = none
dotnet_diagnostic.CA2254.severity = none dotnet_diagnostic.CA2254.severity = none
dotnet_diagnostic.IDE0002.severity = none
dotnet_diagnostic.IDE0305.severity = none dotnet_diagnostic.IDE0305.severity = none
dotnet_naming_rule.interface_should_be_begins_with_i.severity = suggestion dotnet_naming_rule.interface_should_be_begins_with_i.severity = suggestion
dotnet_naming_rule.interface_should_be_begins_with_i.style = begins_with_i dotnet_naming_rule.interface_should_be_begins_with_i.style = begins_with_i

6
sandbox/OpenIddict.Sandbox.AspNet.Server/Controllers/AuthorizationController.cs

@ -139,7 +139,7 @@ public class AuthorizationController : Controller
// return an authorization response without displaying the consent form. // return an authorization response without displaying the consent form.
case ConsentTypes.Implicit: case ConsentTypes.Implicit:
case ConsentTypes.External when authorizations.Count is not 0: case ConsentTypes.External when authorizations.Count is not 0:
case ConsentTypes.Explicit when authorizations.Count is not 0 && !request.HasPrompt(Prompts.Consent): case ConsentTypes.Explicit when authorizations.Count is not 0 && !request.HasPrompt(PromptValues.Consent):
// Create the claims-based identity that will be used by OpenIddict to generate tokens. // Create the claims-based identity that will be used by OpenIddict to generate tokens.
var identity = new ClaimsIdentity( var identity = new ClaimsIdentity(
authenticationType: OpenIddictServerOwinDefaults.AuthenticationType, authenticationType: OpenIddictServerOwinDefaults.AuthenticationType,
@ -178,8 +178,8 @@ public class AuthorizationController : Controller
// At this point, no authorization was found in the database and an error must be returned // At this point, no authorization was found in the database and an error must be returned
// if the client application specified prompt=none in the authorization request. // if the client application specified prompt=none in the authorization request.
case ConsentTypes.Explicit when request.HasPrompt(Prompts.None): case ConsentTypes.Explicit when request.HasPrompt(PromptValues.None):
case ConsentTypes.Systematic when request.HasPrompt(Prompts.None): case ConsentTypes.Systematic when request.HasPrompt(PromptValues.None):
context.Authentication.Challenge( context.Authentication.Challenge(
authenticationTypes: OpenIddictServerOwinDefaults.AuthenticationType, authenticationTypes: OpenIddictServerOwinDefaults.AuthenticationType,
properties: new AuthenticationProperties(new Dictionary<string, string> properties: new AuthenticationProperties(new Dictionary<string, string>

12
sandbox/OpenIddict.Sandbox.AspNetCore.Server/Controllers/AuthorizationController.cs

@ -71,13 +71,13 @@ public class AuthorizationController : Controller
// For scenarios where the default authentication handler configured in the ASP.NET Core // For scenarios where the default authentication handler configured in the ASP.NET Core
// authentication options shouldn't be used, a specific scheme can be specified here. // authentication options shouldn't be used, a specific scheme can be specified here.
var result = await HttpContext.AuthenticateAsync(); var result = await HttpContext.AuthenticateAsync();
if (result == null || !result.Succeeded || request.HasPrompt(Prompts.Login) || if (result == null || !result.Succeeded || request.HasPrompt(PromptValues.Login) ||
(request.MaxAge != null && result.Properties?.IssuedUtc != null && (request.MaxAge != null && result.Properties?.IssuedUtc != null &&
DateTimeOffset.UtcNow - result.Properties.IssuedUtc > TimeSpan.FromSeconds(request.MaxAge.Value))) DateTimeOffset.UtcNow - result.Properties.IssuedUtc > TimeSpan.FromSeconds(request.MaxAge.Value)))
{ {
// If the client application requested promptless authentication, // If the client application requested promptless authentication,
// return an error indicating that the user is not logged in. // return an error indicating that the user is not logged in.
if (request.HasPrompt(Prompts.None)) if (request.HasPrompt(PromptValues.None))
{ {
return Forbid( return Forbid(
authenticationSchemes: OpenIddictServerAspNetCoreDefaults.AuthenticationScheme, authenticationSchemes: OpenIddictServerAspNetCoreDefaults.AuthenticationScheme,
@ -90,7 +90,7 @@ public class AuthorizationController : Controller
// To avoid endless login -> authorization redirects, the prompt=login flag // To avoid endless login -> authorization redirects, the prompt=login flag
// is removed from the authorization request payload before redirecting the user. // is removed from the authorization request payload before redirecting the user.
var prompt = string.Join(" ", request.GetPrompts().Remove(Prompts.Login)); var prompt = string.Join(" ", request.GetPrompts().Remove(PromptValues.Login));
var parameters = Request.HasFormContentType ? var parameters = Request.HasFormContentType ?
Request.Form.Where(parameter => parameter.Key != Parameters.Prompt).ToList() : Request.Form.Where(parameter => parameter.Key != Parameters.Prompt).ToList() :
@ -173,7 +173,7 @@ public class AuthorizationController : Controller
// return an authorization response without displaying the consent form. // return an authorization response without displaying the consent form.
case ConsentTypes.Implicit: case ConsentTypes.Implicit:
case ConsentTypes.External when authorizations.Count is not 0: case ConsentTypes.External when authorizations.Count is not 0:
case ConsentTypes.Explicit when authorizations.Count is not 0 && !request.HasPrompt(Prompts.Consent): case ConsentTypes.Explicit when authorizations.Count is not 0 && !request.HasPrompt(PromptValues.Consent):
// Create the claims-based identity that will be used by OpenIddict to generate tokens. // Create the claims-based identity that will be used by OpenIddict to generate tokens.
var identity = new ClaimsIdentity( var identity = new ClaimsIdentity(
authenticationType: TokenValidationParameters.DefaultAuthenticationType, authenticationType: TokenValidationParameters.DefaultAuthenticationType,
@ -210,8 +210,8 @@ public class AuthorizationController : Controller
// At this point, no authorization was found in the database and an error must be returned // At this point, no authorization was found in the database and an error must be returned
// if the client application specified prompt=none in the authorization request. // if the client application specified prompt=none in the authorization request.
case ConsentTypes.Explicit when request.HasPrompt(Prompts.None): case ConsentTypes.Explicit when request.HasPrompt(PromptValues.None):
case ConsentTypes.Systematic when request.HasPrompt(Prompts.None): case ConsentTypes.Systematic when request.HasPrompt(PromptValues.None):
return Forbid( return Forbid(
authenticationSchemes: OpenIddictServerAspNetCoreDefaults.AuthenticationScheme, authenticationSchemes: OpenIddictServerAspNetCoreDefaults.AuthenticationScheme,
properties: new AuthenticationProperties(new Dictionary<string, string> properties: new AuthenticationProperties(new Dictionary<string, string>

4
src/OpenIddict.Abstractions/OpenIddictConstants.cs

@ -295,6 +295,7 @@ public static class OpenIddictConstants
public const string MtlsEndpointAliases = "mtls_endpoint_aliases"; public const string MtlsEndpointAliases = "mtls_endpoint_aliases";
public const string OpPolicyUri = "op_policy_uri"; public const string OpPolicyUri = "op_policy_uri";
public const string OpTosUri = "op_tos_uri"; public const string OpTosUri = "op_tos_uri";
public const string PromptValuesSupported = "prompt_values_supported";
public const string RequestObjectEncryptionAlgValuesSupported = "request_object_encryption_alg_values_supported"; public const string RequestObjectEncryptionAlgValuesSupported = "request_object_encryption_alg_values_supported";
public const string RequestObjectEncryptionEncValuesSupported = "request_object_encryption_enc_values_supported"; public const string RequestObjectEncryptionEncValuesSupported = "request_object_encryption_enc_values_supported";
public const string RequestObjectSigningAlgValuesSupported = "request_object_signing_alg_values_supported"; public const string RequestObjectSigningAlgValuesSupported = "request_object_signing_alg_values_supported";
@ -430,9 +431,10 @@ public static class OpenIddictConstants
} }
} }
public static class Prompts public static class PromptValues
{ {
public const string Consent = "consent"; public const string Consent = "consent";
public const string Create = "create";
public const string Login = "login"; public const string Login = "login";
public const string None = "none"; public const string None = "none";
public const string SelectAccount = "select_account"; public const string SelectAccount = "select_account";

14
src/OpenIddict.Abstractions/OpenIddictResources.resx

@ -365,12 +365,6 @@ Consider using 'options.AddSigningCredentials(SigningCredentials)' instead.</val
<data name="ID0072" xml:space="preserve"> <data name="ID0072" xml:space="preserve">
<value>Endpoint URIs must be valid URIs.</value> <value>Endpoint URIs must be valid URIs.</value>
</data> </data>
<data name="ID0073" xml:space="preserve">
<value>Claims cannot be null or empty.</value>
</data>
<data name="ID0074" xml:space="preserve">
<value>Scopes cannot be null or empty.</value>
</data>
<data name="ID0075" xml:space="preserve"> <data name="ID0075" xml:space="preserve">
<value>The security token handler cannot be null.</value> <value>The security token handler cannot be null.</value>
</data> </data>
@ -1704,6 +1698,9 @@ To apply post-logout redirection responses, create a class implementing 'IOpenId
<data name="ID0456" xml:space="preserve"> <data name="ID0456" xml:space="preserve">
<value>The specified client authentication method/token binding methods combination is not valid.</value> <value>The specified client authentication method/token binding methods combination is not valid.</value>
</data> </data>
<data name="ID0457" xml:space="preserve">
<value>The '{0}' parameter cannot contain null or empty values.</value>
</data>
<data name="ID2000" xml:space="preserve"> <data name="ID2000" xml:space="preserve">
<value>The security token is missing.</value> <value>The security token is missing.</value>
</data> </data>
@ -2379,7 +2376,7 @@ The principal used to create the token contained the following claims: {Claims}.
<value>The authorization request was rejected because the '{Scope}' scope was missing.</value> <value>The authorization request was rejected because the '{Scope}' scope was missing.</value>
</data> </data>
<data name="ID6040" xml:space="preserve"> <data name="ID6040" xml:space="preserve">
<value>The authorization request was rejected because an invalid prompt parameter was specified.</value> <value>The authorization request was rejected because an invalid prompt combination was specified.</value>
</data> </data>
<data name="ID6041" xml:space="preserve"> <data name="ID6041" xml:space="preserve">
<value>The authorization request was rejected because the specified code challenge method was not supported.</value> <value>The authorization request was rejected because the specified code challenge method was not supported.</value>
@ -2892,6 +2889,9 @@ This may indicate that the hashed entry is corrupted or malformed.</value>
<data name="ID6232" xml:space="preserve"> <data name="ID6232" xml:space="preserve">
<value>An error was returned by ASWebAuthenticationSession while trying to start a sign-out operation.</value> <value>An error was returned by ASWebAuthenticationSession while trying to start a sign-out operation.</value>
</data> </data>
<data name="ID6233" xml:space="preserve">
<value>The authorization request was rejected because an unsupported prompt parameter was specified.</value>
</data>
<data name="ID8000" xml:space="preserve"> <data name="ID8000" xml:space="preserve">
<value>https://documentation.openiddict.com/errors/{0}</value> <value>https://documentation.openiddict.com/errors/{0}</value>
</data> </data>

25
src/OpenIddict.Server/OpenIddictServerBuilder.cs

@ -1634,12 +1634,33 @@ public sealed class OpenIddictServerBuilder
if (Array.Exists(claims, string.IsNullOrEmpty)) if (Array.Exists(claims, string.IsNullOrEmpty))
{ {
throw new ArgumentException(SR.GetResourceString(SR.ID0073), nameof(claims)); throw new ArgumentException(SR.FormatID0457(nameof(claims)), nameof(claims));
} }
return Configure(options => options.Claims.UnionWith(claims)); return Configure(options => options.Claims.UnionWith(claims));
} }
/// <summary>
/// Registers the specified prompt values as supported scopes so
/// they can be returned as part of the discovery document.
/// </summary>
/// <param name="values">The supported prompt values.</param>
/// <returns>The <see cref="OpenIddictServerBuilder"/> instance.</returns>
public OpenIddictServerBuilder RegisterPromptValues(params string[] values)
{
if (values is null)
{
throw new ArgumentNullException(nameof(values));
}
if (Array.Exists(values, string.IsNullOrEmpty))
{
throw new ArgumentException(SR.FormatID0457(nameof(values)), nameof(values));
}
return Configure(options => options.PromptValues.UnionWith(values));
}
/// <summary> /// <summary>
/// Registers the specified scopes as supported scopes so /// Registers the specified scopes as supported scopes so
/// they can be returned as part of the discovery document. /// they can be returned as part of the discovery document.
@ -1655,7 +1676,7 @@ public sealed class OpenIddictServerBuilder
if (Array.Exists(scopes, string.IsNullOrEmpty)) if (Array.Exists(scopes, string.IsNullOrEmpty))
{ {
throw new ArgumentException(SR.GetResourceString(SR.ID0074), nameof(scopes)); throw new ArgumentException(SR.FormatID0457(nameof(scopes)), nameof(scopes));
} }
return Configure(options => options.Scopes.UnionWith(scopes)); return Configure(options => options.Scopes.UnionWith(scopes));

5
src/OpenIddict.Server/OpenIddictServerEvents.Discovery.cs

@ -166,6 +166,11 @@ public static partial class OpenIddictServerEvents
/// </summary> /// </summary>
public HashSet<string> IntrospectionEndpointAuthenticationMethods { get; } = new(StringComparer.Ordinal); public HashSet<string> IntrospectionEndpointAuthenticationMethods { get; } = new(StringComparer.Ordinal);
/// <summary>
/// Gets the list of prompt values supported by the authorization server.
/// </summary>
public HashSet<string> PromptValues { get; } = new(StringComparer.Ordinal);
/// <summary> /// <summary>
/// Gets the list of response modes /// Gets the list of response modes
/// supported by the authorization server. /// supported by the authorization server.

29
src/OpenIddict.Server/OpenIddictServerHandlers.Authentication.cs

@ -875,10 +875,33 @@ public static partial class OpenIddictServerHandlers
throw new ArgumentNullException(nameof(context)); throw new ArgumentNullException(nameof(context));
} }
if (string.IsNullOrEmpty(context.Request.Prompt))
{
return default;
}
// Reject requests specifying an unsupported prompt value.
// See https://openid.net/specs/openid-connect-prompt-create-1_0.html#section-4.1 for more information.
foreach (var value in context.Request.GetPrompts().ToHashSet(StringComparer.Ordinal))
{
if (!context.Options.PromptValues.Contains(value))
{
context.Logger.LogInformation(SR.GetResourceString(SR.ID6233));
context.Reject(
error: Errors.InvalidRequest,
description: SR.FormatID2032(Parameters.Prompt),
uri: SR.FormatID8000(SR.ID2032));
return default;
}
}
// Reject requests specifying prompt=none with consent/login or select_account. // Reject requests specifying prompt=none with consent/login or select_account.
if (context.Request.HasPrompt(Prompts.None) && (context.Request.HasPrompt(Prompts.Consent) || // See https://openid.net/specs/openid-connect-core-1_0.html#AuthRequest for more information.
context.Request.HasPrompt(Prompts.Login) || if (context.Request.HasPrompt(PromptValues.None) && (context.Request.HasPrompt(PromptValues.Consent) ||
context.Request.HasPrompt(Prompts.SelectAccount))) context.Request.HasPrompt(PromptValues.Login) ||
context.Request.HasPrompt(PromptValues.SelectAccount)))
{ {
context.Logger.LogInformation(SR.GetResourceString(SR.ID6040)); context.Logger.LogInformation(SR.GetResourceString(SR.ID6040));

33
src/OpenIddict.Server/OpenIddictServerHandlers.Discovery.cs

@ -41,6 +41,7 @@ public static partial class OpenIddictServerHandlers
AttachScopes.Descriptor, AttachScopes.Descriptor,
AttachClaims.Descriptor, AttachClaims.Descriptor,
AttachSubjectTypes.Descriptor, AttachSubjectTypes.Descriptor,
AttachPromptValues.Descriptor,
AttachSigningAlgorithms.Descriptor, AttachSigningAlgorithms.Descriptor,
AttachAdditionalMetadata.Descriptor, AttachAdditionalMetadata.Descriptor,
@ -250,6 +251,7 @@ public static partial class OpenIddictServerHandlers
[Metadata.IdTokenSigningAlgValuesSupported] = notification.IdTokenSigningAlgorithms.ToArray(), [Metadata.IdTokenSigningAlgValuesSupported] = notification.IdTokenSigningAlgorithms.ToArray(),
[Metadata.CodeChallengeMethodsSupported] = notification.CodeChallengeMethods.ToArray(), [Metadata.CodeChallengeMethodsSupported] = notification.CodeChallengeMethods.ToArray(),
[Metadata.SubjectTypesSupported] = notification.SubjectTypes.ToArray(), [Metadata.SubjectTypesSupported] = notification.SubjectTypes.ToArray(),
[Metadata.PromptValuesSupported] = notification.PromptValues.ToArray(),
[Metadata.TokenEndpointAuthMethodsSupported] = notification.TokenEndpointAuthenticationMethods.ToArray(), [Metadata.TokenEndpointAuthMethodsSupported] = notification.TokenEndpointAuthenticationMethods.ToArray(),
[Metadata.IntrospectionEndpointAuthMethodsSupported] = notification.IntrospectionEndpointAuthenticationMethods.ToArray(), [Metadata.IntrospectionEndpointAuthMethodsSupported] = notification.IntrospectionEndpointAuthenticationMethods.ToArray(),
[Metadata.RevocationEndpointAuthMethodsSupported] = notification.RevocationEndpointAuthenticationMethods.ToArray(), [Metadata.RevocationEndpointAuthMethodsSupported] = notification.RevocationEndpointAuthenticationMethods.ToArray(),
@ -673,6 +675,35 @@ public static partial class OpenIddictServerHandlers
} }
} }
/// <summary>
/// Contains the logic responsible for attaching the supported prompt values to the provider discovery document.
/// </summary>
public sealed class AttachPromptValues : IOpenIddictServerHandler<HandleConfigurationRequestContext>
{
/// <summary>
/// Gets the default descriptor definition assigned to this handler.
/// </summary>
public static OpenIddictServerHandlerDescriptor Descriptor { get; }
= OpenIddictServerHandlerDescriptor.CreateBuilder<HandleConfigurationRequestContext>()
.UseSingletonHandler<AttachPromptValues>()
.SetOrder(AttachSubjectTypes.Descriptor.Order + 1_000)
.SetType(OpenIddictServerHandlerType.BuiltIn)
.Build();
/// <inheritdoc/>
public ValueTask HandleAsync(HandleConfigurationRequestContext context)
{
if (context is null)
{
throw new ArgumentNullException(nameof(context));
}
context.PromptValues.UnionWith(context.Options.PromptValues);
return default;
}
}
/// <summary> /// <summary>
/// Contains the logic responsible for attaching the supported signing algorithms to the provider discovery document. /// Contains the logic responsible for attaching the supported signing algorithms to the provider discovery document.
/// </summary> /// </summary>
@ -684,7 +715,7 @@ public static partial class OpenIddictServerHandlers
public static OpenIddictServerHandlerDescriptor Descriptor { get; } public static OpenIddictServerHandlerDescriptor Descriptor { get; }
= OpenIddictServerHandlerDescriptor.CreateBuilder<HandleConfigurationRequestContext>() = OpenIddictServerHandlerDescriptor.CreateBuilder<HandleConfigurationRequestContext>()
.UseSingletonHandler<AttachSigningAlgorithms>() .UseSingletonHandler<AttachSigningAlgorithms>()
.SetOrder(AttachSubjectTypes.Descriptor.Order + 1_000) .SetOrder(AttachPromptValues.Descriptor.Order + 1_000)
.SetType(OpenIddictServerHandlerType.BuiltIn) .SetType(OpenIddictServerHandlerType.BuiltIn)
.Build(); .Build();

13
src/OpenIddict.Server/OpenIddictServerOptions.cs

@ -375,6 +375,19 @@ public sealed class OpenIddictServerOptions
/// </summary> /// </summary>
public HashSet<string> GrantTypes { get; } = new(StringComparer.Ordinal); public HashSet<string> GrantTypes { get; } = new(StringComparer.Ordinal);
/// <summary>
/// Gets the OpenID Connect prompt values enabled for this application.
/// </summary>
public HashSet<string> PromptValues { get; } = new(StringComparer.Ordinal)
{
// By default, only include the mandatory values defined in the core OpenID Connect specification.
// See https://openid.net/specs/openid-connect-core-1_0.html#AuthRequest for more information.
OpenIddictConstants.PromptValues.Consent,
OpenIddictConstants.PromptValues.Login,
OpenIddictConstants.PromptValues.None,
OpenIddictConstants.PromptValues.SelectAccount
};
/// <summary> /// <summary>
/// Gets or sets a boolean indicating whether PKCE must be used by client applications /// Gets or sets a boolean indicating whether PKCE must be used by client applications
/// when requesting an authorization code (e.g when using the code or hybrid flows). /// when requesting an authorization code (e.g when using the code or hybrid flows).

4
test/OpenIddict.Abstractions.Tests/Primitives/OpenIddictExtensionsTests.cs

@ -225,7 +225,7 @@ public class OpenIddictExtensionsTests
// Act and assert // Act and assert
var exception = Assert.Throws<ArgumentNullException>(() => var exception = Assert.Throws<ArgumentNullException>(() =>
{ {
request.HasPrompt(Prompts.Consent); request.HasPrompt(PromptValues.Consent);
}); });
Assert.Equal("request", exception.ParamName); Assert.Equal("request", exception.ParamName);
@ -277,7 +277,7 @@ public class OpenIddictExtensionsTests
}; };
// Act and assert // Act and assert
Assert.Equal(result, request.HasPrompt(Prompts.Consent)); Assert.Equal(result, request.HasPrompt(PromptValues.Consent));
} }
[Fact] [Fact]

21
test/OpenIddict.Server.IntegrationTests/OpenIddictServerIntegrationTests.Authentication.cs

@ -383,14 +383,17 @@ public abstract partial class OpenIddictServerIntegrationTests
Assert.Equal(SR.FormatID8000(SR.ID2034), response.ErrorUri); Assert.Equal(SR.FormatID8000(SR.ID2034), response.ErrorUri);
} }
[Theory] [Fact]
[InlineData("none consent")] public async Task ValidateAuthorizationRequest_UnsupportedPromptCausesAnError()
[InlineData("none login")]
[InlineData("none select_account")]
public async Task ValidateAuthorizationRequest_InvalidPromptCausesAnError(string prompt)
{ {
// Arrange // Arrange
await using var server = await CreateServerAsync(options => options.EnableDegradedMode()); await using var server = await CreateServerAsync(options =>
{
options.EnableDegradedMode();
options.Configure(options => options.PromptValues.Remove(PromptValues.SelectAccount));
});
await using var client = await server.CreateClientAsync(); await using var client = await server.CreateClientAsync();
// Act // Act
@ -398,7 +401,7 @@ public abstract partial class OpenIddictServerIntegrationTests
{ {
ClientId = "Fabrikam", ClientId = "Fabrikam",
Nonce = "n-0S6_WzA2Mj", Nonce = "n-0S6_WzA2Mj",
Prompt = prompt, Prompt = PromptValues.SelectAccount,
RedirectUri = "http://www.fabrikam.com/path", RedirectUri = "http://www.fabrikam.com/path",
ResponseType = "code id_token token", ResponseType = "code id_token token",
Scope = Scopes.OpenId Scope = Scopes.OpenId
@ -406,8 +409,8 @@ public abstract partial class OpenIddictServerIntegrationTests
// Assert // Assert
Assert.Equal(Errors.InvalidRequest, response.Error); Assert.Equal(Errors.InvalidRequest, response.Error);
Assert.Equal(SR.FormatID2052(Parameters.Prompt), response.ErrorDescription); Assert.Equal(SR.FormatID2032(Parameters.Prompt), response.ErrorDescription);
Assert.Equal(SR.FormatID8000(SR.ID2052), response.ErrorUri); Assert.Equal(SR.FormatID8000(SR.ID2032), response.ErrorUri);
} }
[Theory] [Theory]

24
test/OpenIddict.Server.IntegrationTests/OpenIddictServerIntegrationTests.Discovery.cs

@ -811,6 +811,30 @@ public abstract partial class OpenIddictServerIntegrationTests
Assert.Equal("custom", Assert.Single(types)); Assert.Equal("custom", Assert.Single(types));
} }
[Fact]
public async Task HandleConfigurationRequest_SupportedPromptValuesAreCorrectlyReturned()
{
// Arrange
await using var server = await CreateServerAsync(options =>
{
options.Configure(options => options.PromptValues.Remove(PromptValues.Consent));
options.Configure(options => options.PromptValues.Remove(PromptValues.Login));
options.Configure(options => options.PromptValues.Remove(PromptValues.None));
options.Configure(options => options.PromptValues.Remove(PromptValues.SelectAccount));
options.Configure(options => options.PromptValues.Add("custom"));
});
await using var client = await server.CreateClientAsync();
// Act
var response = await client.GetAsync("/.well-known/openid-configuration");
var types = (string[]?) response[Metadata.PromptValuesSupported];
// Assert
Assert.NotNull(types);
Assert.Equal("custom", Assert.Single(types));
}
[Theory] [Theory]
[InlineData(Algorithms.RsaSha256)] [InlineData(Algorithms.RsaSha256)]
[InlineData(Algorithms.RsaSha384)] [InlineData(Algorithms.RsaSha384)]

59
test/OpenIddict.Server.Tests/OpenIddictServerBuilderTests.cs

@ -2016,18 +2016,62 @@ public class OpenIddictServerBuilderTests
[Theory] [Theory]
[InlineData(null)] [InlineData(null)]
[InlineData("")] [InlineData("")]
public void RegisterClaims_ThrowsAnExceptionForClaim(string claim) public void RegisterClaims_ThrowsAnExceptionForNullOrEmptyClaim(string claim)
{ {
// Arrange // Arrange
var services = CreateServices(); var services = CreateServices();
var builder = CreateBuilder(services); var builder = CreateBuilder(services);
string[] claims = [claim];
// Act and assert // Act and assert
var exception = Assert.Throws<ArgumentException>(() => builder.RegisterClaims(claims)); var exception = Assert.Throws<ArgumentException>(() => builder.RegisterClaims([claim]));
Assert.Equal("claims", exception.ParamName); Assert.Equal("claims", exception.ParamName);
Assert.Contains("Claims cannot be null or empty.", exception.Message); Assert.Contains(SR.FormatID0457("claims"), exception.Message);
}
[Fact]
public void RegisterPromptValues_PromptValuesAreAdded()
{
// Arrange
var services = CreateServices();
var builder = CreateBuilder(services);
// Act
builder.RegisterPromptValues("custom_value_1", "custom_value_2");
var options = GetOptions(services);
// Assert
Assert.Contains("custom_value_1", options.PromptValues);
Assert.Contains("custom_value_2", options.PromptValues);
}
[Fact]
public void RegisterPromptValues_ThrowsAnExceptionForNullPromptValues()
{
// Arrange
var services = CreateServices();
var builder = CreateBuilder(services);
// Act and assert
var exception = Assert.Throws<ArgumentNullException>(() => builder.RegisterPromptValues(values: null!));
Assert.Equal("values", exception.ParamName);
}
[Theory]
[InlineData(null)]
[InlineData("")]
public void RegisterPromptValues_ThrowsAnExceptionForNullOrEmptyValue(string value)
{
// Arrange
var services = CreateServices();
var builder = CreateBuilder(services);
// Act and assert
var exception = Assert.Throws<ArgumentException>(() => builder.RegisterPromptValues([value]));
Assert.Equal("values", exception.ParamName);
Assert.Contains(SR.FormatID0457("values"), exception.Message);
} }
[Fact] [Fact]
@ -2062,18 +2106,17 @@ public class OpenIddictServerBuilderTests
[Theory] [Theory]
[InlineData(null)] [InlineData(null)]
[InlineData("")] [InlineData("")]
public void RegisterScopes_ThrowsAnExceptionForScope(string scope) public void RegisterScopes_ThrowsAnExceptionForNullOrEmptyScope(string scope)
{ {
// Arrange // Arrange
var services = CreateServices(); var services = CreateServices();
var builder = CreateBuilder(services); var builder = CreateBuilder(services);
string[] scopes = [scope];
// Act and assert // Act and assert
var exception = Assert.Throws<ArgumentException>(() => builder.RegisterScopes(scopes)); var exception = Assert.Throws<ArgumentException>(() => builder.RegisterScopes([scope]));
Assert.Equal("scopes", exception.ParamName); Assert.Equal("scopes", exception.ParamName);
Assert.Contains("Scopes cannot be null or empty.", exception.Message); Assert.Contains(SR.FormatID0457("scopes"), exception.Message);
} }
[Fact] [Fact]

Loading…
Cancel
Save