Browse Source

Move the options validation logic to IValidateOptions<TOptions> implementations

pull/2478/head
Kévin Chalet 2 months ago
parent
commit
83f0bb5c8f
  1. 24
      OpenIddict.slnx
  2. 173
      gen/OpenIddict.Client.WebIntegration.Generators/OpenIddictClientWebIntegrationGenerator.cs
  3. 26
      src/OpenIddict.Abstractions/OpenIddictResources.resx
  4. 115
      src/OpenIddict.Client.AspNetCore/OpenIddictClientAspNetCoreConfiguration.cs
  5. 10
      src/OpenIddict.Client.AspNetCore/OpenIddictClientAspNetCoreExtensions.cs
  6. 10
      src/OpenIddict.Client.DataProtection/OpenIddictClientDataProtectionConfiguration.cs
  7. 42
      src/OpenIddict.Client.Owin/OpenIddictClientOwinConfiguration.cs
  8. 3
      src/OpenIddict.Client.Owin/OpenIddictClientOwinExtensions.cs
  9. 134
      src/OpenIddict.Client.SystemIntegration/OpenIddictClientSystemIntegrationConfiguration.cs
  10. 3
      src/OpenIddict.Client.SystemIntegration/OpenIddictClientSystemIntegrationExtensions.cs
  11. 2
      src/OpenIddict.Client.SystemNetHttp/OpenIddictClientSystemNetHttpConfiguration.cs
  12. 4
      src/OpenIddict.Client.SystemNetHttp/OpenIddictClientSystemNetHttpExtensions.cs
  13. 33
      src/OpenIddict.Client.WebIntegration/OpenIddictClientWebIntegrationConfiguration.cs
  14. 14
      src/OpenIddict.Client.WebIntegration/OpenIddictClientWebIntegrationExtensions.cs
  15. 2
      src/OpenIddict.Client.WebIntegration/OpenIddictClientWebIntegrationProviders.xml
  16. 271
      src/OpenIddict.Client/OpenIddictClientConfiguration.cs
  17. 5
      src/OpenIddict.Client/OpenIddictClientExtensions.cs
  18. 88
      src/OpenIddict.Server.AspNetCore/OpenIddictServerAspNetCoreConfiguration.cs
  19. 9
      src/OpenIddict.Server.AspNetCore/OpenIddictServerAspNetCoreExtensions.cs
  20. 10
      src/OpenIddict.Server.DataProtection/OpenIddictServerDataProtectionConfiguration.cs
  21. 10
      src/OpenIddict.Server.Owin/OpenIddictServerOwinConfiguration.cs
  22. 4
      src/OpenIddict.Server.Owin/OpenIddictServerOwinExtensions.cs
  23. 391
      src/OpenIddict.Server/OpenIddictServerConfiguration.cs
  24. 5
      src/OpenIddict.Server/OpenIddictServerExtensions.cs
  25. 38
      src/OpenIddict.Validation.AspNetCore/OpenIddictValidationAspNetCoreConfiguration.cs
  26. 5
      src/OpenIddict.Validation.AspNetCore/OpenIddictValidationAspNetCoreExtensions.cs
  27. 10
      src/OpenIddict.Validation.DataProtection/OpenIddictValidationDataProtectionConfiguration.cs
  28. 39
      src/OpenIddict.Validation.ServerIntegration/OpenIddictValidationServerIntegrationConfiguration.cs
  29. 2
      src/OpenIddict.Validation.ServerIntegration/OpenIddictValidationServerIntegrationExtensions.cs
  30. 2
      src/OpenIddict.Validation.SystemNetHttp/OpenIddictValidationSystemNetHttpConfiguration.cs
  31. 4
      src/OpenIddict.Validation.SystemNetHttp/OpenIddictValidationSystemNetHttpExtensions.cs
  32. 196
      src/OpenIddict.Validation/OpenIddictValidationConfiguration.cs
  33. 5
      src/OpenIddict.Validation/OpenIddictValidationExtensions.cs
  34. 23
      test/OpenIddict.Client.AspNetCore.Tests/OpenIddict.Client.AspNetCore.Tests.csproj
  35. 22
      test/OpenIddict.Client.DataProtection.Tests/OpenIddict.Client.DataProtection.Tests.csproj
  36. 26
      test/OpenIddict.Client.Owin.Tests/OpenIddict.Client.Owin.Tests.csproj
  37. 34
      test/OpenIddict.Client.SystemIntegration.Tests/OpenIddict.Client.SystemIntegration.Tests.csproj
  38. 26
      test/OpenIddict.Client.Tests/OpenIddict.Client.Tests.csproj
  39. 34
      test/OpenIddict.Client.WebIntegration.Tests/OpenIddict.Client.WebIntegration.Tests.csproj
  40. 23
      test/OpenIddict.Server.AspNetCore.Tests/OpenIddict.Server.AspNetCore.Tests.csproj
  41. 6
      test/OpenIddict.Server.DataProtection.Tests/OpenIddict.Server.DataProtection.Tests.csproj
  42. 10
      test/OpenIddict.Server.IntegrationTests/OpenIddict.Server.IntegrationTests.csproj
  43. 17
      test/OpenIddict.Server.IntegrationTests/OpenIddictServerIntegrationTests.Discovery.cs
  44. 26
      test/OpenIddict.Server.Owin.Tests/OpenIddict.Server.Owin.Tests.csproj
  45. 12
      test/OpenIddict.Server.Tests/OpenIddict.Server.Tests.csproj
  46. 23
      test/OpenIddict.Validation.AspNetCore.Tests/OpenIddict.Validation.AspNetCore.Tests.csproj
  47. 22
      test/OpenIddict.Validation.DataProtection.Tests/OpenIddict.Validation.DataProtection.Tests.csproj
  48. 8
      test/OpenIddict.Validation.IntegrationTests/OpenIddict.Validation.IntegrationTests.csproj
  49. 5
      test/OpenIddict.Validation.IntegrationTests/OpenIddictValidationIntegrationTests.cs
  50. 26
      test/OpenIddict.Validation.Owin.Tests/OpenIddict.Validation.Owin.Tests.csproj
  51. 26
      test/OpenIddict.Validation.Tests/OpenIddict.Validation.Tests.csproj

24
OpenIddict.slnx

@ -49,55 +49,67 @@
</Folder>
<Folder Name="/src/">
<Project Path="src/OpenIddict/OpenIddict.csproj" />
<Project Path="src/OpenIddict.Abstractions/OpenIddict.Abstractions.csproj" />
<Project Path="src/OpenIddict.AspNetCore/OpenIddict.AspNetCore.csproj" />
<Project Path="src/OpenIddict.Client/OpenIddict.Client.csproj" />
<Project Path="src/OpenIddict.Client.AspNetCore/OpenIddict.Client.AspNetCore.csproj" />
<Project Path="src/OpenIddict.Client.DataProtection/OpenIddict.Client.DataProtection.csproj" />
<Project Path="src/OpenIddict.Client.Owin/OpenIddict.Client.Owin.csproj" />
<Project Path="src/OpenIddict.Client.SystemIntegration/OpenIddict.Client.SystemIntegration.csproj" />
<Project Path="src/OpenIddict.Client.SystemNetHttp/OpenIddict.Client.SystemNetHttp.csproj" />
<Project Path="src/OpenIddict.Client.WebIntegration/OpenIddict.Client.WebIntegration.csproj" />
<Project Path="src/OpenIddict.Client/OpenIddict.Client.csproj" />
<Project Path="src/OpenIddict.Core/OpenIddict.Core.csproj" />
<Project Path="src/OpenIddict.EntityFramework/OpenIddict.EntityFramework.csproj" />
<Project Path="src/OpenIddict.EntityFramework.Models/OpenIddict.EntityFramework.Models.csproj" />
<Project Path="src/OpenIddict.EntityFrameworkCore/OpenIddict.EntityFrameworkCore.csproj" />
<Project Path="src/OpenIddict.EntityFramework/OpenIddict.EntityFramework.csproj" />
<Project Path="src/OpenIddict.EntityFrameworkCore.Models/OpenIddict.EntityFrameworkCore.Models.csproj" />
<Project Path="src/OpenIddict.EntityFrameworkCore/OpenIddict.EntityFrameworkCore.csproj" />
<Project Path="src/OpenIddict.MongoDb.Models/OpenIddict.MongoDb.Models.csproj" />
<Project Path="src/OpenIddict.MongoDb/OpenIddict.MongoDb.csproj" />
<Project Path="src/OpenIddict.Owin/OpenIddict.Owin.csproj" />
<Project Path="src/OpenIddict.Quartz/OpenIddict.Quartz.csproj" />
<Project Path="src/OpenIddict.Server/OpenIddict.Server.csproj" />
<Project Path="src/OpenIddict.Server.AspNetCore/OpenIddict.Server.AspNetCore.csproj" />
<Project Path="src/OpenIddict.Server.DataProtection/OpenIddict.Server.DataProtection.csproj" />
<Project Path="src/OpenIddict.Server.Owin/OpenIddict.Server.Owin.csproj" />
<Project Path="src/OpenIddict.Validation/OpenIddict.Validation.csproj" />
<Project Path="src/OpenIddict.Server/OpenIddict.Server.csproj" />
<Project Path="src/OpenIddict.Validation.AspNetCore/OpenIddict.Validation.AspNetCore.csproj" />
<Project Path="src/OpenIddict.Validation.DataProtection/OpenIddict.Validation.DataProtection.csproj" />
<Project Path="src/OpenIddict.Validation.Owin/OpenIddict.Validation.Owin.csproj" />
<Project Path="src/OpenIddict.Validation.ServerIntegration/OpenIddict.Validation.ServerIntegration.csproj" />
<Project Path="src/OpenIddict.Validation.SystemNetHttp/OpenIddict.Validation.SystemNetHttp.csproj" />
<Project Path="src/OpenIddict.Validation/OpenIddict.Validation.csproj" />
<Project Path="src/OpenIddict/OpenIddict.csproj" />
</Folder>
<Folder Name="/test/">
<Project Path="test/OpenIddict.Abstractions.Tests/OpenIddict.Abstractions.Tests.csproj" />
<Project Path="test/OpenIddict.Client.AspNetCore.IntegrationTests/OpenIddict.Client.AspNetCore.IntegrationTests.csproj" />
<Project Path="test/OpenIddict.Client.AspNetCore.Tests/OpenIddict.Client.AspNetCore.Tests.csproj" Id="e01de82f-c08a-4346-9106-906da82c01fc" />
<Project Path="test/OpenIddict.Client.DataProtection.Tests/OpenIddict.Client.DataProtection.Tests.csproj" Id="9106b798-291d-4360-9e06-08f9abc5a9de" />
<Project Path="test/OpenIddict.Client.IntegrationTests/OpenIddict.Client.IntegrationTests.csproj" />
<Project Path="test/OpenIddict.Client.Owin.IntegrationTests/OpenIddict.Client.Owin.IntegrationTests.csproj" />
<Project Path="test/OpenIddict.Client.Owin.Tests/OpenIddict.Client.Owin.Tests.csproj" Id="d57775bf-7b38-48f5-b15c-d48916de9166" />
<Project Path="test/OpenIddict.Client.SystemIntegration.Tests/OpenIddict.Client.SystemIntegration.Tests.csproj" Id="0d37c331-021f-4158-89e3-139c00731497" />
<Project Path="test/OpenIddict.Client.Tests/OpenIddict.Client.Tests.csproj" Id="69998197-05ae-4259-adbf-d98fc9a51d88" />
<Project Path="test/OpenIddict.Client.WebIntegration.Tests/OpenIddict.Client.WebIntegration.Tests.csproj" Id="9a65c61e-f5ac-40da-9703-a8c2d6479185" />
<Project Path="test/OpenIddict.Core.Tests/OpenIddict.Core.Tests.csproj" />
<Project Path="test/OpenIddict.EntityFramework.Tests/OpenIddict.EntityFramework.Tests.csproj" />
<Project Path="test/OpenIddict.EntityFrameworkCore.Tests/OpenIddict.EntityFrameworkCore.Tests.csproj" />
<Project Path="test/OpenIddict.MongoDb.Tests/OpenIddict.MongoDb.Tests.csproj" />
<Project Path="test/OpenIddict.Quartz.Tests/OpenIddict.Quartz.Tests.csproj" />
<Project Path="test/OpenIddict.Server.AspNetCore.IntegrationTests/OpenIddict.Server.AspNetCore.IntegrationTests.csproj" />
<Project Path="test/OpenIddict.Server.AspNetCore.Tests/OpenIddict.Server.AspNetCore.Tests.csproj" Id="21d81c43-35e2-4a09-8866-448b86d3c710" />
<Project Path="test/OpenIddict.Server.DataProtection.Tests/OpenIddict.Server.DataProtection.Tests.csproj" />
<Project Path="test/OpenIddict.Server.IntegrationTests/OpenIddict.Server.IntegrationTests.csproj" />
<Project Path="test/OpenIddict.Server.Owin.IntegrationTests/OpenIddict.Server.Owin.IntegrationTests.csproj" />
<Project Path="test/OpenIddict.Server.Owin.Tests/OpenIddict.Server.Owin.Tests.csproj" Id="a6908da1-4188-4f8a-808b-b465b4fa496a" />
<Project Path="test/OpenIddict.Server.Tests/OpenIddict.Server.Tests.csproj" />
<Project Path="test/OpenIddict.Validation.AspNetCore.IntegrationTests/OpenIddict.Validation.AspNetCore.IntegrationTests.csproj" />
<Project Path="test/OpenIddict.Validation.AspNetCore.Tests/OpenIddict.Validation.AspNetCore.Tests.csproj" Id="3afbc55d-c26a-4972-b2dd-a62d938b92ac" />
<Project Path="test/OpenIddict.Validation.DataProtection.Tests/OpenIddict.Validation.DataProtection.Tests.csproj" Id="3a5eb5a1-66ad-420e-a829-2d6e197ee254" />
<Project Path="test/OpenIddict.Validation.IntegrationTests/OpenIddict.Validation.IntegrationTests.csproj" />
<Project Path="test/OpenIddict.Validation.Owin.IntegrationTests/OpenIddict.Validation.Owin.IntegrationTests.csproj" />
<Project Path="test/OpenIddict.Validation.Owin.Tests/OpenIddict.Validation.Owin.Tests.csproj" Id="6c6f2b51-8319-4c93-bf80-36db6a7ce9eb" />
<Project Path="test/OpenIddict.Validation.Tests/OpenIddict.Validation.Tests.csproj" Id="8a9f3177-84c8-4098-bbe2-f9fdda866c43" />
</Folder>
<Folder Name="/workflows/">

173
gen/OpenIddict.Client.WebIntegration.Generators/OpenIddictClientWebIntegrationGenerator.cs

@ -908,6 +908,7 @@ public static partial class OpenIddictClientWebIntegrationConstants
var template = Template.Parse(@"#nullable enable
#pragma warning disable CS0618
using System.ComponentModel.DataAnnotations;
using System.Text.RegularExpressions;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;
@ -926,7 +927,7 @@ public sealed partial class OpenIddictClientWebIntegrationConfiguration
ArgumentNullException.ThrowIfNull(registration);
{{~ for provider in providers ~}}
{{~ if for.index == 0 ~}}
{{~ if for.first ~}}
if (registration.ProviderType is ProviderTypes.{{ provider.name }})
{{~ else ~}}
else if (registration.ProviderType is ProviderTypes.{{ provider.name }})
@ -934,7 +935,7 @@ public sealed partial class OpenIddictClientWebIntegrationConfiguration
{
if (registration.ProviderSettings is not OpenIddictClientWebIntegrationSettings.{{ provider.name }} settings)
{
throw new InvalidOperationException(SR.GetResourceString(SR.ID0406));
return;
}
{{~ for setting in provider.settings ~}}
@ -947,7 +948,7 @@ public sealed partial class OpenIddictClientWebIntegrationConfiguration
{{~ else if setting.type == 'Uri' ~}}
if (settings.{{ setting.property_name }} is null)
{
settings.{{ setting.property_name }} = new Uri(""{{ setting.default_value }}"", UriKind.RelativeOrAbsolute);
settings.{{ setting.property_name }} = CreateUri(""{{ setting.default_value }}"", UriKind.RelativeOrAbsolute);
}
{{~ else if setting.type == 'Boolean' ~}}
if (settings.{{ setting.property_name }} is null)
@ -995,26 +996,6 @@ public sealed partial class OpenIddictClientWebIntegrationConfiguration
}
{{~ end ~}}
{{~ for setting in provider.settings ~}}
{{~ if setting.required ~}}
{{~ if setting.type == 'String' ~}}
if (string.IsNullOrEmpty(settings.{{ setting.property_name }}))
{{~ else ~}}
if (settings.{{ setting.property_name }} is null)
{{~ end ~}}
{
throw new InvalidOperationException(SR.FormatID0332(nameof(settings.{{ setting.property_name }}), Providers.{{ provider.name }}));
}
{{~ end ~}}
{{~ if setting.type == 'Uri' ~}}
if (!settings.{{ setting.property_name }}.IsAbsoluteUri || OpenIddictHelpers.IsImplicitFileUri(settings.{{ setting.property_name }}))
{
throw new InvalidOperationException(SR.FormatID0350(nameof(settings.{{ setting.property_name }}), Providers.{{ provider.name }}));
}
{{~ end ~}}
{{~ end ~}}
registration.ProviderName ??= Providers.{{ provider.name }};
registration.ProviderDisplayName ??= ""{{ provider.display_name }}"";
@ -1022,10 +1003,10 @@ public sealed partial class OpenIddictClientWebIntegrationConfiguration
{
{{~ for environment in provider.environments ~}}
OpenIddictClientWebIntegrationConstants.{{ provider.name }}.Environments.{{ environment.name }}
=> new Uri($""{{ environment.issuer | string.replace '\'' '""' }}"", UriKind.Absolute),
=> CreateUri($""{{ environment.issuer | string.replace '\'' '""' }}"", UriKind.Absolute),
{{~ end ~}}
_ => throw new InvalidOperationException(SR.FormatID0194(nameof(settings.Environment)))
_ => null
};
registration.ConfigurationEndpoint ??= settings.Environment switch
@ -1033,13 +1014,13 @@ public sealed partial class OpenIddictClientWebIntegrationConfiguration
{{~ for environment in provider.environments ~}}
OpenIddictClientWebIntegrationConstants.{{ provider.name }}.Environments.{{ environment.name }}
{{~ if environment.configuration_endpoint ~}}
=> new Uri($""{{ environment.configuration_endpoint | string.replace '\'' '""' }}"", UriKind.Absolute),
=> CreateUri($""{{ environment.configuration_endpoint | string.replace '\'' '""' }}"", UriKind.Absolute),
{{~ else ~}}
=> null,
{{~ end ~}}
{{~ end ~}}
_ => throw new InvalidOperationException(SR.FormatID0194(nameof(settings.Environment)))
_ => null
};
registration.Configuration ??= settings.Environment switch
@ -1049,27 +1030,27 @@ public sealed partial class OpenIddictClientWebIntegrationConfiguration
OpenIddictClientWebIntegrationConstants.{{ provider.name }}.Environments.{{ environment.name }} => new OpenIddictConfiguration
{
{{~ if environment.configuration.authorization_endpoint ~}}
AuthorizationEndpoint = new Uri($""{{ environment.configuration.authorization_endpoint | string.replace '\'' '""' }}"", UriKind.Absolute),
AuthorizationEndpoint = CreateUri($""{{ environment.configuration.authorization_endpoint | string.replace '\'' '""' }}"", UriKind.Absolute),
{{~ end ~}}
{{~ if environment.configuration.device_authorization_endpoint ~}}
DeviceAuthorizationEndpoint = new Uri($""{{ environment.configuration.device_authorization_endpoint | string.replace '\'' '""' }}"", UriKind.Absolute),
DeviceAuthorizationEndpoint = CreateUri($""{{ environment.configuration.device_authorization_endpoint | string.replace '\'' '""' }}"", UriKind.Absolute),
{{~ end ~}}
{{~ if environment.configuration.introspection_endpoint ~}}
IntrospectionEndpoint = new Uri($""{{ environment.configuration.introspection_endpoint | string.replace '\'' '""' }}"", UriKind.Absolute),
IntrospectionEndpoint = CreateUri($""{{ environment.configuration.introspection_endpoint | string.replace '\'' '""' }}"", UriKind.Absolute),
{{~ end ~}}
{{~ if environment.configuration.revocation_endpoint ~}}
RevocationEndpoint = new Uri($""{{ environment.configuration.revocation_endpoint | string.replace '\'' '""' }}"", UriKind.Absolute),
RevocationEndpoint = CreateUri($""{{ environment.configuration.revocation_endpoint | string.replace '\'' '""' }}"", UriKind.Absolute),
{{~ end ~}}
{{~ if environment.configuration.token_endpoint ~}}
TokenEndpoint = new Uri($""{{ environment.configuration.token_endpoint | string.replace '\'' '""' }}"", UriKind.Absolute),
TokenEndpoint = CreateUri($""{{ environment.configuration.token_endpoint | string.replace '\'' '""' }}"", UriKind.Absolute),
{{~ end ~}}
{{~ if environment.configuration.user_info_endpoint ~}}
UserInfoEndpoint = new Uri($""{{ environment.configuration.user_info_endpoint | string.replace '\'' '""' }}"", UriKind.Absolute),
UserInfoEndpoint = CreateUri($""{{ environment.configuration.user_info_endpoint | string.replace '\'' '""' }}"", UriKind.Absolute),
{{~ end ~}}
CodeChallengeMethodsSupported =
@ -1140,7 +1121,7 @@ public sealed partial class OpenIddictClientWebIntegrationConfiguration
{{~ end ~}}
{{~ end ~}}
_ => throw new InvalidOperationException(SR.FormatID0194(nameof(settings.Environment)))
_ => null
};
{{~ for setting in provider.settings ~}}
@ -1182,22 +1163,11 @@ public sealed partial class OpenIddictClientWebIntegrationConfiguration
{
registration.SigningCredentials.Add(new SigningCredentials(key, SecurityAlgorithms.EcdsaSha512));
}
else
{
throw new InvalidOperationException(SR.GetResourceString(SR.ID0068));
}
}
{{~ end ~}}
{{~ if setting.type == 'SigningKey' ~}}
if (settings.{{ setting.property_name }} is not null)
if (settings.{{ setting.property_name }} is not null and not AsymmetricSecurityKey { PrivateKeyStatus: PrivateKeyStatus.DoesNotExist })
{
// If the signing key is an asymmetric security key, ensure it has a private key.
if (settings.{{ setting.property_name }} is AsymmetricSecurityKey { PrivateKeyStatus: PrivateKeyStatus.DoesNotExist })
{
throw new InvalidOperationException(SR.GetResourceString(SR.ID0067));
}
{{~ if setting.signing_algorithm ~}}
registration.SigningCredentials.Add(new SigningCredentials(settings.{{ setting.property_name }}, ""{{ setting.signing_algorithm }}""));
{{~ else ~}}
@ -1226,11 +1196,6 @@ public sealed partial class OpenIddictClientWebIntegrationConfiguration
{
registration.SigningCredentials.Add(new SigningCredentials(settings.{{ setting.property_name }}, SecurityAlgorithms.EcdsaSha512));
}
else
{
throw new InvalidOperationException(SR.GetResourceString(SR.ID0068));
}
{{~ end ~}}
}
{{~ end ~}}
@ -1239,9 +1204,111 @@ public sealed partial class OpenIddictClientWebIntegrationConfiguration
{{~ end ~}}
else
static Uri? CreateUri(string? value, UriKind kind) => Uri.TryCreate(value, kind, out Uri? uri) ? uri : null;
}
public static partial IEnumerable<ValidationResult> ValidateProvider(OpenIddictClientRegistration registration)
{
ArgumentNullException.ThrowIfNull(registration);
return Execute();
IEnumerable<ValidationResult> Execute()
{
throw new InvalidOperationException(SR.GetResourceString(SR.ID0407));
{{~ for provider in providers ~}}
{{~ if for.index == 0 ~}}
if (registration.ProviderType is ProviderTypes.{{ provider.name }})
{{~ else ~}}
else if (registration.ProviderType is ProviderTypes.{{ provider.name }})
{{~ end ~}}
{
if (registration.ProviderSettings is not OpenIddictClientWebIntegrationSettings.{{ provider.name }} settings)
{
yield return new ValidationResult(SR.GetResourceString(SR.ID0406));
// Note: if the settings instance is missing or of the wrong type, abort any further validation.
yield break;
}
// Ensure the specified environment is valid.
if (settings.Environment is not (
{{~ for environment in provider.environments ~}}
{{~ if !for.last ~}}
OpenIddictClientWebIntegrationConstants.{{ provider.name }}.Environments.{{ environment.name }} or
{{~ else ~}}
OpenIddictClientWebIntegrationConstants.{{ provider.name }}.Environments.{{ environment.name }}
{{~ end ~}}
{{~ end ~}}
))
{
yield return new ValidationResult(SR.FormatID0194(nameof(settings.Environment)));
}
{{~ for setting in provider.settings ~}}
{{~ if setting.required ~}}
{{~ if setting.type == 'String' ~}}
if (string.IsNullOrEmpty(settings.{{ setting.property_name }}))
{{~ else ~}}
if (settings.{{ setting.property_name }} is null)
{{~ end ~}}
{
yield return new ValidationResult(SR.FormatID0332(nameof(settings.{{ setting.property_name }}), Providers.{{ provider.name }}));
}
{{~ end ~}}
{{~ if setting.type == 'Uri' ~}}
else if (!settings.{{ setting.property_name }}.IsAbsoluteUri || OpenIddictHelpers.IsImplicitFileUri(settings.{{ setting.property_name }}))
{
yield return new ValidationResult(SR.FormatID0350(nameof(settings.{{ setting.property_name }}), Providers.{{ provider.name }}));
}
{{~ end ~}}
{{~ end ~}}
{{~ for setting in provider.settings ~}}
{{~ if setting.type == 'SigningCertificate' ~}}
if (settings.{{ setting.property_name }} is not null)
{
var key = new X509SecurityKey(settings.{{ setting.property_name }});
if (!key.IsSupportedAlgorithm(SecurityAlgorithms.RsaSha256) &&
!key.IsSupportedAlgorithm(SecurityAlgorithms.HmacSha256) &&
!key.IsSupportedAlgorithm(SecurityAlgorithms.EcdsaSha256) &&
!key.IsSupportedAlgorithm(SecurityAlgorithms.EcdsaSha384) &&
!key.IsSupportedAlgorithm(SecurityAlgorithms.EcdsaSha512))
{
yield return new ValidationResult(SR.GetResourceString(SR.ID0068));
}
}
{{~ end ~}}
{{~ if setting.type == 'SigningKey' ~}}
if (settings.{{ setting.property_name }} is not null)
{
// If the signing key is an asymmetric security key, ensure it has a private key.
if (settings.{{ setting.property_name }} is AsymmetricSecurityKey { PrivateKeyStatus: PrivateKeyStatus.DoesNotExist })
{
yield return new ValidationResult(SR.GetResourceString(SR.ID0067));
}
{{~ if !setting.signing_algorithm ~}}
if (!settings.{{ setting.property_name }}.IsSupportedAlgorithm(SecurityAlgorithms.RsaSha256) &&
!settings.{{ setting.property_name }}.IsSupportedAlgorithm(SecurityAlgorithms.HmacSha256) &&
!settings.{{ setting.property_name }}.IsSupportedAlgorithm(SecurityAlgorithms.EcdsaSha256) &&
!settings.{{ setting.property_name }}.IsSupportedAlgorithm(SecurityAlgorithms.EcdsaSha384) &&
!settings.{{ setting.property_name }}.IsSupportedAlgorithm(SecurityAlgorithms.EcdsaSha512))
{
yield return new ValidationResult(SR.GetResourceString(SR.ID0068));
}
{{~ end ~}}
}
{{~ end ~}}
{{~ end ~}}
}
{{~ end ~}}
else
{
yield return new ValidationResult(SR.GetResourceString(SR.ID0407));
}
}
}
}

26
src/OpenIddict.Abstractions/OpenIddictResources.resx

@ -472,11 +472,10 @@ This may indicate that it was not properly registered in the dependency injectio
<value>The realm cannot be null or empty.</value>
</data>
<data name="ID0108" xml:space="preserve">
<value>The OpenIddict ASP.NET Core server handler cannot be registered as an authentication scheme.
This may indicate that an instance of another handler was registered with the same scheme.</value>
<value>The '{0}' authentication scheme already exists and cannot be registered as an authentication scheme by the OpenIddict ASP.NET Core server integration. Consider removing the conflicting authentication handler.</value>
</data>
<data name="ID0109" xml:space="preserve">
<value>The OpenIddict ASP.NET Core server handler cannot be used as the default scheme handler.
<value>The OpenIddict ASP.NET Core server handler cannot be used as a default authentication scheme handler.
Make sure that neither DefaultAuthenticateScheme, DefaultChallengeScheme, DefaultForbidScheme, DefaultSignInScheme, DefaultSignOutScheme nor DefaultScheme point to an instance of the OpenIddict ASP.NET Core server handler.</value>
</data>
<data name="ID0110" xml:space="preserve">
@ -685,11 +684,10 @@ To register the OpenIddict core services, reference the 'OpenIddict.Core' packag
Error URI: {2}</value>
</data>
<data name="ID0164" xml:space="preserve">
<value>The OpenIddict ASP.NET Core validation handler cannot be registered as an authentication scheme.
This may indicate that an instance of another handler was registered with the same scheme.</value>
<value>The '{0}' authentication scheme already exists and cannot be registered as an authentication scheme by the OpenIddict ASP.NET Core validation integration. Consider removing the conflicting authentication handler.</value>
</data>
<data name="ID0165" xml:space="preserve">
<value>The OpenIddict ASP.NET Core validation handler cannot be used as the default sign-in/sign-out handler.
<value>The OpenIddict ASP.NET Core validation handler cannot be used as a default authentication scheme handler.
Make sure that neither DefaultSignInScheme nor DefaultSignOutScheme point to an instance of the OpenIddict ASP.NET Core validation handler.</value>
</data>
<data name="ID0166" xml:space="preserve">
@ -1016,11 +1014,10 @@ To register the OpenIddict core services, reference the 'OpenIddict.Core' packag
<value>The payload of this authentication ticket was serialized using an unsupported formatter version.</value>
</data>
<data name="ID0288" xml:space="preserve">
<value>The OpenIddict ASP.NET Core client handler cannot be registered as an authentication scheme.
This may indicate that an instance of another handler was registered with the same scheme.</value>
<value>The '{0}' authentication scheme already exists and cannot be registered as an authentication scheme by the OpenIddict ASP.NET Core client integration. Consider removing the conflicting authentication handler.</value>
</data>
<data name="ID0289" xml:space="preserve">
<value>The OpenIddict ASP.NET Core client handler cannot be used as the default authentication/sign-in/sign-out handler.
<value>The OpenIddict ASP.NET Core client handler cannot be used as a default authentication scheme handler.
Make sure that neither DefaultAuthenticateScheme, DefaultSignInScheme, DefaultSignOutScheme nor DefaultScheme point to an instance of the OpenIddict ASP.NET Core client handler.</value>
</data>
<data name="ID0290" xml:space="preserve">
@ -1347,7 +1344,7 @@ To apply post-logout redirection responses, create a class implementing 'IOpenId
<value>The default system browser couldn't be started. If the application executes inside a sandbox, make sure it is allowed to launch URIs or spawn new processes.</value>
</data>
<data name="ID0386" xml:space="preserve">
<value>An application discriminator must be manually set in the OpenIddict client system integration options when no application name is provided by the .NET generic host. To set the application discriminator, call 'services.AddOpenIddict().AddClient().UseSystemIntegration().SetApplicationDiscriminator()'.</value>
<value>A pipe name must be manually set in the OpenIddict client system integration options when enabling activation redirection or the built-in pipe server. To set an application discriminator from which the pipe name will be derived, call 'services.AddOpenIddict().AddClient().UseSystemIntegration().SetApplicationDiscriminator()'. Alternatively, you can directly set the pipe name using 'services.AddOpenIddict().AddClient().UseSystemIntegration().SetPipeName()'.</value>
</data>
<data name="ID0387" xml:space="preserve">
<value>The type extracted from the inter-process notification ({0}) is unknown or invalid, which may indicate that different versions of the OpenIddict client are used for the same application.</value>
@ -1878,6 +1875,15 @@ To use a custom policy relying on the system store, set 'OpenIddictServerOptions
<data name="ID0520" xml:space="preserve">
<value>The output length used for derivation of client secrets cannot be lower than {0} or higher than {1} bits.</value>
</data>
<data name="ID0521" xml:space="preserve">
<value>Client registrations must be assigned a unique registration identifier.</value>
</data>
<data name="ID0522" xml:space="preserve">
<value>Client registrations must be assigned a configuration manager.</value>
</data>
<data name="ID0523" xml:space="preserve">
<value>A configuration manager must be attached to the validation options.</value>
</data>
<data name="ID2000" xml:space="preserve">
<value>The security token is missing.</value>
</data>

115
src/OpenIddict.Client.AspNetCore/OpenIddictClientAspNetCoreConfiguration.cs

@ -17,7 +17,9 @@ namespace OpenIddict.Client.AspNetCore;
public sealed class OpenIddictClientAspNetCoreConfiguration : IConfigureOptions<AuthenticationOptions>,
IConfigureOptions<OpenIddictClientOptions>,
IPostConfigureOptions<AuthenticationOptions>,
IPostConfigureOptions<OpenIddictClientAspNetCoreOptions>
IPostConfigureOptions<OpenIddictClientAspNetCoreOptions>,
IValidateOptions<AuthenticationOptions>,
IValidateOptions<OpenIddictClientAspNetCoreOptions>
{
private readonly IServiceProvider _provider;
@ -33,28 +35,22 @@ public sealed class OpenIddictClientAspNetCoreConfiguration : IConfigureOptions<
{
ArgumentNullException.ThrowIfNull(options);
// If a handler was already registered and the type doesn't correspond to the OpenIddict handler, throw an exception.
if (options.SchemeMap.TryGetValue(OpenIddictClientAspNetCoreDefaults.AuthenticationScheme, out var builder) &&
builder.HandlerType != typeof(OpenIddictClientAspNetCoreHandler))
// Register the authentication scheme handler used by the OpenIddict ASP.NET Core client integration.
if (!options.SchemeMap.ContainsKey(OpenIddictClientAspNetCoreDefaults.AuthenticationScheme))
{
throw new InvalidOperationException(SR.GetResourceString(SR.ID0288));
options.AddScheme<OpenIddictClientAspNetCoreHandler>(
OpenIddictClientAspNetCoreDefaults.AuthenticationScheme, displayName: null);
}
options.AddScheme<OpenIddictClientAspNetCoreHandler>(
OpenIddictClientAspNetCoreDefaults.AuthenticationScheme, displayName: null);
// Resolve the forwarded authentication schemes managed by the OpenIddict ASP.NET Core
// client host and add an entry for each scheme in the ASP.NET Core authentication options.
foreach (var scheme in _provider.GetRequiredService<IOptionsMonitor<OpenIddictClientAspNetCoreOptions>>()
.CurrentValue.ForwardedAuthenticationSchemes)
{
if (options.SchemeMap.TryGetValue(scheme.Name, out builder) &&
builder.HandlerType != typeof(OpenIddictClientAspNetCoreForwarder))
if (!options.SchemeMap.ContainsKey(scheme.Name))
{
throw new InvalidOperationException(SR.FormatID0414(scheme.Name));
options.AddScheme<OpenIddictClientAspNetCoreForwarder>(scheme.Name, scheme.DisplayName);
}
options.AddScheme<OpenIddictClientAspNetCoreForwarder>(scheme.Name, scheme.DisplayName);
}
}
@ -67,9 +63,9 @@ public sealed class OpenIddictClientAspNetCoreConfiguration : IConfigureOptions<
{
foreach (var (provider, registrations) in _provider.GetRequiredService<IOptionsMonitor<OpenIddictClientOptions>>()
.CurrentValue.Registrations
.Where(registration => !string.IsNullOrEmpty(registration.ProviderName))
.GroupBy(registration => registration.ProviderName)
.Select(group => (ProviderName: group.Key, Registrations: group.ToList())))
.Where(static registration => !string.IsNullOrEmpty(registration.ProviderName))
.GroupBy(static registration => registration.ProviderName)
.Select(static group => (ProviderName: group.Key, Registrations: group.ToList())))
{
// If an explicit mapping was already added, don't overwrite it.
if (options.ForwardedAuthenticationSchemes.Exists(scheme =>
@ -78,11 +74,9 @@ public sealed class OpenIddictClientAspNetCoreConfiguration : IConfigureOptions<
continue;
}
// Ensure multiple client registrations don't share the same provider
// name when automatic authentication scheme forwarding is enabled.
if (registrations is not [OpenIddictClientRegistration registration])
{
throw new InvalidOperationException(SR.FormatID0415(provider));
continue;
}
options.ForwardedAuthenticationSchemes.Add(new AuthenticationScheme(
@ -107,14 +101,6 @@ public sealed class OpenIddictClientAspNetCoreConfiguration : IConfigureOptions<
{
ArgumentNullException.ThrowIfNull(options);
if (!TryValidate(options.SchemeMap, options.DefaultAuthenticateScheme) ||
!TryValidate(options.SchemeMap, options.DefaultScheme) ||
!TryValidate(options.SchemeMap, options.DefaultSignInScheme) ||
!TryValidate(options.SchemeMap, options.DefaultSignOutScheme))
{
throw new InvalidOperationException(SR.GetResourceString(SR.ID0289));
}
// Starting in ASP.NET 7.0, the authentication stack integrates a fallback
// mechanism to select the default scheme to use when no value is set, but
// only if a single handler has been registered in the authentication options.
@ -131,8 +117,44 @@ public sealed class OpenIddictClientAspNetCoreConfiguration : IConfigureOptions<
{
options.AddScheme<IAuthenticationHandler>(Guid.NewGuid().ToString(), displayName: null);
}
}
/// <inheritdoc/>
public ValidateOptionsResult Validate(string? name, AuthenticationOptions options)
{
ArgumentNullException.ThrowIfNull(options);
var builder = new ValidateOptionsResultBuilder();
static bool TryValidate(IDictionary<string, AuthenticationSchemeBuilder> map, string? scheme)
// Ensure the default schemes are not mapped to the OpenIddict client handler or forwarder.
if (!ValidateDefaultScheme(options.SchemeMap, options.DefaultAuthenticateScheme) ||
!ValidateDefaultScheme(options.SchemeMap, options.DefaultScheme) ||
!ValidateDefaultScheme(options.SchemeMap, options.DefaultSignInScheme) ||
!ValidateDefaultScheme(options.SchemeMap, options.DefaultSignOutScheme))
{
builder.AddError(SR.GetResourceString(SR.ID0289));
}
// Ensure the main authentication scheme was not hijacked by another component
// and that the handler type corresponds to the OpenIddict client handler.
if (!ValidateHandlerType<OpenIddictClientAspNetCoreHandler>(
options.SchemeMap, OpenIddictClientAspNetCoreDefaults.AuthenticationScheme))
{
builder.AddError(SR.GetResourceString(SR.ID0288));
}
// Ensure the forwarded authentication schemes are mapped to the OpenIddict client forwarder.
foreach (var group in _provider.GetRequiredService<IOptionsMonitor<OpenIddictClientAspNetCoreOptions>>()
.CurrentValue.ForwardedAuthenticationSchemes
.GroupBy(static scheme => scheme.Name)
.Where(group => !ValidateHandlerType<OpenIddictClientAspNetCoreForwarder>(options.SchemeMap, group.Key)))
{
builder.AddError(SR.FormatID0414(group.Key));
}
return builder.Build();
static bool ValidateDefaultScheme(IDictionary<string, AuthenticationSchemeBuilder> map, string? scheme)
{
// If the scheme was not set or if it cannot be found in the map, return true.
if (string.IsNullOrEmpty(scheme) || !map.TryGetValue(scheme, out var builder))
@ -143,5 +165,42 @@ public sealed class OpenIddictClientAspNetCoreConfiguration : IConfigureOptions<
return builder.HandlerType != typeof(OpenIddictClientAspNetCoreHandler) &&
builder.HandlerType != typeof(OpenIddictClientAspNetCoreForwarder);
}
static bool ValidateHandlerType<THandler>(IDictionary<string, AuthenticationSchemeBuilder> map, string? scheme)
where THandler : IAuthenticationHandler
{
// If the scheme was not set or if it cannot be found in the map, return true.
if (string.IsNullOrEmpty(scheme) || !map.TryGetValue(scheme, out var builder))
{
return true;
}
return builder.HandlerType == typeof(THandler);
}
}
/// <inheritdoc/>
public ValidateOptionsResult Validate(string? name, OpenIddictClientAspNetCoreOptions options)
{
ArgumentNullException.ThrowIfNull(options);
var builder = new ValidateOptionsResultBuilder();
// Ensure multiple client registrations don't share the same provider
// name when automatic authentication scheme forwarding is enabled.
if (!options.DisableAutomaticAuthenticationSchemeForwarding)
{
foreach (var (provider, registrations) in _provider.GetRequiredService<IOptionsMonitor<OpenIddictClientOptions>>()
.CurrentValue.Registrations
.Where(static registration => !string.IsNullOrEmpty(registration.ProviderName))
.GroupBy(static registration => registration.ProviderName)
.Select(static group => (ProviderName: group.Key, Registrations: group.ToList()))
.Where(static group => group.Registrations.Count is > 1))
{
builder.AddError(SR.FormatID0415(provider));
}
}
return builder.Build();
}
}

10
src/OpenIddict.Client.AspNetCore/OpenIddictClientAspNetCoreExtensions.cs

@ -49,14 +49,20 @@ public static class OpenIddictClientAspNetCoreExtensions
IConfigureOptions<AuthenticationOptions>, OpenIddictClientAspNetCoreConfiguration>());
builder.Services.TryAddEnumerable(ServiceDescriptor.Singleton<
IPostConfigureOptions<AuthenticationOptions>, OpenIddictClientAspNetCoreConfiguration>());
IConfigureOptions<OpenIddictClientOptions>, OpenIddictClientAspNetCoreConfiguration>());
builder.Services.TryAddEnumerable(ServiceDescriptor.Singleton<
IConfigureOptions<OpenIddictClientOptions>, OpenIddictClientAspNetCoreConfiguration>());
IPostConfigureOptions<AuthenticationOptions>, OpenIddictClientAspNetCoreConfiguration>());
builder.Services.TryAddEnumerable(ServiceDescriptor.Singleton<
IPostConfigureOptions<OpenIddictClientAspNetCoreOptions>, OpenIddictClientAspNetCoreConfiguration>());
builder.Services.TryAddEnumerable(ServiceDescriptor.Singleton<
IValidateOptions<AuthenticationOptions>, OpenIddictClientAspNetCoreConfiguration>());
builder.Services.TryAddEnumerable(ServiceDescriptor.Singleton<
IValidateOptions<OpenIddictClientAspNetCoreOptions>, OpenIddictClientAspNetCoreConfiguration>());
return new OpenIddictClientAspNetCoreBuilder(builder.Services);
}

10
src/OpenIddict.Client.DataProtection/OpenIddictClientDataProtectionConfiguration.cs

@ -17,14 +17,14 @@ namespace OpenIddict.Client.DataProtection;
public sealed class OpenIddictClientDataProtectionConfiguration : IConfigureOptions<OpenIddictClientOptions>,
IPostConfigureOptions<OpenIddictClientDataProtectionOptions>
{
private readonly IDataProtectionProvider _dataProtectionProvider;
private readonly IServiceProvider _provider;
/// <summary>
/// Creates a new instance of the <see cref="OpenIddictClientDataProtectionConfiguration"/> class.
/// </summary>
/// <param name="dataProtectionProvider">The ASP.NET Core Data Protection provider.</param>
public OpenIddictClientDataProtectionConfiguration(IDataProtectionProvider dataProtectionProvider)
=> _dataProtectionProvider = dataProtectionProvider;
/// <param name="provider">The service provider.</param>
public OpenIddictClientDataProtectionConfiguration(IServiceProvider provider)
=> _provider = provider ?? throw new ArgumentNullException(nameof(provider));
/// <inheritdoc/>
public void Configure(OpenIddictClientOptions options)
@ -40,6 +40,6 @@ public sealed class OpenIddictClientDataProtectionConfiguration : IConfigureOpti
{
ArgumentNullException.ThrowIfNull(options);
options.DataProtectionProvider ??= _dataProtectionProvider;
options.DataProtectionProvider ??= _provider.GetDataProtectionProvider();
}
}

42
src/OpenIddict.Client.Owin/OpenIddictClientOwinConfiguration.cs

@ -16,7 +16,8 @@ namespace OpenIddict.Client.Owin;
/// </summary>
[EditorBrowsable(EditorBrowsableState.Advanced)]
public sealed class OpenIddictClientOwinConfiguration : IConfigureOptions<OpenIddictClientOptions>,
IPostConfigureOptions<OpenIddictClientOwinOptions>
IPostConfigureOptions<OpenIddictClientOwinOptions>,
IValidateOptions<OpenIddictClientOwinOptions>
{
private readonly IServiceProvider _provider;
@ -53,11 +54,6 @@ public sealed class OpenIddictClientOwinConfiguration : IConfigureOptions<OpenId
_ => new CookieManager()
};
if (options.AuthenticationMode is AuthenticationMode.Active)
{
throw new InvalidOperationException(SR.GetResourceString(SR.ID0314));
}
if (!options.DisableAutomaticAuthenticationTypeForwarding)
{
foreach (var (provider, registrations) in _provider.GetRequiredService<IOptionsMonitor<OpenIddictClientOptions>>()
@ -73,11 +69,9 @@ public sealed class OpenIddictClientOwinConfiguration : IConfigureOptions<OpenId
continue;
}
// Ensure multiple client registrations don't share the same provider
// name when automatic authentication type forwarding is enabled.
if (registrations is not [OpenIddictClientRegistration registration])
{
throw new InvalidOperationException(SR.FormatID0416(provider));
continue;
}
var description = new AuthenticationDescription
@ -98,4 +92,34 @@ public sealed class OpenIddictClientOwinConfiguration : IConfigureOptions<OpenId
}
}
}
/// <inheritdoc/>
public ValidateOptionsResult Validate(string? name, OpenIddictClientOwinOptions options)
{
ArgumentNullException.ThrowIfNull(options);
var builder = new ValidateOptionsResultBuilder();
if (options.AuthenticationMode is AuthenticationMode.Active)
{
builder.AddError(SR.GetResourceString(SR.ID0314));
}
// Ensure multiple client registrations don't share the same provider
// name when automatic authentication type forwarding is enabled.
if (!options.DisableAutomaticAuthenticationTypeForwarding)
{
foreach (var (provider, registrations) in _provider.GetRequiredService<IOptionsMonitor<OpenIddictClientOptions>>()
.CurrentValue.Registrations
.Where(static registration => !string.IsNullOrEmpty(registration.ProviderName))
.GroupBy(static registration => registration.ProviderName)
.Select(static group => (ProviderName: group.Key, Registrations: group.ToList()))
.Where(static group => group.Registrations.Count is > 1))
{
builder.AddError(SR.FormatID0416(provider));
}
}
return builder.Build();
}
}

3
src/OpenIddict.Client.Owin/OpenIddictClientOwinExtensions.cs

@ -52,6 +52,9 @@ public static class OpenIddictClientOwinExtensions
builder.Services.TryAddEnumerable(ServiceDescriptor.Singleton<
IPostConfigureOptions<OpenIddictClientOwinOptions>, OpenIddictClientOwinConfiguration>());
builder.Services.TryAddEnumerable(ServiceDescriptor.Singleton<
IValidateOptions<OpenIddictClientOwinOptions>, OpenIddictClientOwinConfiguration>());
return new OpenIddictClientOwinBuilder(builder.Services);
}

134
src/OpenIddict.Client.SystemIntegration/OpenIddictClientSystemIntegrationConfiguration.cs

@ -11,6 +11,7 @@ using System.Security.AccessControl;
using System.Security.Cryptography;
using System.Security.Principal;
using System.Text;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Options;
using Microsoft.IdentityModel.Tokens;
@ -24,16 +25,17 @@ namespace OpenIddict.Client.SystemIntegration;
[EditorBrowsable(EditorBrowsableState.Advanced)]
public sealed class OpenIddictClientSystemIntegrationConfiguration : IConfigureOptions<OpenIddictClientOptions>,
IPostConfigureOptions<OpenIddictClientOptions>,
IPostConfigureOptions<OpenIddictClientSystemIntegrationOptions>
IPostConfigureOptions<OpenIddictClientSystemIntegrationOptions>,
IValidateOptions<OpenIddictClientSystemIntegrationOptions>
{
private readonly IHostEnvironment _environment;
private readonly IServiceProvider _provider;
/// <summary>
/// Creates a new instance of the <see cref="OpenIddictClientSystemIntegrationConfiguration"/> class.
/// </summary>
/// <param name="environment">The host environment.</param>
public OpenIddictClientSystemIntegrationConfiguration(IHostEnvironment environment)
=> _environment = environment ?? throw new ArgumentNullException(nameof(environment));
/// <param name="provider">The service provider.</param>
public OpenIddictClientSystemIntegrationConfiguration(IServiceProvider provider)
=> _provider = provider ?? throw new ArgumentNullException(nameof(provider));
/// <inheritdoc/>
public void Configure(OpenIddictClientOptions options)
@ -62,50 +64,6 @@ public sealed class OpenIddictClientSystemIntegrationConfiguration : IConfigureO
{
ArgumentNullException.ThrowIfNull(options);
// Ensure the operating system version is supported.
if (!OperatingSystem.IsAndroidVersionAtLeast(21) && !OperatingSystem.IsIOSVersionAtLeast(12) &&
!OperatingSystem.IsLinux() && !OperatingSystem.IsMacCatalystVersionAtLeast(13, 1) &&
!OperatingSystem.IsMacOSVersionAtLeast(10, 15) && !OperatingSystem.IsWindowsVersionAtLeast(7))
{
throw new PlatformNotSupportedException(SR.GetResourceString(SR.ID0389));
}
#if !ANDROID
// When running on Android, iOS or Mac Catalyst, ensure the version compiled for these platforms
// is used to prevent the generic/non-OS specific TFM from being used as launching the system
// browser cannot be done using Process.Start() and requires using OS-specific APIs that are
// not available on the portable version of the OpenIddict.Client.SystemIntegration package.
if (OperatingSystem.IsAndroid())
{
throw new PlatformNotSupportedException(SR.GetResourceString(SR.ID0449));
}
#endif
#if !IOS && !MACCATALYST
if (OperatingSystem.IsIOS() || OperatingSystem.IsMacCatalyst())
{
throw new PlatformNotSupportedException(SR.GetResourceString(SR.ID0449));
}
#endif
#pragma warning disable CA1416
// If explicitly set, ensure the specified authentication mode is supported.
if (options.AuthenticationMode is ASWebAuthenticationSession && !IsASWebAuthenticationSessionSupported())
{
throw new PlatformNotSupportedException(SR.GetResourceString(SR.ID0446));
}
else if (options.AuthenticationMode is CustomTabsIntent && !IsCustomTabsIntentSupported())
{
throw new PlatformNotSupportedException(SR.GetResourceString(SR.ID0452));
}
else if (options.AuthenticationMode is WebAuthenticationBroker && !IsWebAuthenticationBrokerSupported())
{
throw new PlatformNotSupportedException(SR.GetResourceString(SR.ID0392));
}
#pragma warning restore CA1416
// When possible, always prefer OS-managed modes. Otherwise, fall back to the system browser.
options.AuthenticationMode ??=
IsASWebAuthenticationSessionSupported() ? ASWebAuthenticationSession :
@ -128,16 +86,12 @@ public sealed class OpenIddictClientSystemIntegrationConfiguration : IConfigureO
options.EnableEmbeddedWebServer ??= false;
}
// If no explicit application discriminator was specified, compute the SHA-256 hash
// of the application name resolved from the host and use it as a unique identifier.
if (string.IsNullOrEmpty(options.ApplicationDiscriminator))
// If no explicit application discriminator was specified, compute the SHA-256 hash of the application
// name resolved from the host environment (if available) and use it as a unique identifier.
if (string.IsNullOrEmpty(options.ApplicationDiscriminator) &&
_provider.GetService<IHostEnvironment>() is IHostEnvironment { ApplicationName.Length: > 0 } environment)
{
if (string.IsNullOrEmpty(_environment.ApplicationName))
{
throw new InvalidOperationException(SR.GetResourceString(SR.ID0386));
}
var digest = SHA256.HashData(Encoding.UTF8.GetBytes(_environment.ApplicationName));
var digest = SHA256.HashData(Encoding.UTF8.GetBytes(environment.ApplicationName));
// Note: only the left-most half of the hash is used to limit the length of the resulting discriminator,
// which is required on platforms like macOS, where the name of pipes is always prefixed with a static part
@ -152,7 +106,7 @@ public sealed class OpenIddictClientSystemIntegrationConfiguration : IConfigureO
}
// If no explicit pipe name was specified, build one using the application discriminator.
if (string.IsNullOrEmpty(options.PipeName))
if (string.IsNullOrEmpty(options.PipeName) && !string.IsNullOrEmpty(options.ApplicationDiscriminator))
{
// Note: on Windows, the name is deliberately prefixed with "LOCAL\" to support
// partial trust/sandboxed applications that are executed in an AppContainer
@ -195,4 +149,66 @@ public sealed class OpenIddictClientSystemIntegrationConfiguration : IConfigureO
}
}
}
/// <inheritdoc/>
public ValidateOptionsResult Validate(string? name, OpenIddictClientSystemIntegrationOptions options)
{
ArgumentNullException.ThrowIfNull(options);
var builder = new ValidateOptionsResultBuilder();
// Ensure the operating system version is supported.
if (!OperatingSystem.IsAndroidVersionAtLeast(21) && !OperatingSystem.IsIOSVersionAtLeast(12) &&
!OperatingSystem.IsLinux() && !OperatingSystem.IsMacCatalystVersionAtLeast(13, 1) &&
!OperatingSystem.IsMacOSVersionAtLeast(10, 15) && !OperatingSystem.IsWindowsVersionAtLeast(7))
{
builder.AddError(SR.GetResourceString(SR.ID0389));
}
#if !ANDROID
// When running on Android, iOS or Mac Catalyst, ensure the version compiled for these platforms
// is used to prevent the generic/non-OS specific TFM from being used as launching the system
// browser cannot be done using Process.Start() and requires using OS-specific APIs that are
// not available on the portable version of the OpenIddict.Client.SystemIntegration package.
if (OperatingSystem.IsAndroid())
{
builder.AddError(SR.GetResourceString(SR.ID0449));
}
#endif
#if !IOS && !MACCATALYST
else if (OperatingSystem.IsIOS() || OperatingSystem.IsMacCatalyst())
{
builder.AddError(SR.GetResourceString(SR.ID0449));
}
#endif
#pragma warning disable CA1416
// If explicitly set, ensure the specified authentication mode is supported.
if (options.AuthenticationMode is ASWebAuthenticationSession && !IsASWebAuthenticationSessionSupported())
{
builder.AddError(SR.GetResourceString(SR.ID0446));
}
else if (options.AuthenticationMode is CustomTabsIntent && !IsCustomTabsIntentSupported())
{
builder.AddError(SR.GetResourceString(SR.ID0452));
}
else if (options.AuthenticationMode is WebAuthenticationBroker && !IsWebAuthenticationBrokerSupported())
{
builder.AddError(SR.GetResourceString(SR.ID0392));
}
#pragma warning restore CA1416
// If activation redirection or the pipe server is enabled, ensure a pipe name
// was specified or could be inferred from the application discriminator.
if (string.IsNullOrEmpty(options.PipeName) &&
(options.EnableActivationRedirection is true || options.EnablePipeServer is true))
{
builder.AddError(SR.GetResourceString(SR.ID0386));
}
return builder.Build();
}
}

3
src/OpenIddict.Client.SystemIntegration/OpenIddictClientSystemIntegrationExtensions.cs

@ -104,6 +104,9 @@ public static class OpenIddictClientSystemIntegrationExtensions
builder.Services.TryAddEnumerable(ServiceDescriptor.Singleton<
IPostConfigureOptions<OpenIddictClientSystemIntegrationOptions>, OpenIddictClientSystemIntegrationConfiguration>());
builder.Services.TryAddEnumerable(ServiceDescriptor.Singleton<
IValidateOptions<OpenIddictClientSystemIntegrationOptions>, OpenIddictClientSystemIntegrationConfiguration>());
return new OpenIddictClientSystemIntegrationBuilder(builder.Services);
}

2
src/OpenIddict.Client.SystemNetHttp/OpenIddictClientSystemNetHttpConfiguration.cs

@ -112,9 +112,7 @@ public sealed class OpenIddictClientSystemNetHttpConfiguration : IConfigureOptio
#if NET
else if (options.CurrentValue.HttpResiliencePipeline is ResiliencePipeline<HttpResponseMessage> pipeline)
{
#pragma warning disable EXTEXP0001
builder.AdditionalHandlers.Add(new ResilienceHandler(pipeline));
#pragma warning restore EXTEXP0001
}
#endif
if (builder.PrimaryHandler is not HttpClientHandler handler)

4
src/OpenIddict.Client.SystemNetHttp/OpenIddictClientSystemNetHttpExtensions.cs

@ -38,10 +38,10 @@ public static class OpenIddictClientSystemNetHttpExtensions
// Note: TryAddEnumerable() is used here to ensure the initializers are registered only once.
builder.Services.TryAddEnumerable(ServiceDescriptor.Singleton<
IConfigureOptions<OpenIddictClientOptions>, OpenIddictClientSystemNetHttpConfiguration>());
IConfigureOptions<HttpClientFactoryOptions>, OpenIddictClientSystemNetHttpConfiguration>());
builder.Services.TryAddEnumerable(ServiceDescriptor.Singleton<
IConfigureOptions<HttpClientFactoryOptions>, OpenIddictClientSystemNetHttpConfiguration>());
IConfigureOptions<OpenIddictClientOptions>, OpenIddictClientSystemNetHttpConfiguration>());
builder.Services.TryAddEnumerable(ServiceDescriptor.Singleton<
IPostConfigureOptions<HttpClientFactoryOptions>, OpenIddictClientSystemNetHttpConfiguration>());

33
src/OpenIddict.Client.WebIntegration/OpenIddictClientWebIntegrationConfiguration.cs

@ -5,6 +5,7 @@
*/
using System.ComponentModel;
using System.ComponentModel.DataAnnotations;
using Microsoft.Extensions.Options;
namespace OpenIddict.Client.WebIntegration;
@ -14,7 +15,8 @@ namespace OpenIddict.Client.WebIntegration;
/// </summary>
[EditorBrowsable(EditorBrowsableState.Advanced)]
public sealed partial class OpenIddictClientWebIntegrationConfiguration : IConfigureOptions<OpenIddictClientOptions>,
IPostConfigureOptions<OpenIddictClientOptions>
IPostConfigureOptions<OpenIddictClientOptions>,
IValidateOptions<OpenIddictClientOptions>
{
/// <inheritdoc/>
public void Configure(OpenIddictClientOptions options)
@ -32,8 +34,6 @@ public sealed partial class OpenIddictClientWebIntegrationConfiguration : IConfi
foreach (var registration in options.Registrations)
{
// If the client registration has a provider type attached, apply
// the configuration logic corresponding to the specified provider.
if (!string.IsNullOrEmpty(registration.ProviderType))
{
ConfigureProvider(registration);
@ -41,6 +41,24 @@ public sealed partial class OpenIddictClientWebIntegrationConfiguration : IConfi
}
}
/// <inheritdoc/>
public ValidateOptionsResult Validate(string? name, OpenIddictClientOptions options)
{
ArgumentNullException.ThrowIfNull(options);
var builder = new ValidateOptionsResultBuilder();
foreach (var registration in options.Registrations)
{
if (!string.IsNullOrEmpty(registration.ProviderType))
{
builder.AddResults(ValidateProvider(registration));
}
}
return builder.Build();
}
/// <summary>
/// Amends the registration with the provider-specific configuration logic.
/// </summary>
@ -48,4 +66,13 @@ public sealed partial class OpenIddictClientWebIntegrationConfiguration : IConfi
// Note: the implementation of this method is automatically generated by the source generator.
[EditorBrowsable(EditorBrowsableState.Never)]
public static partial void ConfigureProvider(OpenIddictClientRegistration registration);
/// <summary>
/// Validates the registration using the provider-specific configuration logic.
/// </summary>
/// <param name="registration">The client registration.</param>
/// <returns>The validation results.</returns>
// Note: the implementation of this method is automatically generated by the source generator.
[EditorBrowsable(EditorBrowsableState.Never)]
public static partial IEnumerable<ValidationResult> ValidateProvider(OpenIddictClientRegistration registration);
}

14
src/OpenIddict.Client.WebIntegration/OpenIddictClientWebIntegrationExtensions.cs

@ -38,9 +38,9 @@ public static partial class OpenIddictClientWebIntegrationExtensions
builder.Services.TryAddEnumerable(ServiceDescriptor.Singleton<
IConfigureOptions<OpenIddictClientOptions>, OpenIddictClientWebIntegrationConfiguration>());
// Note: the IPostConfigureOptions<OpenIddictClientOptions> service responsible for populating
// the client registrations MUST be registered before OpenIddictClientConfiguration to ensure
// the registrations are correctly populated before being validated.
// Note: the IPostConfigureOptions<OpenIddictClientOptions> and IValidateOptions<OpenIddictClientOptions>
// services responsible for populating and validating the client registrations MUST be registered before
// OpenIddictClientConfiguration to ensure the registrations are correctly populated and validated.
if (!builder.Services.Any(static descriptor =>
descriptor.ServiceType == typeof(IPostConfigureOptions<OpenIddictClientOptions>) &&
descriptor.ImplementationType == typeof(OpenIddictClientWebIntegrationConfiguration)))
@ -49,6 +49,14 @@ public static partial class OpenIddictClientWebIntegrationExtensions
IPostConfigureOptions<OpenIddictClientOptions>, OpenIddictClientWebIntegrationConfiguration>());
}
if (!builder.Services.Any(static descriptor =>
descriptor.ServiceType == typeof(IValidateOptions<OpenIddictClientOptions>) &&
descriptor.ImplementationType == typeof(OpenIddictClientWebIntegrationConfiguration)))
{
builder.Services.Insert(0, ServiceDescriptor.Singleton<
IValidateOptions<OpenIddictClientOptions>, OpenIddictClientWebIntegrationConfiguration>());
}
return new OpenIddictClientWebIntegrationBuilder(builder.Services);
}

2
src/OpenIddict.Client.WebIntegration/OpenIddictClientWebIntegrationProviders.xml

@ -2,7 +2,7 @@
xsi:noNamespaceSchemaLocation="OpenIddictClientWebIntegrationProviders.xsd">
<!--
Note: for more information on how to add a new web provider integration, visit
https://documentation.openiddict.com/guides/contributing-a-new-web-provider.html.
https://documentation.openiddict.com/guides/contributing-a-new-web-provider.
-->
<!--

271
src/OpenIddict.Client/OpenIddictClientConfiguration.cs

@ -20,67 +20,34 @@ namespace OpenIddict.Client;
/// Contains the methods required to ensure that the OpenIddict client configuration is valid.
/// </summary>
[EditorBrowsable(EditorBrowsableState.Advanced)]
public sealed class OpenIddictClientConfiguration : IPostConfigureOptions<OpenIddictClientOptions>
public sealed class OpenIddictClientConfiguration : IPostConfigureOptions<OpenIddictClientOptions>,
IValidateOptions<OpenIddictClientOptions>
{
private readonly OpenIddictClientService _service;
private readonly IServiceProvider _provider;
/// <summary>
/// Creates a new instance of the <see cref="OpenIddictClientConfiguration"/> class.
/// </summary>
/// <param name="provider">The service provider.</param>
/// <param name="service">The OpenIddict client service.</param>
public OpenIddictClientConfiguration(IServiceProvider provider, OpenIddictClientService service)
{
_provider = provider ?? throw new ArgumentNullException(nameof(provider));
_service = service ?? throw new ArgumentNullException(nameof(service));
}
public OpenIddictClientConfiguration(IServiceProvider provider)
=> _provider = provider ?? throw new ArgumentNullException(nameof(provider));
/// <inheritdoc/>
public void PostConfigure(string? name, OpenIddictClientOptions options)
{
ArgumentNullException.ThrowIfNull(options);
if (options.JsonWebTokenHandler is null)
{
throw new InvalidOperationException(SR.GetResourceString(SR.ID0075));
}
options.TimeProvider ??= _provider.GetService<TimeProvider>() ?? TimeProvider.System;
foreach (var registration in options.Registrations)
{
if (registration.Issuer is null)
{
throw string.IsNullOrEmpty(registration.ProviderType) ?
new InvalidOperationException(SR.GetResourceString(SR.ID0405)) :
new InvalidOperationException(SR.GetResourceString(SR.ID0411));
}
if (!registration.Issuer.IsAbsoluteUri || OpenIddictHelpers.IsImplicitFileUri(registration.Issuer))
{
throw new InvalidOperationException(SR.GetResourceString(SR.ID0136));
}
if (!string.IsNullOrEmpty(registration.Issuer.Fragment) || !string.IsNullOrEmpty(registration.Issuer.Query))
{
throw new InvalidOperationException(SR.GetResourceString(SR.ID0137));
}
// If no explicit registration identifier was set, compute a stable
// hash based on the issuer URI and the provider name, if available.
if (string.IsNullOrEmpty(registration.RegistrationId))
if (registration.Issuer is not null && string.IsNullOrEmpty(registration.RegistrationId))
{
registration.RegistrationId = ComputeDefaultRegistrationId(registration);
}
// Ensure the registration identifier doesn't contain U+001E or U+001F separators as they are
// used by the System.Net.Http integration to separate properties in the HTTP client names.
else if (registration.RegistrationId.Any(static character => character is '\u001e' or '\u001f'))
{
throw new InvalidOperationException(SR.GetResourceString(SR.ID0455));
}
// If no client type was explicitly set, assume the client is confidential if a client secret
// or a signing key/certificate (typically used with private_key_jwt, tls_client_auth or
// self_signed_tls_client_auth) has been attached to the client registration.
@ -96,30 +63,19 @@ public sealed class OpenIddictClientConfiguration : IPostConfigureOptions<OpenId
{
if (registration.Configuration is not null)
{
if (registration.Configuration.Issuer is not null &&
registration.Configuration.Issuer != registration.Issuer)
{
throw new InvalidOperationException(SR.GetResourceString(SR.ID0395));
}
registration.Configuration.Issuer ??= registration.Issuer;
registration.ConfigurationManager = new StaticConfigurationManager<OpenIddictConfiguration>(registration.Configuration);
}
else
else if (registration.Issuer is not null)
{
if (!options.Handlers.Exists(static descriptor => descriptor.ContextType == typeof(ApplyConfigurationRequestContext)) ||
!options.Handlers.Exists(static descriptor => descriptor.ContextType == typeof(ApplyJsonWebKeySetRequestContext)))
{
throw new InvalidOperationException(SR.GetResourceString(SR.ID0313));
}
registration.ConfigurationEndpoint = OpenIddictHelpers.CreateAbsoluteUri(
registration.ConfigurationEndpoint ??= OpenIddictHelpers.CreateAbsoluteUri(
registration.Issuer,
registration.ConfigurationEndpoint ?? new Uri(".well-known/openid-configuration", UriKind.Relative));
registration.ConfigurationManager = new ConfigurationManager<OpenIddictConfiguration>(
registration.ConfigurationEndpoint.AbsoluteUri, new OpenIddictClientRetriever(_service, registration))
registration.ConfigurationEndpoint.AbsoluteUri,
new OpenIddictClientRetriever(_provider.GetRequiredService<OpenIddictClientService>(), registration))
{
AutomaticRefreshInterval = ConfigurationManager<OpenIddictConfiguration>.DefaultAutomaticRefreshInterval,
RefreshInterval = ConfigurationManager<OpenIddictConfiguration>.DefaultRefreshInterval
@ -146,75 +102,6 @@ public sealed class OpenIddictClientConfiguration : IPostConfigureOptions<OpenId
.Distinct()
.ToList());
// Ensure at least one flow has been enabled.
if (options.GrantTypes.Count is 0 && options.ResponseTypes.Count is 0)
{
throw new InvalidOperationException(SR.GetResourceString(SR.ID0076));
}
var uris = options.RedirectionEndpointUris.Distinct()
.Concat(options.PostLogoutRedirectionEndpointUris.Distinct())
.ToList();
// Ensure endpoint URIs are unique across endpoints.
if (uris.Count != uris.Distinct().Count())
{
throw new InvalidOperationException(SR.GetResourceString(SR.ID0285));
}
// Ensure the redirection endpoint has been enabled when the authorization code or implicit grants are supported.
if (options.RedirectionEndpointUris.Count is 0 && (options.GrantTypes.Contains(GrantTypes.AuthorizationCode) ||
options.GrantTypes.Contains(GrantTypes.Implicit)))
{
throw new InvalidOperationException(SR.GetResourceString(SR.ID0356));
}
// Ensure the grant types/response types configuration is consistent.
foreach (var type in options.ResponseTypes)
{
var types = type.Split(Separators.Space, StringSplitOptions.RemoveEmptyEntries).ToHashSet(StringComparer.Ordinal);
if (types.Contains(ResponseTypes.Code) && !options.GrantTypes.Contains(GrantTypes.AuthorizationCode))
{
throw new InvalidOperationException(SR.FormatID0281(ResponseTypes.Code));
}
if (types.Contains(ResponseTypes.IdToken) && !options.GrantTypes.Contains(GrantTypes.Implicit))
{
throw new InvalidOperationException(SR.FormatID0282(ResponseTypes.IdToken));
}
if (types.Contains(ResponseTypes.Token) && !options.GrantTypes.Contains(GrantTypes.Implicit))
{
throw new InvalidOperationException(SR.FormatID0282(ResponseTypes.Token));
}
}
// When the redirection or post-logout redirection endpoint has been enabled, ensure signing
// and encryption credentials have been provided as they are required to protect state tokens.
if (options.RedirectionEndpointUris.Count is not 0 || options.PostLogoutRedirectionEndpointUris.Count is not 0)
{
if (options.EncryptionCredentials.Count is 0)
{
throw new InvalidOperationException(SR.GetResourceString(SR.ID0357));
}
if (options.SigningCredentials.Count is 0)
{
throw new InvalidOperationException(SR.GetResourceString(SR.ID0358));
}
}
// Ensure registration identifiers are not used in multiple client registrations.
//
// Note: a string comparer ignoring casing is deliberately used to prevent two
// registrations using the same identifier with a different casing from being added.
if (options.Registrations.Count != options.Registrations.Select(registration => registration.RegistrationId)
.Distinct(StringComparer.OrdinalIgnoreCase)
.Count())
{
throw new InvalidOperationException(SR.GetResourceString(SR.ID0347));
}
// Sort the handlers collection using the order associated with each handler.
options.Handlers.Sort((left, right) => left.Order.CompareTo(right.Order));
@ -335,4 +222,144 @@ public sealed class OpenIddictClientConfiguration : IPostConfigureOptions<OpenId
}
}
}
/// <inheritdoc/>
public ValidateOptionsResult Validate(string? name, OpenIddictClientOptions options)
{
ArgumentNullException.ThrowIfNull(options);
var builder = new ValidateOptionsResultBuilder();
if (options.JsonWebTokenHandler is null)
{
builder.AddError(SR.GetResourceString(SR.ID0075));
}
foreach (var registration in options.Registrations)
{
if (string.IsNullOrEmpty(registration.RegistrationId))
{
builder.AddError(SR.GetResourceString(SR.ID0521));
}
// Ensure the registration identifier doesn't contain U+001E or U+001F separators as they are
// used by the System.Net.Http integration to separate properties in the HTTP client names.
else if (registration.RegistrationId.Any(static character => character is '\u001e' or '\u001f'))
{
builder.AddError(SR.GetResourceString(SR.ID0455));
}
if (registration.Issuer is null)
{
builder.AddError(string.IsNullOrEmpty(registration.ProviderType)
? SR.GetResourceString(SR.ID0405)
: SR.GetResourceString(SR.ID0411));
}
else if (!registration.Issuer.IsAbsoluteUri || OpenIddictHelpers.IsImplicitFileUri(registration.Issuer))
{
builder.AddError(SR.GetResourceString(SR.ID0136));
}
else if (!string.IsNullOrEmpty(registration.Issuer.Fragment) || !string.IsNullOrEmpty(registration.Issuer.Query))
{
builder.AddError(SR.GetResourceString(SR.ID0137));
}
// If an issuer was attached to the static configuration, ensure it matches the issuer specified in the client registration.
if (registration.Configuration?.Issuer is not null && registration.Configuration.Issuer != registration.Issuer)
{
builder.AddError(SR.GetResourceString(SR.ID0395));
}
if (registration.ConfigurationManager is null)
{
builder.AddError(SR.GetResourceString(SR.ID0522));
}
// If a non-static configuration manager is used, ensure that the required discovery handlers are registered.
else if (!typeof(StaticConfigurationManager<OpenIddictConfiguration>).IsAssignableFrom(registration.ConfigurationManager.GetType()) &&
(!options.Handlers.Exists(static descriptor => descriptor.ContextType == typeof(ApplyConfigurationRequestContext)) ||
!options.Handlers.Exists(static descriptor => descriptor.ContextType == typeof(ApplyJsonWebKeySetRequestContext))))
{
builder.AddError(SR.GetResourceString(SR.ID0313));
}
}
// Ensure at least one flow has been enabled.
if (options.GrantTypes.Count is 0 && options.ResponseTypes.Count is 0)
{
builder.AddError(SR.GetResourceString(SR.ID0076));
}
// Ensure endpoint URIs are unique across endpoints.
if (!ValidateUniqueEndpointUris(options))
{
builder.AddError(SR.GetResourceString(SR.ID0285));
}
// Ensure the redirection endpoint has been enabled when the authorization code or implicit grants are supported.
if (options.RedirectionEndpointUris.Count is 0 && (options.GrantTypes.Contains(GrantTypes.AuthorizationCode) ||
options.GrantTypes.Contains(GrantTypes.Implicit)))
{
builder.AddError(SR.GetResourceString(SR.ID0356));
}
// Ensure the grant types/response types configuration is consistent.
foreach (var type in options.ResponseTypes)
{
var types = type.Split(Separators.Space, StringSplitOptions.RemoveEmptyEntries).ToHashSet(StringComparer.Ordinal);
if (types.Contains(ResponseTypes.Code) && !options.GrantTypes.Contains(GrantTypes.AuthorizationCode))
{
builder.AddError(SR.FormatID0281(ResponseTypes.Code));
}
if (types.Contains(ResponseTypes.IdToken) && !options.GrantTypes.Contains(GrantTypes.Implicit))
{
builder.AddError(SR.FormatID0282(ResponseTypes.IdToken));
}
if (types.Contains(ResponseTypes.Token) && !options.GrantTypes.Contains(GrantTypes.Implicit))
{
builder.AddError(SR.FormatID0282(ResponseTypes.Token));
}
}
// When the redirection or post-logout redirection endpoint has been enabled, ensure signing
// and encryption credentials have been provided as they are required to protect state tokens.
if (options.RedirectionEndpointUris.Count is not 0 || options.PostLogoutRedirectionEndpointUris.Count is not 0)
{
if (options.EncryptionCredentials.Count is 0)
{
builder.AddError(SR.GetResourceString(SR.ID0357));
}
if (options.SigningCredentials.Count is 0)
{
builder.AddError(SR.GetResourceString(SR.ID0358));
}
}
// Ensure registration identifiers are not used in multiple client registrations.
//
// Note: a string comparer ignoring casing is deliberately used to prevent two
// registrations using the same identifier with a different casing from being added.
if (options.Registrations.Count != options.Registrations.Select(registration => registration.RegistrationId)
.Distinct(StringComparer.OrdinalIgnoreCase)
.Count())
{
builder.AddError(SR.GetResourceString(SR.ID0347));
}
return builder.Build();
static bool ValidateUniqueEndpointUris(OpenIddictClientOptions options)
{
var uris = options.RedirectionEndpointUris.Distinct()
.Concat(options.PostLogoutRedirectionEndpointUris.Distinct())
.ToList();
return uris.Count == uris.Distinct().Count();
}
}
}

5
src/OpenIddict.Client/OpenIddictClientExtensions.cs

@ -81,10 +81,13 @@ public static class OpenIddictClientExtensions
// Note: the order used here is not important, as the actual order is set in the options.
builder.Services.TryAdd(DefaultHandlers.Select(descriptor => descriptor.ServiceDescriptor));
// Note: TryAddEnumerable() is used here to ensure the initializer is registered only once.
// Note: TryAddEnumerable() is used here to ensure the initializers are registered only once.
builder.Services.TryAddEnumerable(ServiceDescriptor.Singleton<
IPostConfigureOptions<OpenIddictClientOptions>, OpenIddictClientConfiguration>());
builder.Services.TryAddEnumerable(ServiceDescriptor.Singleton<
IValidateOptions<OpenIddictClientOptions>, OpenIddictClientConfiguration>());
return new OpenIddictClientBuilder(builder.Services);
}

88
src/OpenIddict.Server.AspNetCore/OpenIddictServerAspNetCoreConfiguration.cs

@ -16,23 +16,21 @@ namespace OpenIddict.Server.AspNetCore;
public sealed class OpenIddictServerAspNetCoreConfiguration : IConfigureOptions<AuthenticationOptions>,
IConfigureOptions<OpenIddictServerOptions>,
IPostConfigureOptions<AuthenticationOptions>,
IPostConfigureOptions<OpenIddictServerAspNetCoreOptions>,
IPostConfigureOptions<OpenIddictServerOptions>
IPostConfigureOptions<OpenIddictServerOptions>,
IValidateOptions<AuthenticationOptions>,
IValidateOptions<OpenIddictServerAspNetCoreOptions>
{
/// <inheritdoc/>
public void Configure(AuthenticationOptions options)
{
ArgumentNullException.ThrowIfNull(options);
// If a handler was already registered and the type doesn't correspond to the OpenIddict handler, throw an exception.
if (options.SchemeMap.TryGetValue(OpenIddictServerAspNetCoreDefaults.AuthenticationScheme, out var builder) &&
builder.HandlerType != typeof(OpenIddictServerAspNetCoreHandler))
// Register the authentication scheme handler used by the OpenIddict ASP.NET Core server integration.
if (!options.SchemeMap.ContainsKey(OpenIddictServerAspNetCoreDefaults.AuthenticationScheme))
{
throw new InvalidOperationException(SR.GetResourceString(SR.ID0108));
options.AddScheme<OpenIddictServerAspNetCoreHandler>(
OpenIddictServerAspNetCoreDefaults.AuthenticationScheme, displayName: null);
}
options.AddScheme<OpenIddictServerAspNetCoreHandler>(
OpenIddictServerAspNetCoreDefaults.AuthenticationScheme, displayName: null);
}
/// <inheritdoc/>
@ -52,16 +50,6 @@ public sealed class OpenIddictServerAspNetCoreConfiguration : IConfigureOptions<
{
ArgumentNullException.ThrowIfNull(options);
if (!TryValidate(options.SchemeMap, options.DefaultAuthenticateScheme) ||
!TryValidate(options.SchemeMap, options.DefaultChallengeScheme) ||
!TryValidate(options.SchemeMap, options.DefaultForbidScheme) ||
!TryValidate(options.SchemeMap, options.DefaultScheme) ||
!TryValidate(options.SchemeMap, options.DefaultSignInScheme) ||
!TryValidate(options.SchemeMap, options.DefaultSignOutScheme))
{
throw new InvalidOperationException(SR.GetResourceString(SR.ID0109));
}
// Starting in ASP.NET 7.0, the authentication stack integrates a fallback
// mechanism to select the default scheme to use when no value is set, but
// only if a single handler has been registered in the authentication options.
@ -80,45 +68,69 @@ public sealed class OpenIddictServerAspNetCoreConfiguration : IConfigureOptions<
{
options.AddScheme<IAuthenticationHandler>(Guid.NewGuid().ToString(), displayName: null);
}
}
/// <inheritdoc/>
public void PostConfigure(string? name, OpenIddictServerOptions options)
{
ArgumentNullException.ThrowIfNull(options);
static bool TryValidate(IDictionary<string, AuthenticationSchemeBuilder> map, string? scheme)
// Enable tls_client_auth and self_signed_tls_client_auth support if the
// corresponding chain policies have been configured in the server options.
if (options.PublicKeyInfrastructureTlsClientAuthenticationPolicy is not null)
{
// If the scheme was not set or if it cannot be found in the map, return true.
if (string.IsNullOrEmpty(scheme) || !map.TryGetValue(scheme, out var builder))
{
return true;
}
options.ClientAuthenticationMethods.Add(ClientAuthenticationMethods.TlsClientAuth);
}
return builder.HandlerType != typeof(OpenIddictServerAspNetCoreHandler);
if (options.SelfSignedTlsClientAuthenticationPolicy is not null)
{
options.ClientAuthenticationMethods.Add(ClientAuthenticationMethods.SelfSignedTlsClientAuth);
}
}
/// <inheritdoc/>
public void PostConfigure(string? name, OpenIddictServerAspNetCoreOptions options)
public ValidateOptionsResult Validate(string? name, AuthenticationOptions options)
{
ArgumentNullException.ThrowIfNull(options);
if (options.EnableErrorPassthrough && options.EnableStatusCodePagesIntegration)
var builder = new ValidateOptionsResultBuilder();
if (!ValidateDefaultScheme(options.SchemeMap, options.DefaultAuthenticateScheme) ||
!ValidateDefaultScheme(options.SchemeMap, options.DefaultChallengeScheme) ||
!ValidateDefaultScheme(options.SchemeMap, options.DefaultForbidScheme) ||
!ValidateDefaultScheme(options.SchemeMap, options.DefaultScheme) ||
!ValidateDefaultScheme(options.SchemeMap, options.DefaultSignInScheme) ||
!ValidateDefaultScheme(options.SchemeMap, options.DefaultSignOutScheme))
{
builder.AddError(SR.GetResourceString(SR.ID0109));
}
return builder.Build();
static bool ValidateDefaultScheme(IDictionary<string, AuthenticationSchemeBuilder> map, string? scheme)
{
throw new InvalidOperationException(SR.GetResourceString(SR.ID0110));
// If the scheme was not set or if it cannot be found in the map, return true.
if (string.IsNullOrEmpty(scheme) || !map.TryGetValue(scheme, out var builder))
{
return true;
}
return builder.HandlerType != typeof(OpenIddictServerAspNetCoreHandler);
}
}
/// <inheritdoc/>
public void PostConfigure(string? name, OpenIddictServerOptions options)
public ValidateOptionsResult Validate(string? name, OpenIddictServerAspNetCoreOptions options)
{
ArgumentNullException.ThrowIfNull(options);
// Enable tls_client_auth and self_signed_tls_client_auth support if the
// corresponding chain policies have been configured in the server options.
if (options.PublicKeyInfrastructureTlsClientAuthenticationPolicy is not null)
{
options.ClientAuthenticationMethods.Add(ClientAuthenticationMethods.TlsClientAuth);
}
var builder = new ValidateOptionsResultBuilder();
if (options.SelfSignedTlsClientAuthenticationPolicy is not null)
if (options.EnableErrorPassthrough && options.EnableStatusCodePagesIntegration)
{
options.ClientAuthenticationMethods.Add(ClientAuthenticationMethods.SelfSignedTlsClientAuth);
builder.AddError(SR.GetResourceString(SR.ID0110));
}
return builder.Build();
}
}

9
src/OpenIddict.Server.AspNetCore/OpenIddictServerAspNetCoreExtensions.cs

@ -50,17 +50,20 @@ public static class OpenIddictServerAspNetCoreExtensions
builder.Services.TryAddEnumerable(ServiceDescriptor.Singleton<
IConfigureOptions<AuthenticationOptions>, OpenIddictServerAspNetCoreConfiguration>());
builder.Services.TryAddEnumerable(ServiceDescriptor.Singleton<
IConfigureOptions<OpenIddictServerOptions>, OpenIddictServerAspNetCoreConfiguration>());
builder.Services.TryAddEnumerable(ServiceDescriptor.Singleton<
IPostConfigureOptions<AuthenticationOptions>, OpenIddictServerAspNetCoreConfiguration>());
builder.Services.TryAddEnumerable(ServiceDescriptor.Singleton<
IConfigureOptions<OpenIddictServerOptions>, OpenIddictServerAspNetCoreConfiguration>());
IPostConfigureOptions<OpenIddictServerOptions>, OpenIddictServerAspNetCoreConfiguration>());
builder.Services.TryAddEnumerable(ServiceDescriptor.Singleton<
IPostConfigureOptions<OpenIddictServerAspNetCoreOptions>, OpenIddictServerAspNetCoreConfiguration>());
IValidateOptions<AuthenticationOptions>, OpenIddictServerAspNetCoreConfiguration>());
builder.Services.TryAddEnumerable(ServiceDescriptor.Singleton<
IPostConfigureOptions<OpenIddictServerOptions>, OpenIddictServerAspNetCoreConfiguration>());
IValidateOptions<OpenIddictServerAspNetCoreOptions>, OpenIddictServerAspNetCoreConfiguration>());
return new OpenIddictServerAspNetCoreBuilder(builder.Services);
}

10
src/OpenIddict.Server.DataProtection/OpenIddictServerDataProtectionConfiguration.cs

@ -17,14 +17,14 @@ namespace OpenIddict.Server.DataProtection;
public sealed class OpenIddictServerDataProtectionConfiguration : IConfigureOptions<OpenIddictServerOptions>,
IPostConfigureOptions<OpenIddictServerDataProtectionOptions>
{
private readonly IDataProtectionProvider _dataProtectionProvider;
private readonly IServiceProvider _provider;
/// <summary>
/// Creates a new instance of the <see cref="OpenIddictServerDataProtectionConfiguration"/> class.
/// </summary>
/// <param name="dataProtectionProvider">The ASP.NET Core Data Protection provider.</param>
public OpenIddictServerDataProtectionConfiguration(IDataProtectionProvider dataProtectionProvider)
=> _dataProtectionProvider = dataProtectionProvider;
/// <param name="provider">The service provider used to resolve dependencies.</param>
public OpenIddictServerDataProtectionConfiguration(IServiceProvider provider)
=> _provider = provider ?? throw new ArgumentNullException(nameof(provider));
/// <inheritdoc/>
public void Configure(OpenIddictServerOptions options)
@ -40,6 +40,6 @@ public sealed class OpenIddictServerDataProtectionConfiguration : IConfigureOpti
{
ArgumentNullException.ThrowIfNull(options);
options.DataProtectionProvider ??= _dataProtectionProvider;
options.DataProtectionProvider ??= _provider.GetDataProtectionProvider();
}
}

10
src/OpenIddict.Server.Owin/OpenIddictServerOwinConfiguration.cs

@ -15,7 +15,7 @@ namespace OpenIddict.Server.Owin;
[EditorBrowsable(EditorBrowsableState.Advanced)]
public sealed class OpenIddictServerOwinConfiguration : IConfigureOptions<OpenIddictServerOptions>,
IPostConfigureOptions<OpenIddictServerOptions>,
IPostConfigureOptions<OpenIddictServerOwinOptions>
IValidateOptions<OpenIddictServerOwinOptions>
{
/// <inheritdoc/>
public void Configure(OpenIddictServerOptions options)
@ -48,13 +48,17 @@ public sealed class OpenIddictServerOwinConfiguration : IConfigureOptions<OpenId
}
/// <inheritdoc/>
public void PostConfigure(string? name, OpenIddictServerOwinOptions options)
public ValidateOptionsResult Validate(string? name, OpenIddictServerOwinOptions options)
{
ArgumentNullException.ThrowIfNull(options);
var builder = new ValidateOptionsResultBuilder();
if (options.AuthenticationMode is AuthenticationMode.Active)
{
throw new InvalidOperationException(SR.GetResourceString(SR.ID0119));
builder.AddError(SR.GetResourceString(SR.ID0119));
}
return builder.Build();
}
}

4
src/OpenIddict.Server.Owin/OpenIddictServerOwinExtensions.cs

@ -53,10 +53,10 @@ public static class OpenIddictServerOwinExtensions
IConfigureOptions<OpenIddictServerOptions>, OpenIddictServerOwinConfiguration>());
builder.Services.TryAddEnumerable(ServiceDescriptor.Singleton<
IPostConfigureOptions<OpenIddictServerOwinOptions>, OpenIddictServerOwinConfiguration>());
IPostConfigureOptions<OpenIddictServerOptions>, OpenIddictServerOwinConfiguration>());
builder.Services.TryAddEnumerable(ServiceDescriptor.Singleton<
IPostConfigureOptions<OpenIddictServerOptions>, OpenIddictServerOwinConfiguration>());
IValidateOptions<OpenIddictServerOwinOptions>, OpenIddictServerOwinConfiguration>());
return new OpenIddictServerOwinBuilder(builder.Services);
}

391
src/OpenIddict.Server/OpenIddictServerConfiguration.cs

@ -8,7 +8,6 @@ using System.ComponentModel;
using System.Diagnostics;
using System.Globalization;
using System.Security.Cryptography.X509Certificates;
using System.Text;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;
using Microsoft.IdentityModel.Tokens;
@ -19,7 +18,8 @@ namespace OpenIddict.Server;
/// Contains the methods required to ensure that the OpenIddict server configuration is valid.
/// </summary>
[EditorBrowsable(EditorBrowsableState.Advanced)]
public sealed class OpenIddictServerConfiguration : IPostConfigureOptions<OpenIddictServerOptions>
public sealed class OpenIddictServerConfiguration : IPostConfigureOptions<OpenIddictServerOptions>,
IValidateOptions<OpenIddictServerOptions>
{
private readonly IServiceProvider _provider;
@ -53,15 +53,130 @@ public sealed class OpenIddictServerConfiguration : IPostConfigureOptions<OpenId
options.DisableRollingRefreshTokens = true;
}
// If token storage was disabled, user codes will be returned as-is by OpenIddict instead of being
// automatically converted to reference identifiers (in this case, custom event handlers must be
// registered to manually store the token payload in a database or cache and return a user code
// that can be used and entered by a human user in a web form). Since the default logic is not
// going be used, disable the formatting logic by setting UserCodeDisplayFormat to null here.
if (options.DisableTokenStorage)
{
options.UserCodeLength = 0;
options.UserCodeCharset.Clear();
options.UserCodeDisplayFormat = null;
}
// Sort the handlers collection using the order associated with each handler.
options.Handlers.Sort(static (left, right) => left.Order.CompareTo(right.Order));
var now = options.TimeProvider.GetUtcNow().LocalDateTime;
// Sort the encryption and signing credentials.
options.EncryptionCredentials.Sort((left, right) => Compare(left.Key, right.Key, now));
options.SigningCredentials.Sort((left, right) => Compare(left.Key, right.Key, now));
// Generate a key identifier for the encryption/signing keys that don't already have one.
foreach (var key in options.EncryptionCredentials.Select(credentials => credentials.Key)
.Concat(options.SigningCredentials.Select(credentials => credentials.Key))
.Where(key => string.IsNullOrEmpty(key.KeyId)))
{
key.KeyId = GetKeyIdentifier(key);
}
// Attach the signing credentials to the token validation parameters.
options.TokenValidationParameters.IssuerSigningKeys =
from credentials in options.SigningCredentials
select credentials.Key;
// Attach the encryption credentials to the token validation parameters.
options.TokenValidationParameters.TokenDecryptionKeys =
from credentials in options.EncryptionCredentials
select credentials.Key;
static int Compare(SecurityKey left, SecurityKey right, DateTime now) => (left, right) switch
{
// If the two keys refer to the same instances, return 0.
(SecurityKey first, SecurityKey second) when ReferenceEquals(first, second) => 0,
// If one of the keys is a symmetric key, prefer it to the other one.
(SymmetricSecurityKey, SymmetricSecurityKey) => 0,
(SymmetricSecurityKey, SecurityKey) => -1,
(SecurityKey, SymmetricSecurityKey) => 1,
// If one of the keys is backed by a X.509 certificate, don't prefer it if it's not valid yet.
(X509SecurityKey first, SecurityKey) when first.Certificate.NotBefore > now => 1,
(SecurityKey, X509SecurityKey second) when second.Certificate.NotBefore > now => -1,
// If the two keys are backed by a X.509 certificate, prefer the one with the furthest expiration date.
(X509SecurityKey first, X509SecurityKey second) => -first.Certificate.NotAfter.CompareTo(second.Certificate.NotAfter),
// If one of the keys is backed by a X.509 certificate, prefer the X.509 security key.
(X509SecurityKey, SecurityKey) => -1,
(SecurityKey, X509SecurityKey) => 1,
// If the two keys are not backed by a X.509 certificate, none should be preferred to the other.
(SecurityKey, SecurityKey) => 0
};
static string? GetKeyIdentifier(SecurityKey key)
{
// When no key identifier can be retrieved from the security keys, a value is automatically
// inferred from the hexadecimal representation of the certificate thumbprint (SHA-1)
// when the key is bound to a X.509 certificate or from the public part of the signing key.
if (key is X509SecurityKey x509SecurityKey)
{
return x509SecurityKey.Certificate.Thumbprint;
}
if (key is RsaSecurityKey rsaSecurityKey)
{
// Note: if the RSA parameters are not attached to the signing key,
// extract them by calling ExportParameters on the RSA instance.
var parameters = rsaSecurityKey.Parameters;
if (parameters.Modulus is null)
{
parameters = rsaSecurityKey.Rsa.ExportParameters(includePrivateParameters: false);
Debug.Assert(parameters.Modulus is not null, SR.GetResourceString(SR.ID4003));
}
// Only use the 40 first chars of the base64url-encoded modulus.
var identifier = Base64UrlEncoder.Encode(parameters.Modulus);
return identifier[..Math.Min(identifier.Length, 40)].ToUpperInvariant();
}
if (key is ECDsaSecurityKey ecsdaSecurityKey)
{
// Extract the ECDSA parameters from the signing credentials.
var parameters = ecsdaSecurityKey.ECDsa.ExportParameters(includePrivateParameters: false);
Debug.Assert(parameters.Q.X is not null, SR.GetResourceString(SR.ID4004));
// Only use the 40 first chars of the base64url-encoded X coordinate.
var identifier = Base64UrlEncoder.Encode(parameters.Q.X);
return identifier[..Math.Min(identifier.Length, 40)].ToUpperInvariant();
}
return null;
}
}
/// <inheritdoc/>
public ValidateOptionsResult Validate(string? name, OpenIddictServerOptions options)
{
ArgumentNullException.ThrowIfNull(options);
var builder = new ValidateOptionsResultBuilder();
if (options.JsonWebTokenHandler is null)
{
throw new InvalidOperationException(SR.GetResourceString(SR.ID0075));
builder.AddError(SR.GetResourceString(SR.ID0075));
}
// Ensure at least one flow has been enabled.
if (options.GrantTypes.Count is 0 && options.ResponseTypes.Count is 0)
{
throw new InvalidOperationException(SR.GetResourceString(SR.ID0076));
builder.AddError(SR.GetResourceString(SR.ID0076));
}
var uris = options.AuthorizationEndpointUris.Distinct()
@ -80,7 +195,7 @@ public sealed class OpenIddictServerConfiguration : IPostConfigureOptions<OpenId
// Ensure endpoint URIs are unique across endpoints.
if (uris.Count != uris.Distinct().Count())
{
throw new InvalidOperationException(SR.GetResourceString(SR.ID0285));
builder.AddError(SR.GetResourceString(SR.ID0285));
}
// Ensure the authorization endpoint has been enabled when
@ -88,13 +203,13 @@ public sealed class OpenIddictServerConfiguration : IPostConfigureOptions<OpenId
if (options.AuthorizationEndpointUris.Count is 0 && (options.GrantTypes.Contains(GrantTypes.AuthorizationCode) ||
options.GrantTypes.Contains(GrantTypes.Implicit)))
{
throw new InvalidOperationException(SR.GetResourceString(SR.ID0077));
builder.AddError(SR.GetResourceString(SR.ID0077));
}
// Ensure the device authorization endpoint has been enabled when the device grant is supported.
if (options.DeviceAuthorizationEndpointUris.Count is 0 && options.GrantTypes.Contains(GrantTypes.DeviceCode))
{
throw new InvalidOperationException(SR.GetResourceString(SR.ID0078));
builder.AddError(SR.GetResourceString(SR.ID0078));
}
// Ensure the token endpoint has been enabled when the authorization code,
@ -105,19 +220,19 @@ public sealed class OpenIddictServerConfiguration : IPostConfigureOptions<OpenId
options.GrantTypes.Contains(GrantTypes.Password) ||
options.GrantTypes.Contains(GrantTypes.RefreshToken)))
{
throw new InvalidOperationException(SR.GetResourceString(SR.ID0079));
builder.AddError(SR.GetResourceString(SR.ID0079));
}
// Ensure the end-user verification endpoint has been enabled when the device grant is supported.
if (options.EndUserVerificationEndpointUris.Count is 0 && options.GrantTypes.Contains(GrantTypes.DeviceCode))
{
throw new InvalidOperationException(SR.GetResourceString(SR.ID0080));
builder.AddError(SR.GetResourceString(SR.ID0080));
}
// Ensure the device grant is allowed when the device authorization endpoint is enabled.
if (options.DeviceAuthorizationEndpointUris.Count is > 0 && !options.GrantTypes.Contains(GrantTypes.DeviceCode))
{
throw new InvalidOperationException(SR.GetResourceString(SR.ID0084));
builder.AddError(SR.GetResourceString(SR.ID0084));
}
// Ensure the grant types/response types configuration is consistent.
@ -126,17 +241,17 @@ public sealed class OpenIddictServerConfiguration : IPostConfigureOptions<OpenId
var types = type.Split(Separators.Space, StringSplitOptions.RemoveEmptyEntries).ToHashSet(StringComparer.Ordinal);
if (types.Contains(ResponseTypes.Code) && !options.GrantTypes.Contains(GrantTypes.AuthorizationCode))
{
throw new InvalidOperationException(SR.FormatID0281(ResponseTypes.Code));
builder.AddError(SR.FormatID0281(ResponseTypes.Code));
}
if (types.Contains(ResponseTypes.IdToken) && !options.GrantTypes.Contains(GrantTypes.Implicit))
{
throw new InvalidOperationException(SR.FormatID0282(ResponseTypes.IdToken));
builder.AddError(SR.FormatID0282(ResponseTypes.IdToken));
}
if (types.Contains(ResponseTypes.Token) && !options.GrantTypes.Contains(GrantTypes.Implicit))
{
throw new InvalidOperationException(SR.FormatID0282(ResponseTypes.Token));
builder.AddError(SR.FormatID0282(ResponseTypes.Token));
}
}
@ -147,85 +262,83 @@ public sealed class OpenIddictServerConfiguration : IPostConfigureOptions<OpenId
options.RevocationEndpointUris.Count is not 0 ||
options.TokenEndpointUris.Count is not 0))
{
throw new InvalidOperationException(SR.GetResourceString(SR.ID0419));
builder.AddError(SR.GetResourceString(SR.ID0419));
}
// Ensure the client authentication methods/client assertion types configuration is consistent.
if (options.ClientAuthenticationMethods.Contains(ClientAuthenticationMethods.PrivateKeyJwt) &&
!options.ClientAssertionTypes.Contains(ClientAssertionTypes.JwtBearer))
{
throw new InvalidOperationException(SR.FormatID0420(
ClientAssertionTypes.JwtBearer, ClientAuthenticationMethods.PrivateKeyJwt));
builder.AddError(SR.FormatID0420(ClientAssertionTypes.JwtBearer, ClientAuthenticationMethods.PrivateKeyJwt));
}
if (options.ClientAuthenticationMethods.Contains(ClientAuthenticationMethods.ClientSecretJwt) &&
!options.ClientAssertionTypes.Contains(ClientAssertionTypes.JwtBearer))
{
throw new InvalidOperationException(SR.FormatID0420(
ClientAssertionTypes.JwtBearer, ClientAuthenticationMethods.ClientSecretJwt));
builder.AddError(SR.FormatID0420(ClientAssertionTypes.JwtBearer, ClientAuthenticationMethods.ClientSecretJwt));
}
// If the tls_client_auth or self_signed_tls_client_auth methods are enabled, ensure a chain policy has been set.
if (options.ClientAuthenticationMethods.Contains(ClientAuthenticationMethods.TlsClientAuth) &&
options.PublicKeyInfrastructureTlsClientAuthenticationPolicy is null)
{
throw new InvalidOperationException(SR.GetResourceString(SR.ID0505));
builder.AddError(SR.GetResourceString(SR.ID0505));
}
if (options.ClientAuthenticationMethods.Contains(ClientAuthenticationMethods.SelfSignedTlsClientAuth) &&
options.SelfSignedTlsClientAuthenticationPolicy is null)
{
throw new InvalidOperationException(SR.GetResourceString(SR.ID0506));
builder.AddError(SR.GetResourceString(SR.ID0506));
}
// Ensure at least one supported subject type is listed.
if (options.SubjectTypes.Count is 0)
{
throw new InvalidOperationException(SR.GetResourceString(SR.ID0421));
builder.AddError(SR.GetResourceString(SR.ID0421));
}
// Ensure reference tokens support was not enabled when token storage is disabled.
if (options.DisableTokenStorage && (options.UseReferenceAccessTokens || options.UseReferenceRefreshTokens))
{
throw new InvalidOperationException(SR.GetResourceString(SR.ID0083));
builder.AddError(SR.GetResourceString(SR.ID0083));
}
// Ensure authorization or end session request caching was not enabled when token storage is disabled.
if (options.DisableTokenStorage && (options.EnableAuthorizationRequestCaching || options.EnableEndSessionRequestCaching))
{
throw new InvalidOperationException(SR.GetResourceString(SR.ID0465));
builder.AddError(SR.GetResourceString(SR.ID0465));
}
// Prevent the device authorization flow from being used if token storage is disabled, unless the degraded
// mode has been enabled (in this case, additional checks will be enforced later to require custom handlers).
if (options.DisableTokenStorage && !options.EnableDegradedMode && options.GrantTypes.Contains(GrantTypes.DeviceCode))
{
throw new InvalidOperationException(SR.GetResourceString(SR.ID0367));
builder.AddError(SR.GetResourceString(SR.ID0367));
}
// Ensure at least one subject token type is configured when the token exchange grant is enabled.
if (options.SubjectTokenTypes.Count is 0 && options.GrantTypes.Contains(GrantTypes.TokenExchange))
{
throw new InvalidOperationException(SR.GetResourceString(SR.ID0486));
builder.AddError(SR.GetResourceString(SR.ID0486));
}
// Prevent internal token types from being used as subject, actor or requested token types.
if (options.SubjectTokenTypes.Any(static type => type.StartsWith(
TokenTypeIdentifiers.Prefixes.OpenIddict, StringComparison.OrdinalIgnoreCase)))
{
throw new InvalidOperationException(SR.GetResourceString(SR.ID0487));
builder.AddError(SR.GetResourceString(SR.ID0487));
}
if (options.ActorTokenTypes.Any(static type => type.StartsWith(
TokenTypeIdentifiers.Prefixes.OpenIddict, StringComparison.OrdinalIgnoreCase)))
{
throw new InvalidOperationException(SR.GetResourceString(SR.ID0488));
builder.AddError(SR.GetResourceString(SR.ID0488));
}
if (options.RequestedTokenTypes.Any(static type => type.StartsWith(
TokenTypeIdentifiers.Prefixes.OpenIddict, StringComparison.OrdinalIgnoreCase)))
{
throw new InvalidOperationException(SR.GetResourceString(SR.ID0489));
builder.AddError(SR.GetResourceString(SR.ID0489));
}
// Ensure that the default requested token type (used when the caller doesn't specify a
@ -233,28 +346,28 @@ public sealed class OpenIddictServerConfiguration : IPostConfigureOptions<OpenId
// and that the configured value is also present in the list of allowed token types.
if (string.IsNullOrEmpty(options.DefaultRequestedTokenType))
{
throw new InvalidOperationException(SR.GetResourceString(SR.ID0490));
builder.AddError(SR.GetResourceString(SR.ID0490));
}
if (options.DefaultRequestedTokenType.StartsWith(
TokenTypeIdentifiers.Prefixes.OpenIddict, StringComparison.OrdinalIgnoreCase))
{
throw new InvalidOperationException(SR.GetResourceString(SR.ID0491));
builder.AddError(SR.GetResourceString(SR.ID0491));
}
if (!options.RequestedTokenTypes.Contains(options.DefaultRequestedTokenType))
{
throw new InvalidOperationException(SR.GetResourceString(SR.ID0492));
builder.AddError(SR.GetResourceString(SR.ID0492));
}
if (options.EncryptionCredentials.Count is 0)
{
throw new InvalidOperationException(SR.GetResourceString(SR.ID0085));
builder.AddError(SR.GetResourceString(SR.ID0085));
}
if (!options.SigningCredentials.Exists(static credentials => credentials.Key is AsymmetricSecurityKey))
{
throw new InvalidOperationException(SR.GetResourceString(SR.ID0086));
builder.AddError(SR.GetResourceString(SR.ID0086));
}
var now = options.TimeProvider.GetUtcNow().LocalDateTime;
@ -264,7 +377,7 @@ public sealed class OpenIddictServerConfiguration : IPostConfigureOptions<OpenId
credentials.Key is X509SecurityKey { Certificate: X509Certificate2 certificate } &&
(certificate.NotBefore > now || certificate.NotAfter < now)))
{
throw new InvalidOperationException(SR.GetResourceString(SR.ID0087));
builder.AddError(SR.GetResourceString(SR.ID0087));
}
// If all the registered signing credentials are backed by a X.509 certificate, at least one of them must be valid.
@ -272,7 +385,7 @@ public sealed class OpenIddictServerConfiguration : IPostConfigureOptions<OpenId
credentials.Key is X509SecurityKey { Certificate: X509Certificate2 certificate } &&
(certificate.NotBefore > now || certificate.NotAfter < now)))
{
throw new InvalidOperationException(SR.GetResourceString(SR.ID0088));
builder.AddError(SR.GetResourceString(SR.ID0088));
}
// When set, the mTLS endpoint aliases MUST represent absolute HTTPS URLs.
@ -283,7 +396,7 @@ public sealed class OpenIddictServerConfiguration : IPostConfigureOptions<OpenId
!TryValidateMtlsEndpointAlias(options.MtlsTokenEndpointAliasUri) ||
!TryValidateMtlsEndpointAlias(options.MtlsUserInfoEndpointAliasUri))
{
throw new InvalidOperationException(SR.GetResourceString(SR.ID0499));
builder.AddError(SR.GetResourceString(SR.ID0499));
}
// Prevent the mTLS aliases from being configured if the corresponding endpoints haven't been enabled.
@ -294,7 +407,7 @@ public sealed class OpenIddictServerConfiguration : IPostConfigureOptions<OpenId
(options.MtlsTokenEndpointAliasUri is not null && options.TokenEndpointUris.Count is 0) ||
(options.MtlsUserInfoEndpointAliasUri is not null && options.UserInfoEndpointUris.Count is 0))
{
throw new InvalidOperationException(SR.GetResourceString(SR.ID0510));
builder.AddError(SR.GetResourceString(SR.ID0510));
}
// If at least one mTLS endpoint alias was configured, require that the issuer be explicitly set
@ -307,7 +420,7 @@ public sealed class OpenIddictServerConfiguration : IPostConfigureOptions<OpenId
options.MtlsTokenEndpointAliasUri is not null ||
options.MtlsUserInfoEndpointAliasUri is not null))
{
throw new InvalidOperationException(SR.GetResourceString(SR.ID0500));
builder.AddError(SR.GetResourceString(SR.ID0500));
}
// Ensure no end certificate was included in the PKI TLS client authentication
@ -319,13 +432,13 @@ public sealed class OpenIddictServerConfiguration : IPostConfigureOptions<OpenId
!OpenIddictHelpers.IsCertificateAuthority(certificate) ||
!OpenIddictHelpers.HasKeyUsage(certificate, X509KeyUsageFlags.KeyCertSign)))
{
throw new InvalidOperationException(SR.GetResourceString(SR.ID0501));
builder.AddError(SR.GetResourceString(SR.ID0501));
}
if (options.PublicKeyInfrastructureTlsClientAuthenticationPolicy.ExtraStore.Cast<X509Certificate2>()
.Any(static certificate => certificate.HasPrivateKey))
{
throw new InvalidOperationException(SR.GetResourceString(SR.ID0511));
builder.AddError(SR.GetResourceString(SR.ID0511));
}
#if NET
@ -334,13 +447,13 @@ public sealed class OpenIddictServerConfiguration : IPostConfigureOptions<OpenId
!OpenIddictHelpers.IsCertificateAuthority(certificate) ||
!OpenIddictHelpers.HasKeyUsage(certificate, X509KeyUsageFlags.KeyCertSign)))
{
throw new InvalidOperationException(SR.GetResourceString(SR.ID0501));
builder.AddError(SR.GetResourceString(SR.ID0501));
}
if (options.PublicKeyInfrastructureTlsClientAuthenticationPolicy.CustomTrustStore.Cast<X509Certificate2>()
.Any(static certificate => certificate.HasPrivateKey))
{
throw new InvalidOperationException(SR.GetResourceString(SR.ID0511));
builder.AddError(SR.GetResourceString(SR.ID0511));
}
#endif
}
@ -350,13 +463,13 @@ public sealed class OpenIddictServerConfiguration : IPostConfigureOptions<OpenId
{
if (options.SelfSignedTlsClientAuthenticationPolicy.ExtraStore.Count is not 0)
{
throw new InvalidOperationException(SR.GetResourceString(SR.ID0502));
builder.AddError(SR.GetResourceString(SR.ID0502));
}
#if NET
if (options.SelfSignedTlsClientAuthenticationPolicy.CustomTrustStore.Count is not 0)
{
throw new InvalidOperationException(SR.GetResourceString(SR.ID0502));
builder.AddError(SR.GetResourceString(SR.ID0502));
}
#endif
}
@ -371,7 +484,7 @@ public sealed class OpenIddictServerConfiguration : IPostConfigureOptions<OpenId
descriptor.Type is OpenIddictServerHandlerType.Custom &&
descriptor.FilterTypes.All(type => !typeof(RequireDegradedModeDisabled).IsAssignableFrom(type))))
{
throw new InvalidOperationException(SR.GetResourceString(SR.ID0089));
builder.AddError(SR.GetResourceString(SR.ID0089));
}
if (options.DeviceAuthorizationEndpointUris.Count is not 0 && !options.Handlers.Exists(static descriptor =>
@ -380,7 +493,7 @@ public sealed class OpenIddictServerConfiguration : IPostConfigureOptions<OpenId
descriptor.Type is OpenIddictServerHandlerType.Custom &&
descriptor.FilterTypes.All(type => !typeof(RequireDegradedModeDisabled).IsAssignableFrom(type))))
{
throw new InvalidOperationException(SR.GetResourceString(SR.ID0090));
builder.AddError(SR.GetResourceString(SR.ID0090));
}
if (options.IntrospectionEndpointUris.Count is not 0 && !options.Handlers.Exists(static descriptor =>
@ -389,7 +502,7 @@ public sealed class OpenIddictServerConfiguration : IPostConfigureOptions<OpenId
descriptor.Type is OpenIddictServerHandlerType.Custom &&
descriptor.FilterTypes.All(type => !typeof(RequireDegradedModeDisabled).IsAssignableFrom(type))))
{
throw new InvalidOperationException(SR.GetResourceString(SR.ID0091));
builder.AddError(SR.GetResourceString(SR.ID0091));
}
if (options.EndSessionEndpointUris.Count is not 0 && !options.Handlers.Exists(static descriptor =>
@ -397,7 +510,7 @@ public sealed class OpenIddictServerConfiguration : IPostConfigureOptions<OpenId
descriptor.Type is OpenIddictServerHandlerType.Custom &&
descriptor.FilterTypes.All(type => !typeof(RequireDegradedModeDisabled).IsAssignableFrom(type))))
{
throw new InvalidOperationException(SR.GetResourceString(SR.ID0092));
builder.AddError(SR.GetResourceString(SR.ID0092));
}
if (options.PushedAuthorizationEndpointUris.Count is not 0 && !options.Handlers.Exists(static descriptor =>
@ -406,7 +519,7 @@ public sealed class OpenIddictServerConfiguration : IPostConfigureOptions<OpenId
descriptor.Type is OpenIddictServerHandlerType.Custom &&
descriptor.FilterTypes.All(type => !typeof(RequireDegradedModeDisabled).IsAssignableFrom(type))))
{
throw new InvalidOperationException(SR.GetResourceString(SR.ID0466));
builder.AddError(SR.GetResourceString(SR.ID0466));
}
if (options.RevocationEndpointUris.Count is not 0 && !options.Handlers.Exists(static descriptor =>
@ -415,7 +528,7 @@ public sealed class OpenIddictServerConfiguration : IPostConfigureOptions<OpenId
descriptor.Type is OpenIddictServerHandlerType.Custom &&
descriptor.FilterTypes.All(type => !typeof(RequireDegradedModeDisabled).IsAssignableFrom(type))))
{
throw new InvalidOperationException(SR.GetResourceString(SR.ID0093));
builder.AddError(SR.GetResourceString(SR.ID0093));
}
if (options.TokenEndpointUris.Count is not 0 && !options.Handlers.Exists(static descriptor =>
@ -424,7 +537,7 @@ public sealed class OpenIddictServerConfiguration : IPostConfigureOptions<OpenId
descriptor.Type is OpenIddictServerHandlerType.Custom &&
descriptor.FilterTypes.All(type => !typeof(RequireDegradedModeDisabled).IsAssignableFrom(type))))
{
throw new InvalidOperationException(SR.GetResourceString(SR.ID0094));
builder.AddError(SR.GetResourceString(SR.ID0094));
}
if (options.EndUserVerificationEndpointUris.Count is not 0 && !options.Handlers.Exists(static descriptor =>
@ -432,7 +545,7 @@ public sealed class OpenIddictServerConfiguration : IPostConfigureOptions<OpenId
descriptor.Type is OpenIddictServerHandlerType.Custom &&
descriptor.FilterTypes.All(type => !typeof(RequireDegradedModeDisabled).IsAssignableFrom(type))))
{
throw new InvalidOperationException(SR.GetResourceString(SR.ID0095));
builder.AddError(SR.GetResourceString(SR.ID0095));
}
// If the degraded mode was enabled, ensure custom validation/generation handlers
@ -445,7 +558,7 @@ public sealed class OpenIddictServerConfiguration : IPostConfigureOptions<OpenId
descriptor.Type is OpenIddictServerHandlerType.Custom &&
descriptor.FilterTypes.All(static type => !typeof(RequireDegradedModeDisabled).IsAssignableFrom(type))))
{
throw new InvalidOperationException(SR.GetResourceString(SR.ID0096));
builder.AddError(SR.GetResourceString(SR.ID0096));
}
if (!options.Handlers.Exists(static descriptor =>
@ -453,183 +566,61 @@ public sealed class OpenIddictServerConfiguration : IPostConfigureOptions<OpenId
descriptor.Type is OpenIddictServerHandlerType.Custom &&
descriptor.FilterTypes.All(static type => !typeof(RequireDegradedModeDisabled).IsAssignableFrom(type))))
{
throw new InvalidOperationException(SR.GetResourceString(SR.ID0097));
builder.AddError(SR.GetResourceString(SR.ID0097));
}
}
}
// If token storage was disabled, user codes will be returned as-is by OpenIddict instead of being
// automatically converted to reference identifiers (in this case, custom event handlers must be
// registered to manually store the token payload in a database or cache and return a user code
// that can be used and entered by a human user in a web form). Since the default logic is not
// going be used, disable the formatting logic by setting UserCodeDisplayFormat to null here.
if (options.DisableTokenStorage)
{
options.UserCodeLength = 0;
options.UserCodeCharset.Clear();
options.UserCodeDisplayFormat = null;
}
else
if (!options.DisableTokenStorage)
{
if (options.UserCodeLength is < 6)
{
throw new InvalidOperationException(SR.FormatID0439(6));
builder.AddError(SR.FormatID0439(6));
}
if (options.UserCodeCharset.Count is < 9)
{
throw new InvalidOperationException(SR.FormatID0440(9));
builder.AddError(SR.FormatID0440(9));
}
if (options.UserCodeCharset.Count != options.UserCodeCharset.Distinct(StringComparer.Ordinal).Count())
{
throw new InvalidOperationException(SR.GetResourceString(SR.ID0436));
builder.AddError(SR.GetResourceString(SR.ID0436));
}
foreach (var character in options.UserCodeCharset)
{
#if NET
// On supported platforms, ensure each character added to the
// charset represents exactly one grapheme cluster/text element.
var enumerator = StringInfo.GetTextElementEnumerator(character);
if (!enumerator.MoveNext() || enumerator.MoveNext())
{
throw new InvalidOperationException(SR.GetResourceString(SR.ID0437));
}
#else
// On unsupported platforms, prevent non-ASCII characters from being used.
if (character.Any(static character => (uint) character > '\x007f'))
{
throw new InvalidOperationException(SR.GetResourceString(SR.ID0438));
}
#endif
}
if (string.IsNullOrEmpty(options.UserCodeDisplayFormat))
// On supported platforms, ensure each character added to the
// charset represents exactly one grapheme cluster/text element.
if (options.UserCodeCharset.Any(static character => !ValidateUserCodeCharacter(character)))
{
var builder = new StringBuilder();
var count = options.UserCodeLength % 5 is 0 ? 5 :
options.UserCodeLength % 4 is 0 ? 4 :
options.UserCodeLength % 3 is 0 ? 3 :
options.UserCodeLength % 2 is 0 ? 2 : 1;
for (var index = 0; index < options.UserCodeLength; index++)
{
if (index is > 0 && index % count is 0)
{
builder.Append(Separators.Dash[0]);
}
builder.Append('{');
builder.Append(index);
builder.Append('}');
}
options.UserCodeDisplayFormat = builder.ToString();
builder.AddError(SR.GetResourceString(SR.ID0437));
}
if (options.UserCodeCharset.Contains("-", StringComparer.Ordinal) &&
options.UserCodeDisplayFormat.Any(static character => character is '-'))
{
throw new InvalidOperationException(SR.FormatID0441('-'));
}
}
// Sort the handlers collection using the order associated with each handler.
options.Handlers.Sort(static (left, right) => left.Order.CompareTo(right.Order));
// Sort the encryption and signing credentials.
options.EncryptionCredentials.Sort((left, right) => Compare(left.Key, right.Key, now));
options.SigningCredentials.Sort((left, right) => Compare(left.Key, right.Key, now));
// Generate a key identifier for the encryption/signing keys that don't already have one.
foreach (var key in options.EncryptionCredentials.Select(credentials => credentials.Key)
.Concat(options.SigningCredentials.Select(credentials => credentials.Key))
.Where(key => string.IsNullOrEmpty(key.KeyId)))
{
key.KeyId = GetKeyIdentifier(key);
}
// Attach the signing credentials to the token validation parameters.
options.TokenValidationParameters.IssuerSigningKeys =
from credentials in options.SigningCredentials
select credentials.Key;
// Attach the encryption credentials to the token validation parameters.
options.TokenValidationParameters.TokenDecryptionKeys =
from credentials in options.EncryptionCredentials
select credentials.Key;
static int Compare(SecurityKey left, SecurityKey right, DateTime now) => (left, right) switch
{
// If the two keys refer to the same instances, return 0.
(SecurityKey first, SecurityKey second) when ReferenceEquals(first, second) => 0,
// If one of the keys is a symmetric key, prefer it to the other one.
(SymmetricSecurityKey, SymmetricSecurityKey) => 0,
(SymmetricSecurityKey, SecurityKey) => -1,
(SecurityKey, SymmetricSecurityKey) => 1,
// If one of the keys is backed by a X.509 certificate, don't prefer it if it's not valid yet.
(X509SecurityKey first, SecurityKey) when first.Certificate.NotBefore > now => 1,
(SecurityKey, X509SecurityKey second) when second.Certificate.NotBefore > now => -1,
// If the two keys are backed by a X.509 certificate, prefer the one with the furthest expiration date.
(X509SecurityKey first, X509SecurityKey second) => -first.Certificate.NotAfter.CompareTo(second.Certificate.NotAfter),
// If one of the keys is backed by a X.509 certificate, prefer the X.509 security key.
(X509SecurityKey, SecurityKey) => -1,
(SecurityKey, X509SecurityKey) => 1,
// If the two keys are not backed by a X.509 certificate, none should be preferred to the other.
(SecurityKey, SecurityKey) => 0
};
static string? GetKeyIdentifier(SecurityKey key)
{
// When no key identifier can be retrieved from the security keys, a value is automatically
// inferred from the hexadecimal representation of the certificate thumbprint (SHA-1)
// when the key is bound to a X.509 certificate or from the public part of the signing key.
if (key is X509SecurityKey x509SecurityKey)
#else
// On unsupported platforms, prevent non-ASCII characters from being used.
if (options.UserCodeCharset.Any(static character => character.Any(static character => (uint) character > '\x007f')))
{
return x509SecurityKey.Certificate.Thumbprint;
builder.AddError(SR.GetResourceString(SR.ID0438));
}
#endif
if (key is RsaSecurityKey rsaSecurityKey)
if (!string.IsNullOrEmpty(options.UserCodeDisplayFormat) &&
options.UserCodeCharset.Contains("-", StringComparer.Ordinal) &&
options.UserCodeDisplayFormat.Any(static character => character is '-'))
{
// Note: if the RSA parameters are not attached to the signing key,
// extract them by calling ExportParameters on the RSA instance.
var parameters = rsaSecurityKey.Parameters;
if (parameters.Modulus is null)
{
parameters = rsaSecurityKey.Rsa.ExportParameters(includePrivateParameters: false);
Debug.Assert(parameters.Modulus is not null, SR.GetResourceString(SR.ID4003));
}
// Only use the 40 first chars of the base64url-encoded modulus.
var identifier = Base64UrlEncoder.Encode(parameters.Modulus);
return identifier[..Math.Min(identifier.Length, 40)].ToUpperInvariant();
builder.AddError(SR.FormatID0441('-'));
}
if (key is ECDsaSecurityKey ecsdaSecurityKey)
#if NET
static bool ValidateUserCodeCharacter(string character)
{
// Extract the ECDSA parameters from the signing credentials.
var parameters = ecsdaSecurityKey.ECDsa.ExportParameters(includePrivateParameters: false);
Debug.Assert(parameters.Q.X is not null, SR.GetResourceString(SR.ID4004));
// Only use the 40 first chars of the base64url-encoded X coordinate.
var identifier = Base64UrlEncoder.Encode(parameters.Q.X);
return identifier[..Math.Min(identifier.Length, 40)].ToUpperInvariant();
var enumerator = StringInfo.GetTextElementEnumerator(character);
return enumerator.MoveNext() && !enumerator.MoveNext();
}
return null;
#endif
}
return builder.Build();
static bool TryValidateMtlsEndpointAlias(Uri? uri) => uri is null ||
(uri.IsAbsoluteUri && string.Equals(uri.Scheme, Uri.UriSchemeHttps, StringComparison.OrdinalIgnoreCase));
}

5
src/OpenIddict.Server/OpenIddictServerExtensions.cs

@ -98,10 +98,13 @@ public static class OpenIddictServerExtensions
builder.Services.TryAddSingleton<RequireUserCodeValidated>();
builder.Services.TryAddSingleton<RequireUserInfoRequest>();
// Note: TryAddEnumerable() is used here to ensure the initializer is registered only once.
// Note: TryAddEnumerable() is used here to ensure the initializers are registered only once.
builder.Services.TryAddEnumerable(ServiceDescriptor.Singleton<
IPostConfigureOptions<OpenIddictServerOptions>, OpenIddictServerConfiguration>());
builder.Services.TryAddEnumerable(ServiceDescriptor.Singleton<
IValidateOptions<OpenIddictServerOptions>, OpenIddictServerConfiguration>());
return new OpenIddictServerBuilder(builder.Services);
}

38
src/OpenIddict.Validation.AspNetCore/OpenIddictValidationAspNetCoreConfiguration.cs

@ -15,22 +15,20 @@ namespace OpenIddict.Validation.AspNetCore;
[EditorBrowsable(EditorBrowsableState.Advanced)]
public sealed class OpenIddictValidationAspNetCoreConfiguration : IConfigureOptions<AuthenticationOptions>,
IConfigureOptions<OpenIddictValidationOptions>,
IPostConfigureOptions<AuthenticationOptions>
IPostConfigureOptions<AuthenticationOptions>,
IValidateOptions<AuthenticationOptions>
{
/// <inheritdoc/>
public void Configure(AuthenticationOptions options)
{
ArgumentNullException.ThrowIfNull(options);
// If a handler was already registered and the type doesn't correspond to the OpenIddict handler, throw an exception.
if (options.SchemeMap.TryGetValue(OpenIddictValidationAspNetCoreDefaults.AuthenticationScheme, out var builder) &&
builder.HandlerType != typeof(OpenIddictValidationAspNetCoreHandler))
// Register the authentication scheme handler used by the OpenIddict ASP.NET Core server integration.
if (!options.SchemeMap.ContainsKey(OpenIddictValidationAspNetCoreDefaults.AuthenticationScheme))
{
throw new InvalidOperationException(SR.GetResourceString(SR.ID0164));
options.AddScheme<OpenIddictValidationAspNetCoreHandler>(
OpenIddictValidationAspNetCoreDefaults.AuthenticationScheme, displayName: null);
}
options.AddScheme<OpenIddictValidationAspNetCoreHandler>(
OpenIddictValidationAspNetCoreDefaults.AuthenticationScheme, displayName: null);
}
/// <inheritdoc/>
@ -47,12 +45,6 @@ public sealed class OpenIddictValidationAspNetCoreConfiguration : IConfigureOpti
{
ArgumentNullException.ThrowIfNull(options);
if (!TryValidate(options.SchemeMap, options.DefaultSignInScheme) ||
!TryValidate(options.SchemeMap, options.DefaultSignOutScheme))
{
throw new InvalidOperationException(SR.GetResourceString(SR.ID0165));
}
// Starting in ASP.NET 7.0, the authentication stack integrates a fallback
// mechanism to select the default scheme to use when no value is set, but
// only if a single handler has been registered in the authentication options.
@ -68,8 +60,24 @@ public sealed class OpenIddictValidationAspNetCoreConfiguration : IConfigureOpti
{
options.AddScheme<IAuthenticationHandler>(Guid.NewGuid().ToString(), displayName: null);
}
}
/// <inheritdoc/>
public ValidateOptionsResult Validate(string? name, AuthenticationOptions options)
{
ArgumentNullException.ThrowIfNull(options);
var builder = new ValidateOptionsResultBuilder();
if (!ValidateDefaultScheme(options.SchemeMap, options.DefaultSignInScheme) ||
!ValidateDefaultScheme(options.SchemeMap, options.DefaultSignOutScheme))
{
builder.AddError(SR.GetResourceString(SR.ID0165));
}
return builder.Build();
static bool TryValidate(IDictionary<string, AuthenticationSchemeBuilder> map, string? scheme)
static bool ValidateDefaultScheme(IDictionary<string, AuthenticationSchemeBuilder> map, string? scheme)
{
// If the scheme was not set or if it cannot be found in the map, return true.
if (string.IsNullOrEmpty(scheme) || !map.TryGetValue(scheme, out var builder))

5
src/OpenIddict.Validation.AspNetCore/OpenIddictValidationAspNetCoreExtensions.cs

@ -45,11 +45,14 @@ public static class OpenIddictValidationAspNetCoreExtensions
builder.Services.TryAddEnumerable(ServiceDescriptor.Singleton<
IConfigureOptions<AuthenticationOptions>, OpenIddictValidationAspNetCoreConfiguration>());
builder.Services.TryAddEnumerable(ServiceDescriptor.Singleton<
IConfigureOptions<OpenIddictValidationOptions>, OpenIddictValidationAspNetCoreConfiguration>());
builder.Services.TryAddEnumerable(ServiceDescriptor.Singleton<
IPostConfigureOptions<AuthenticationOptions>, OpenIddictValidationAspNetCoreConfiguration>());
builder.Services.TryAddEnumerable(ServiceDescriptor.Singleton<
IConfigureOptions<OpenIddictValidationOptions>, OpenIddictValidationAspNetCoreConfiguration>());
IValidateOptions<AuthenticationOptions>, OpenIddictValidationAspNetCoreConfiguration>());
return new OpenIddictValidationAspNetCoreBuilder(builder.Services);
}

10
src/OpenIddict.Validation.DataProtection/OpenIddictValidationDataProtectionConfiguration.cs

@ -17,14 +17,14 @@ namespace OpenIddict.Validation.DataProtection;
public sealed class OpenIddictValidationDataProtectionConfiguration : IConfigureOptions<OpenIddictValidationOptions>,
IPostConfigureOptions<OpenIddictValidationDataProtectionOptions>
{
private readonly IDataProtectionProvider _dataProtectionProvider;
private readonly IServiceProvider _provider;
/// <summary>
/// Creates a new instance of the <see cref="OpenIddictValidationDataProtectionConfiguration"/> class.
/// </summary>
/// <param name="dataProtectionProvider">The ASP.NET Core Data Protection provider.</param>
public OpenIddictValidationDataProtectionConfiguration(IDataProtectionProvider dataProtectionProvider)
=> _dataProtectionProvider = dataProtectionProvider;
/// <param name="provider">The service provider.</param>
public OpenIddictValidationDataProtectionConfiguration(IServiceProvider provider)
=> _provider = provider ?? throw new ArgumentNullException(nameof(provider));
/// <inheritdoc/>
public void Configure(OpenIddictValidationOptions options)
@ -40,6 +40,6 @@ public sealed class OpenIddictValidationDataProtectionConfiguration : IConfigure
{
ArgumentNullException.ThrowIfNull(options);
options.DataProtectionProvider ??= _dataProtectionProvider;
options.DataProtectionProvider ??= _provider.GetDataProtectionProvider();
}
}

39
src/OpenIddict.Validation.ServerIntegration/OpenIddictValidationServerIntegrationConfiguration.cs

@ -5,6 +5,7 @@
*/
using System.ComponentModel;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;
using OpenIddict.Server;
@ -15,67 +16,75 @@ namespace OpenIddict.Validation.ServerIntegration;
/// </summary>
[EditorBrowsable(EditorBrowsableState.Advanced)]
public sealed class OpenIddictValidationServerIntegrationConfiguration : IConfigureOptions<OpenIddictValidationOptions>,
IPostConfigureOptions<OpenIddictValidationOptions>
IValidateOptions<OpenIddictValidationOptions>
{
private readonly IOptionsMonitor<OpenIddictServerOptions> _options;
private readonly IServiceProvider _provider;
/// <summary>
/// Creates a new instance of the <see cref="OpenIddictValidationServerIntegrationConfiguration"/> class.
/// </summary>
/// <param name="options">The OpenIddict server options.</param>
public OpenIddictValidationServerIntegrationConfiguration(IOptionsMonitor<OpenIddictServerOptions> options)
=> _options = options ?? throw new ArgumentNullException(nameof(options));
/// <param name="provider">The service provider.</param>
public OpenIddictValidationServerIntegrationConfiguration(IServiceProvider provider)
=> _provider = provider ?? throw new ArgumentNullException(nameof(provider));
/// <inheritdoc/>
public void Configure(OpenIddictValidationOptions options)
{
ArgumentNullException.ThrowIfNull(options);
var settings = _provider.GetRequiredService<IOptionsMonitor<OpenIddictServerOptions>>().CurrentValue;
// Note: the issuer may be null. In this case, it will be usually provided by
// a validation handler registered by the host (e.g ASP.NET Core or OWIN/Katana).
options.Issuer = _options.CurrentValue.Issuer;
options.Issuer = settings.Issuer;
options.Configuration = new OpenIddictConfiguration
{
Issuer = options.Issuer
};
// Import the signing keys from the server configuration.
foreach (var credentials in _options.CurrentValue.SigningCredentials)
foreach (var credentials in settings.SigningCredentials)
{
options.Configuration.SigningKeys.Add(credentials.Key);
}
// Import the encryption keys from the server configuration.
options.EncryptionCredentials.AddRange(_options.CurrentValue.EncryptionCredentials);
options.EncryptionCredentials.AddRange(settings.EncryptionCredentials);
// Note: token entry validation must be enabled to be able to validate reference access tokens.
options.EnableTokenEntryValidation = _options.CurrentValue.UseReferenceAccessTokens;
options.EnableTokenEntryValidation = settings.UseReferenceAccessTokens;
}
/// <inheritdoc/>
public void PostConfigure(string? name, OpenIddictValidationOptions options)
public ValidateOptionsResult Validate(string? name, OpenIddictValidationOptions options)
{
ArgumentNullException.ThrowIfNull(options);
var builder = new ValidateOptionsResultBuilder();
var settings = _provider.GetRequiredService<IOptionsMonitor<OpenIddictServerOptions>>().CurrentValue;
if (options.ValidationType is not OpenIddictValidationType.Direct)
{
throw new InvalidOperationException(SR.GetResourceString(SR.ID0170));
builder.AddError(SR.GetResourceString(SR.ID0170));
}
// Note: authorization validation requires that authorizations have an entry
// in the database (containing at least the authorization metadata), which is
// not created if the authorization storage is disabled in the server options.
if (options.EnableAuthorizationEntryValidation && _options.CurrentValue.DisableAuthorizationStorage)
if (options.EnableAuthorizationEntryValidation && settings.DisableAuthorizationStorage)
{
throw new InvalidOperationException(SR.GetResourceString(SR.ID0171));
builder.AddError(SR.GetResourceString(SR.ID0171));
}
// Note: token validation requires that tokens have an entry in the database
// (containing at least the token metadata), which is not created if the
// token storage is disabled in the OpenIddict server options.
if (options.EnableTokenEntryValidation && _options.CurrentValue.DisableTokenStorage)
if (options.EnableTokenEntryValidation && settings.DisableTokenStorage)
{
throw new InvalidOperationException(SR.GetResourceString(SR.ID0172));
builder.AddError(SR.GetResourceString(SR.ID0172));
}
return builder.Build();
}
}

2
src/OpenIddict.Validation.ServerIntegration/OpenIddictValidationServerIntegrationExtensions.cs

@ -32,7 +32,7 @@ public static class OpenIddictValidationServerIntegrationExtensions
IConfigureOptions<OpenIddictValidationOptions>, OpenIddictValidationServerIntegrationConfiguration>());
builder.Services.TryAddEnumerable(ServiceDescriptor.Singleton<
IPostConfigureOptions<OpenIddictValidationOptions>, OpenIddictValidationServerIntegrationConfiguration>());
IValidateOptions<OpenIddictValidationOptions>, OpenIddictValidationServerIntegrationConfiguration>());
return new OpenIddictValidationServerIntegrationBuilder(builder.Services);
}

2
src/OpenIddict.Validation.SystemNetHttp/OpenIddictValidationSystemNetHttpConfiguration.cs

@ -109,9 +109,7 @@ public sealed class OpenIddictValidationSystemNetHttpConfiguration : IConfigureO
#if NET
else if (options.CurrentValue.HttpResiliencePipeline is ResiliencePipeline<HttpResponseMessage> pipeline)
{
#pragma warning disable EXTEXP0001
builder.AdditionalHandlers.Add(new ResilienceHandler(pipeline));
#pragma warning restore EXTEXP0001
}
#endif
if (builder.PrimaryHandler is not HttpClientHandler handler)

4
src/OpenIddict.Validation.SystemNetHttp/OpenIddictValidationSystemNetHttpExtensions.cs

@ -38,10 +38,10 @@ public static class OpenIddictValidationSystemNetHttpExtensions
// Note: TryAddEnumerable() is used here to ensure the initializers are registered only once.
builder.Services.TryAddEnumerable(ServiceDescriptor.Singleton<
IConfigureOptions<OpenIddictValidationOptions>, OpenIddictValidationSystemNetHttpConfiguration>());
IConfigureOptions<HttpClientFactoryOptions>, OpenIddictValidationSystemNetHttpConfiguration>());
builder.Services.TryAddEnumerable(ServiceDescriptor.Singleton<
IConfigureOptions<HttpClientFactoryOptions>, OpenIddictValidationSystemNetHttpConfiguration>());
IConfigureOptions<OpenIddictValidationOptions>, OpenIddictValidationSystemNetHttpConfiguration>());
builder.Services.TryAddEnumerable(ServiceDescriptor.Singleton<
IPostConfigureOptions<HttpClientFactoryOptions>, OpenIddictValidationSystemNetHttpConfiguration>());

196
src/OpenIddict.Validation/OpenIddictValidationConfiguration.cs

@ -15,21 +15,17 @@ namespace OpenIddict.Validation;
/// <summary>
/// Contains the methods required to ensure that the OpenIddict validation configuration is valid.
/// </summary>
public sealed class OpenIddictValidationConfiguration : IPostConfigureOptions<OpenIddictValidationOptions>
public sealed class OpenIddictValidationConfiguration : IPostConfigureOptions<OpenIddictValidationOptions>,
IValidateOptions<OpenIddictValidationOptions>
{
private readonly OpenIddictValidationService _service;
private readonly IServiceProvider _provider;
/// <summary>
/// Creates a new instance of the <see cref="OpenIddictValidationConfiguration"/> class.
/// </summary>
/// <param name="provider">The service provider.</param>
/// <param name="service">The validation service.</param>
public OpenIddictValidationConfiguration(IServiceProvider provider, OpenIddictValidationService service)
{
_provider = provider ?? throw new ArgumentNullException(nameof(provider));
_service = service ?? throw new ArgumentNullException(nameof(service));
}
public OpenIddictValidationConfiguration(IServiceProvider provider)
=> _provider = provider ?? throw new ArgumentNullException(nameof(provider));
/// <inheritdoc/>
public void PostConfigure(string? name, OpenIddictValidationOptions options)
@ -38,103 +34,25 @@ public sealed class OpenIddictValidationConfiguration : IPostConfigureOptions<Op
options.TimeProvider ??= _provider.GetService<TimeProvider>() ?? TimeProvider.System;
if (options.JsonWebTokenHandler is null)
{
throw new InvalidOperationException(SR.GetResourceString(SR.ID0075));
}
if (options.Configuration is null && options.ConfigurationManager is null &&
options.Issuer is null && options.ConfigurationEndpoint is null)
{
throw new InvalidOperationException(SR.GetResourceString(SR.ID0128));
}
if (options.Issuer is not null)
{
if (!options.Issuer.IsAbsoluteUri || OpenIddictHelpers.IsImplicitFileUri(options.Issuer))
{
throw new InvalidOperationException(SR.GetResourceString(SR.ID0136));
}
if (!string.IsNullOrEmpty(options.Issuer.Fragment) || !string.IsNullOrEmpty(options.Issuer.Query))
{
throw new InvalidOperationException(SR.GetResourceString(SR.ID0137));
}
}
if (options.ValidationType is OpenIddictValidationType.Introspection)
{
if (!options.Handlers.Exists(static descriptor => descriptor.ContextType == typeof(ApplyIntrospectionRequestContext)))
{
throw new InvalidOperationException(SR.GetResourceString(SR.ID0129));
}
if (options.Issuer is null && options.ConfigurationEndpoint is null)
{
throw new InvalidOperationException(SR.GetResourceString(SR.ID0130));
}
if (string.IsNullOrEmpty(options.ClientId))
{
throw new InvalidOperationException(SR.GetResourceString(SR.ID0131));
}
if (options.SigningCredentials.Count is 0 && string.IsNullOrEmpty(options.ClientSecret))
{
throw new InvalidOperationException(SR.GetResourceString(SR.ID0132));
}
if (options.EnableAuthorizationEntryValidation)
{
throw new InvalidOperationException(SR.GetResourceString(SR.ID0133));
}
if (options.EnableTokenEntryValidation)
{
throw new InvalidOperationException(SR.GetResourceString(SR.ID0134));
}
}
var now = options.TimeProvider.GetUtcNow().LocalDateTime;
// If all the registered encryption credentials are backed by a X.509 certificate, at least one of them must be valid.
if (options.EncryptionCredentials.Count is not 0 && options.EncryptionCredentials.TrueForAll(credentials =>
credentials.Key is X509SecurityKey { Certificate: X509Certificate2 certificate } &&
(certificate.NotBefore > now || certificate.NotAfter < now)))
{
throw new InvalidOperationException(SR.GetResourceString(SR.ID0087));
}
if (options.ConfigurationManager is null)
{
if (options.Configuration is not null)
{
if (options.Configuration.Issuer is not null &&
options.Configuration.Issuer != options.Issuer)
{
throw new InvalidOperationException(SR.GetResourceString(SR.ID0394));
}
// Note: the issuer may be null. In this case, it will be usually provided by
// a validation handler registered by the host (e.g ASP.NET Core or OWIN/Katana).
options.Configuration.Issuer ??= options.Issuer;
options.ConfigurationManager = new StaticConfigurationManager<OpenIddictConfiguration>(options.Configuration);
}
else
else if (options.Issuer is not null)
{
if (!options.Handlers.Exists(static descriptor => descriptor.ContextType == typeof(ApplyConfigurationRequestContext)) ||
!options.Handlers.Exists(static descriptor => descriptor.ContextType == typeof(ApplyJsonWebKeySetRequestContext)))
{
throw new InvalidOperationException(SR.GetResourceString(SR.ID0135));
}
options.ConfigurationEndpoint = OpenIddictHelpers.CreateAbsoluteUri(
options.ConfigurationEndpoint ??= OpenIddictHelpers.CreateAbsoluteUri(
options.Issuer,
options.ConfigurationEndpoint ?? new Uri(".well-known/openid-configuration", UriKind.Relative));
options.ConfigurationManager = new ConfigurationManager<OpenIddictConfiguration>(
options.ConfigurationEndpoint.AbsoluteUri, new OpenIddictValidationRetriever(_service))
options.ConfigurationEndpoint.AbsoluteUri,
new OpenIddictValidationRetriever(_provider.GetRequiredService<OpenIddictValidationService>()))
{
AutomaticRefreshInterval = ConfigurationManager<OpenIddictConfiguration>.DefaultAutomaticRefreshInterval,
RefreshInterval = ConfigurationManager<OpenIddictConfiguration>.DefaultRefreshInterval
@ -145,6 +63,8 @@ public sealed class OpenIddictValidationConfiguration : IPostConfigureOptions<Op
// Sort the handlers collection using the order associated with each handler.
options.Handlers.Sort((left, right) => left.Order.CompareTo(right.Order));
var now = options.TimeProvider.GetUtcNow().LocalDateTime;
// Sort the encryption and signing credentials.
options.EncryptionCredentials.Sort((left, right) => Compare(left.Key, right.Key, now));
options.SigningCredentials.Sort((left, right) => Compare(left.Key, right.Key, now));
@ -179,4 +99,100 @@ public sealed class OpenIddictValidationConfiguration : IPostConfigureOptions<Op
(SecurityKey, SecurityKey) => 0
};
}
/// <inheritdoc/>
public ValidateOptionsResult Validate(string? name, OpenIddictValidationOptions options)
{
ArgumentNullException.ThrowIfNull(options);
var builder = new ValidateOptionsResultBuilder();
if (options.JsonWebTokenHandler is null)
{
builder.AddError(SR.GetResourceString(SR.ID0075));
}
if (options.Configuration is null && options.ConfigurationManager is null &&
options.Issuer is null && options.ConfigurationEndpoint is null)
{
builder.AddError(SR.GetResourceString(SR.ID0128));
}
if (options.Issuer is not null)
{
if (!options.Issuer.IsAbsoluteUri || OpenIddictHelpers.IsImplicitFileUri(options.Issuer))
{
builder.AddError(SR.GetResourceString(SR.ID0136));
}
else if (!string.IsNullOrEmpty(options.Issuer.Fragment) || !string.IsNullOrEmpty(options.Issuer.Query))
{
builder.AddError(SR.GetResourceString(SR.ID0137));
}
// If an issuer was attached to the static configuration, ensure it matches the issuer specified in the options.
if (options.Configuration?.Issuer is not null && options.Configuration.Issuer != options.Issuer)
{
builder.AddError(SR.GetResourceString(SR.ID0394));
}
}
if (options.ConfigurationManager is null)
{
builder.AddError(SR.GetResourceString(SR.ID0523));
}
// If a non-static configuration manager is used, ensure that the required discovery handlers are registered.
else if (!typeof(StaticConfigurationManager<OpenIddictConfiguration>).IsAssignableFrom(options.ConfigurationManager.GetType()) &&
(!options.Handlers.Exists(static descriptor => descriptor.ContextType == typeof(ApplyConfigurationRequestContext)) ||
!options.Handlers.Exists(static descriptor => descriptor.ContextType == typeof(ApplyJsonWebKeySetRequestContext))))
{
builder.AddError(SR.GetResourceString(SR.ID0135));
}
if (options.ValidationType is OpenIddictValidationType.Introspection)
{
if (!options.Handlers.Exists(static descriptor => descriptor.ContextType == typeof(ApplyIntrospectionRequestContext)))
{
builder.AddError(SR.GetResourceString(SR.ID0129));
}
if (options.Issuer is null && options.ConfigurationEndpoint is null)
{
builder.AddError(SR.GetResourceString(SR.ID0130));
}
if (string.IsNullOrEmpty(options.ClientId))
{
builder.AddError(SR.GetResourceString(SR.ID0131));
}
if (options.SigningCredentials.Count is 0 && string.IsNullOrEmpty(options.ClientSecret))
{
builder.AddError(SR.GetResourceString(SR.ID0132));
}
if (options.EnableAuthorizationEntryValidation)
{
builder.AddError(SR.GetResourceString(SR.ID0133));
}
if (options.EnableTokenEntryValidation)
{
builder.AddError(SR.GetResourceString(SR.ID0134));
}
}
var now = options.TimeProvider.GetUtcNow().LocalDateTime;
// If all the registered encryption credentials are backed by a X.509 certificate, at least one of them must be valid.
if (options.EncryptionCredentials.Count is not 0 && options.EncryptionCredentials.TrueForAll(credentials =>
credentials.Key is X509SecurityKey { Certificate: X509Certificate2 certificate } &&
(certificate.NotBefore > now || certificate.NotAfter < now)))
{
builder.AddError(SR.GetResourceString(SR.ID0087));
}
return builder.Build();
}
}

5
src/OpenIddict.Validation/OpenIddictValidationExtensions.cs

@ -53,10 +53,13 @@ public static class OpenIddictValidationExtensions
builder.Services.TryAddSingleton<RequireTokenPresenterValidationEnabled>();
builder.Services.TryAddSingleton<RequireTokenProofOfPossessionValidationEnabled>();
// Note: TryAddEnumerable() is used here to ensure the initializer is registered only once.
// Note: TryAddEnumerable() is used here to ensure the initializers are registered only once.
builder.Services.TryAddEnumerable(ServiceDescriptor.Singleton<
IPostConfigureOptions<OpenIddictValidationOptions>, OpenIddictValidationConfiguration>());
builder.Services.TryAddEnumerable(ServiceDescriptor.Singleton<
IValidateOptions<OpenIddictValidationOptions>, OpenIddictValidationConfiguration>());
return new OpenIddictValidationBuilder(builder.Services);
}

23
test/OpenIddict.Client.AspNetCore.Tests/OpenIddict.Client.AspNetCore.Tests.csproj

@ -0,0 +1,23 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>$(NetCoreTargetFrameworks)</TargetFrameworks>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\..\src\OpenIddict.Client.AspNetCore\OpenIddict.Client.AspNetCore.csproj" />
<ProjectReference Include="..\..\src\OpenIddict.Core\OpenIddict.Core.csproj" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.DependencyInjection" />
<PackageReference Include="Moq" />
</ItemGroup>
<ItemGroup>
<Using Include="OpenIddict.Abstractions" />
<Using Include="OpenIddict.Abstractions.OpenIddictConstants" Static="true" />
<Using Include="OpenIddict.Abstractions.OpenIddictResources" Alias="SR" />
</ItemGroup>
</Project>

22
test/OpenIddict.Client.DataProtection.Tests/OpenIddict.Client.DataProtection.Tests.csproj

@ -0,0 +1,22 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>$(NetFrameworkTargetFrameworks);$(NetCoreTargetFrameworks)</TargetFrameworks>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\..\src\OpenIddict.Client.DataProtection\OpenIddict.Client.DataProtection.csproj" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.DependencyInjection" />
<PackageReference Include="Moq" />
</ItemGroup>
<ItemGroup>
<Using Include="OpenIddict.Abstractions" />
<Using Include="OpenIddict.Abstractions.OpenIddictConstants" Static="true" />
<Using Include="OpenIddict.Abstractions.OpenIddictResources" Alias="SR" />
</ItemGroup>
</Project>

26
test/OpenIddict.Client.Owin.Tests/OpenIddict.Client.Owin.Tests.csproj

@ -0,0 +1,26 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>$(NetFrameworkTargetFrameworks)</TargetFrameworks>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\..\src\OpenIddict.Client.Owin\OpenIddict.Client.Owin.csproj" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.DependencyInjection" />
<PackageReference Include="Moq" />
</ItemGroup>
<ItemGroup Condition=" '$(TargetFrameworkIdentifier)' == '.NETFramework' ">
<Reference Include="System.Net.Http" />
</ItemGroup>
<ItemGroup>
<Using Include="OpenIddict.Abstractions" />
<Using Include="OpenIddict.Abstractions.OpenIddictConstants" Static="true" />
<Using Include="OpenIddict.Abstractions.OpenIddictResources" Alias="SR" />
</ItemGroup>
</Project>

34
test/OpenIddict.Client.SystemIntegration.Tests/OpenIddict.Client.SystemIntegration.Tests.csproj

@ -0,0 +1,34 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>$(NetFrameworkTargetFrameworks);$(NetCoreTargetFrameworks)</TargetFrameworks>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\..\src\OpenIddict.Client.SystemIntegration\OpenIddict.Client.SystemIntegration.csproj" />
<ProjectReference Include="..\..\src\OpenIddict.Core\OpenIddict.Core.csproj" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="AngleSharp" />
<PackageReference Include="MartinCostello.Logging.XUnit" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection" />
<PackageReference Include="Moq" />
</ItemGroup>
<ItemGroup Condition=" '$(TargetFrameworkIdentifier)' == '.NETFramework' ">
<Reference Include="System.Net.Http" />
</ItemGroup>
<ItemGroup Condition=" '$(TargetFrameworkIdentifier)' == '.NETFramework' ">
<PackageReference Include="System.Linq.Async" />
<PackageReference Include="System.Net.Http.Json" />
</ItemGroup>
<ItemGroup>
<Using Include="OpenIddict.Abstractions" />
<Using Include="OpenIddict.Abstractions.OpenIddictConstants" Static="true" />
<Using Include="OpenIddict.Abstractions.OpenIddictResources" Alias="SR" />
</ItemGroup>
</Project>

26
test/OpenIddict.Client.Tests/OpenIddict.Client.Tests.csproj

@ -0,0 +1,26 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>$(NetFrameworkTargetFrameworks);$(NetCoreTargetFrameworks)</TargetFrameworks>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\..\src\OpenIddict.Client\OpenIddict.Client.csproj" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.DependencyInjection" />
<PackageReference Include="Moq" />
</ItemGroup>
<ItemGroup Condition=" '$(TargetFrameworkIdentifier)' == '.NETFramework' ">
<Reference Include="System.Net.Http" />
</ItemGroup>
<ItemGroup>
<Using Include="OpenIddict.Abstractions" />
<Using Include="OpenIddict.Abstractions.OpenIddictConstants" Static="true" />
<Using Include="OpenIddict.Abstractions.OpenIddictResources" Alias="SR" />
</ItemGroup>
</Project>

34
test/OpenIddict.Client.WebIntegration.Tests/OpenIddict.Client.WebIntegration.Tests.csproj

@ -0,0 +1,34 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>$(NetFrameworkTargetFrameworks);$(NetCoreTargetFrameworks)</TargetFrameworks>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\..\src\OpenIddict.Client.WebIntegration\OpenIddict.Client.WebIntegration.csproj" />
<ProjectReference Include="..\..\src\OpenIddict.Core\OpenIddict.Core.csproj" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="AngleSharp" />
<PackageReference Include="MartinCostello.Logging.XUnit" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection" />
<PackageReference Include="Moq" />
</ItemGroup>
<ItemGroup Condition=" '$(TargetFrameworkIdentifier)' == '.NETFramework' ">
<Reference Include="System.Net.Http" />
</ItemGroup>
<ItemGroup Condition=" '$(TargetFrameworkIdentifier)' == '.NETFramework' ">
<PackageReference Include="System.Linq.Async" />
<PackageReference Include="System.Net.Http.Json" />
</ItemGroup>
<ItemGroup>
<Using Include="OpenIddict.Abstractions" />
<Using Include="OpenIddict.Abstractions.OpenIddictConstants" Static="true" />
<Using Include="OpenIddict.Abstractions.OpenIddictResources" Alias="SR" />
</ItemGroup>
</Project>

23
test/OpenIddict.Server.AspNetCore.Tests/OpenIddict.Server.AspNetCore.Tests.csproj

@ -0,0 +1,23 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>$(NetCoreTargetFrameworks)</TargetFrameworks>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\..\src\OpenIddict.Server.AspNetCore\OpenIddict.Server.AspNetCore.csproj" />
<ProjectReference Include="..\..\src\OpenIddict.Core\OpenIddict.Core.csproj" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.DependencyInjection" />
<PackageReference Include="Moq" />
</ItemGroup>
<ItemGroup>
<Using Include="OpenIddict.Abstractions" />
<Using Include="OpenIddict.Abstractions.OpenIddictConstants" Static="true" />
<Using Include="OpenIddict.Abstractions.OpenIddictResources" Alias="SR" />
</ItemGroup>
</Project>

6
test/OpenIddict.Server.DataProtection.Tests/OpenIddict.Server.DataProtection.Tests.csproj

@ -5,12 +5,12 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Moq" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection" />
<ProjectReference Include="..\..\src\OpenIddict.Server.DataProtection\OpenIddict.Server.DataProtection.csproj" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\src\OpenIddict.Server.DataProtection\OpenIddict.Server.DataProtection.csproj" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection" />
<PackageReference Include="Moq" />
</ItemGroup>
<ItemGroup>

10
test/OpenIddict.Server.IntegrationTests/OpenIddict.Server.IntegrationTests.csproj

@ -4,11 +4,6 @@
<TargetFrameworks>$(NetFrameworkTargetFrameworks);$(NetCoreTargetFrameworks)</TargetFrameworks>
</PropertyGroup>
<ItemGroup>
<EmbeddedResource Include="Certificate.cer" />
<EmbeddedResource Include="Certificate.pfx" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\src\OpenIddict.Core\OpenIddict.Core.csproj" />
<ProjectReference Include="..\..\src\OpenIddict.Server\OpenIddict.Server.csproj" />
@ -30,6 +25,11 @@
<PackageReference Include="System.Net.Http.Json" />
</ItemGroup>
<ItemGroup>
<EmbeddedResource Include="Certificate.cer" />
<EmbeddedResource Include="Certificate.pfx" />
</ItemGroup>
<ItemGroup>
<Using Include="OpenIddict.Abstractions" />
<Using Include="OpenIddict.Abstractions.OpenIddictConstants" Static="true" />

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

@ -9,6 +9,8 @@ using System.Net.Http;
using System.Security.Cryptography;
using System.Text.Json;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection.Extensions;
using Microsoft.Extensions.Options;
using Microsoft.IdentityModel.Tokens;
using Moq;
using Xunit;
@ -611,12 +613,17 @@ public abstract partial class OpenIddictServerIntegrationTests
public async Task HandleConfigurationRequest_ConfiguredGrantTypesAreReturned()
{
// Arrange
await using var server = await CreateServerAsync(options => options.Services.PostConfigure<OpenIddictServerOptions>(options =>
await using var server = await CreateServerAsync(options =>
{
options.GrantTypes.Clear();
options.GrantTypes.Add(GrantTypes.AuthorizationCode);
options.GrantTypes.Add(GrantTypes.Password);
}));
options.Services.PostConfigure<OpenIddictServerOptions>(options =>
{
options.GrantTypes.Clear();
options.GrantTypes.Add(GrantTypes.AuthorizationCode);
options.GrantTypes.Add(GrantTypes.Password);
});
options.Services.RemoveAll<IValidateOptions<OpenIddictServerOptions>>();
});
await using var client = await server.CreateClientAsync();

26
test/OpenIddict.Server.Owin.Tests/OpenIddict.Server.Owin.Tests.csproj

@ -0,0 +1,26 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>$(NetFrameworkTargetFrameworks)</TargetFrameworks>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\..\src\OpenIddict.Server.Owin\OpenIddict.Server.Owin.csproj" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.DependencyInjection" />
<PackageReference Include="Moq" />
</ItemGroup>
<ItemGroup Condition=" '$(TargetFrameworkIdentifier)' == '.NETFramework' ">
<Reference Include="System.Net.Http" />
</ItemGroup>
<ItemGroup>
<Using Include="OpenIddict.Abstractions" />
<Using Include="OpenIddict.Abstractions.OpenIddictConstants" Static="true" />
<Using Include="OpenIddict.Abstractions.OpenIddictResources" Alias="SR" />
</ItemGroup>
</Project>

12
test/OpenIddict.Server.Tests/OpenIddict.Server.Tests.csproj

@ -5,22 +5,22 @@
</PropertyGroup>
<ItemGroup>
<EmbeddedResource Include="Certificate.pfx" />
<ProjectReference Include="..\..\src\OpenIddict.Server\OpenIddict.Server.csproj" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Moq" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\src\OpenIddict.Server\OpenIddict.Server.csproj" />
<PackageReference Include="Moq" />
</ItemGroup>
<ItemGroup Condition=" '$(TargetFrameworkIdentifier)' == '.NETFramework' ">
<Reference Include="System.Net.Http" />
</ItemGroup>
<ItemGroup>
<EmbeddedResource Include="Certificate.pfx" />
</ItemGroup>
<ItemGroup>
<Using Include="OpenIddict.Abstractions" />
<Using Include="OpenIddict.Abstractions.OpenIddictConstants" Static="true" />

23
test/OpenIddict.Validation.AspNetCore.Tests/OpenIddict.Validation.AspNetCore.Tests.csproj

@ -0,0 +1,23 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>$(NetCoreTargetFrameworks)</TargetFrameworks>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\..\src\OpenIddict.Validation.AspNetCore\OpenIddict.Validation.AspNetCore.csproj" />
<ProjectReference Include="..\..\src\OpenIddict.Core\OpenIddict.Core.csproj" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.DependencyInjection" />
<PackageReference Include="Moq" />
</ItemGroup>
<ItemGroup>
<Using Include="OpenIddict.Abstractions" />
<Using Include="OpenIddict.Abstractions.OpenIddictConstants" Static="true" />
<Using Include="OpenIddict.Abstractions.OpenIddictResources" Alias="SR" />
</ItemGroup>
</Project>

22
test/OpenIddict.Validation.DataProtection.Tests/OpenIddict.Validation.DataProtection.Tests.csproj

@ -0,0 +1,22 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>$(NetFrameworkTargetFrameworks);$(NetCoreTargetFrameworks)</TargetFrameworks>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\..\src\OpenIddict.Validation.DataProtection\OpenIddict.Validation.DataProtection.csproj" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.DependencyInjection" />
<PackageReference Include="Moq" />
</ItemGroup>
<ItemGroup>
<Using Include="OpenIddict.Abstractions" />
<Using Include="OpenIddict.Abstractions.OpenIddictConstants" Static="true" />
<Using Include="OpenIddict.Abstractions.OpenIddictResources" Alias="SR" />
</ItemGroup>
</Project>

8
test/OpenIddict.Validation.IntegrationTests/OpenIddict.Validation.IntegrationTests.csproj

@ -4,10 +4,6 @@
<TargetFrameworks>$(NetFrameworkTargetFrameworks);$(NetCoreTargetFrameworks)</TargetFrameworks>
</PropertyGroup>
<ItemGroup>
<EmbeddedResource Include="Certificate.cer" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\src\OpenIddict.Core\OpenIddict.Core.csproj" />
<ProjectReference Include="..\..\src\OpenIddict.Validation\OpenIddict.Validation.csproj" />
@ -29,6 +25,10 @@
<PackageReference Include="System.Net.Http.Json" />
</ItemGroup>
<ItemGroup>
<EmbeddedResource Include="Certificate.cer" />
</ItemGroup>
<ItemGroup>
<Using Include="OpenIddict.Abstractions" />
<Using Include="OpenIddict.Abstractions.OpenIddictConstants" Static="true" />

5
test/OpenIddict.Validation.IntegrationTests/OpenIddictValidationIntegrationTests.cs

@ -10,6 +10,7 @@ using System.Reflection;
using System.Security.Claims;
using System.Security.Cryptography.X509Certificates;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection.Extensions;
using Microsoft.Extensions.Options;
using Microsoft.IdentityModel.Protocols;
using Microsoft.IdentityModel.Tokens;
@ -86,6 +87,8 @@ public abstract partial class OpenIddictValidationIntegrationTests
await using var server = await CreateServerAsync(options =>
{
options.Configure(options => options.ConfigurationManager = manager);
options.Services.RemoveAll<IValidateOptions<OpenIddictValidationOptions>>();
});
await using var client = await server.CreateClientAsync();
@ -108,6 +111,8 @@ public abstract partial class OpenIddictValidationIntegrationTests
await using var server = await CreateServerAsync(options =>
{
options.Configure(options => options.ConfigurationManager = manager);
options.Services.RemoveAll<IValidateOptions<OpenIddictValidationOptions>>();
});
await using var client = await server.CreateClientAsync();

26
test/OpenIddict.Validation.Owin.Tests/OpenIddict.Validation.Owin.Tests.csproj

@ -0,0 +1,26 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>$(NetFrameworkTargetFrameworks)</TargetFrameworks>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\..\src\OpenIddict.Validation.Owin\OpenIddict.Validation.Owin.csproj" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.DependencyInjection" />
<PackageReference Include="Moq" />
</ItemGroup>
<ItemGroup Condition=" '$(TargetFrameworkIdentifier)' == '.NETFramework' ">
<Reference Include="System.Net.Http" />
</ItemGroup>
<ItemGroup>
<Using Include="OpenIddict.Abstractions" />
<Using Include="OpenIddict.Abstractions.OpenIddictConstants" Static="true" />
<Using Include="OpenIddict.Abstractions.OpenIddictResources" Alias="SR" />
</ItemGroup>
</Project>

26
test/OpenIddict.Validation.Tests/OpenIddict.Validation.Tests.csproj

@ -0,0 +1,26 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>$(NetFrameworkTargetFrameworks);$(NetCoreTargetFrameworks)</TargetFrameworks>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\..\src\OpenIddict.Validation\OpenIddict.Validation.csproj" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.DependencyInjection" />
<PackageReference Include="Moq" />
</ItemGroup>
<ItemGroup Condition=" '$(TargetFrameworkIdentifier)' == '.NETFramework' ">
<Reference Include="System.Net.Http" />
</ItemGroup>
<ItemGroup>
<Using Include="OpenIddict.Abstractions" />
<Using Include="OpenIddict.Abstractions.OpenIddictConstants" Static="true" />
<Using Include="OpenIddict.Abstractions.OpenIddictResources" Alias="SR" />
</ItemGroup>
</Project>
Loading…
Cancel
Save