Browse Source

Update the web integration to use native options and revamp how user agents are generated

pull/1518/head
Kévin Chalet 3 years ago
parent
commit
2a7becd6d3
  1. 639
      gen/OpenIddict.Client.WebIntegration.Generators/OpenIddictClientWebIntegrationGenerator.cs
  2. 31
      sandbox/OpenIddict.Sandbox.AspNet.Client/Startup.cs
  3. 13
      sandbox/OpenIddict.Sandbox.AspNet.Server/Startup.cs
  4. 41
      sandbox/OpenIddict.Sandbox.AspNetCore.Client/Startup.cs
  5. 14
      sandbox/OpenIddict.Sandbox.AspNetCore.Server/Startup.cs
  6. 16
      src/OpenIddict.Client.AspNetCore/OpenIddictClientAspNetCoreBuilder.cs
  7. 16
      src/OpenIddict.Client.Owin/OpenIddictClientOwinBuilder.cs
  8. 34
      src/OpenIddict.Client.SystemNetHttp/OpenIddictClientSystemNetHttpBuilder.cs
  9. 13
      src/OpenIddict.Client.SystemNetHttp/OpenIddictClientSystemNetHttpConfiguration.cs
  10. 17
      src/OpenIddict.Client.SystemNetHttp/OpenIddictClientSystemNetHttpHandlers.cs
  11. 9
      src/OpenIddict.Client.SystemNetHttp/OpenIddictClientSystemNetHttpOptions.cs
  12. 2
      src/OpenIddict.Client.WebIntegration/OpenIddictClientWebIntegrationConstants.cs
  13. 15
      src/OpenIddict.Client.WebIntegration/OpenIddictClientWebIntegrationEnvironments.cs
  14. 4
      src/OpenIddict.Client.WebIntegration/OpenIddictClientWebIntegrationHandlers.Discovery.cs
  15. 21
      src/OpenIddict.Client.WebIntegration/OpenIddictClientWebIntegrationHandlers.Exchange.cs
  16. 2
      src/OpenIddict.Client.WebIntegration/OpenIddictClientWebIntegrationHandlers.Protection.cs
  17. 11
      src/OpenIddict.Client.WebIntegration/OpenIddictClientWebIntegrationHandlers.Userinfo.cs
  18. 59
      src/OpenIddict.Client.WebIntegration/OpenIddictClientWebIntegrationHandlers.cs
  19. 31
      src/OpenIddict.Client.WebIntegration/OpenIddictClientWebIntegrationHelpers.cs
  20. 7
      src/OpenIddict.Client.WebIntegration/OpenIddictClientWebIntegrationOptions.cs
  21. 46
      src/OpenIddict.Client.WebIntegration/OpenIddictClientWebIntegrationProvider.cs
  22. 12
      src/OpenIddict.Client.WebIntegration/OpenIddictClientWebIntegrationProviders.xml
  23. 43
      src/OpenIddict.Client.WebIntegration/OpenIddictClientWebIntegrationSettings.cs
  24. 16
      src/OpenIddict.Client/OpenIddictClientBuilder.cs
  25. 22
      src/OpenIddict.Validation.SystemNetHttp/OpenIddictValidationSystemNetHttpBuilder.cs
  26. 5
      src/OpenIddict.Validation.SystemNetHttp/OpenIddictValidationSystemNetHttpConfiguration.cs
  27. 17
      src/OpenIddict.Validation.SystemNetHttp/OpenIddictValidationSystemNetHttpHandlers.cs
  28. 7
      src/OpenIddict.Validation.SystemNetHttp/OpenIddictValidationSystemNetHttpOptions.cs

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

@ -1,4 +1,5 @@
using System.Text;
using System.Globalization;
using System.Text;
using System.Xml.Linq;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.Text;
@ -34,24 +35,22 @@ namespace OpenIddict.Client.WebIntegration.Generators
"OpenIddictClientWebIntegrationConstants.generated.cs",
SourceText.From(GenerateConstants(document), Encoding.UTF8));
context.AddSource(
"OpenIddictClientWebIntegrationEnvironments.generated.cs",
SourceText.From(GenerateEnvironments(document), Encoding.UTF8));
context.AddSource(
"OpenIddictClientWebIntegrationHelpers.generated.cs",
SourceText.From(GenerateHelpers(document), Encoding.UTF8));
context.AddSource(
"OpenIddictClientWebIntegrationSettings.generated.cs",
SourceText.From(GenerateSettings(document), Encoding.UTF8));
"OpenIddictClientWebIntegrationOptions.generated.cs",
SourceText.From(GenerateOptions(document), Encoding.UTF8));
static string GenerateBuilderMethods(XDocument document)
{
var template = Template.Parse(@"#nullable enable
using System.ComponentModel;
using Microsoft.Extensions.DependencyInjection.Extensions;
using Microsoft.Extensions.Options;
using Microsoft.IdentityModel.Tokens;
using OpenIddict.Client;
using OpenIddict.Client.WebIntegration;
using static OpenIddict.Client.WebIntegration.OpenIddictClientWebIntegrationConstants;
@ -62,27 +61,162 @@ public partial class OpenIddictClientWebIntegrationBuilder
{
{{~ for provider in providers ~}}
/// <summary>
/// Enables {{ provider.name }} integration using the specified settings.
/// Enables {{ provider.name }} integration using the specified options.
{{~ if provider.documentation ~}}
/// For more information, visit <see href=""{{ provider.documentation }}"">the official website</see>.
/// </summary>
{{~ end ~}}
/// <remarks>This extension can be safely called multiple times.</remarks>
/// <returns>The <see cref=""OpenIddictClientWebIntegrationBuilder.{{ provider.name }}""/>.</returns>
public OpenIddictClientWebIntegrationBuilder.{{ provider.name }} Use{{ provider.name }}()
{
// Note: TryAddEnumerable() is used here to ensure the initializers are registered only once.
Services.TryAddEnumerable(new[]
{
ServiceDescriptor.Singleton<
IConfigureOptions<OpenIddictClientOptions>, OpenIddictClientWebIntegrationConfiguration.{{ provider.name }}>(),
ServiceDescriptor.Singleton<
IPostConfigureOptions<OpenIddictClientWebIntegrationOptions.{{ provider.name }}>, OpenIddictClientWebIntegrationConfiguration.{{ provider.name }}>()
});
return new OpenIddictClientWebIntegrationBuilder.{{ provider.name }}(Services);
}
/// <summary>
/// Enables {{ provider.name }} integration using the specified options.
{{~ if provider.documentation ~}}
/// <remarks>
/// For more information about {{ provider.name }} integration, visit <see href=""{{ provider.documentation }}"">the official website</see>.
/// </remarks>
/// For more information, visit <see href=""{{ provider.documentation }}"">the official website</see>.
/// </summary>
{{~ end ~}}
/// <param name=""settings"">The provider settings.</param>
/// <remarks>This extension can be safely called multiple times.</remarks>
/// <param name=""configuration"">The delegate used to configure the OpenIddict/{{ provider.name }} options.</param>
/// <returns>The <see cref=""OpenIddictClientWebIntegrationBuilder""/>.</returns>
public OpenIddictClientWebIntegrationBuilder Add{{ provider.name }}(OpenIddictClientWebIntegrationSettings.{{ provider.name }} settings)
public OpenIddictClientWebIntegrationBuilder Use{{ provider.name }}(Action<OpenIddictClientWebIntegrationBuilder.{{ provider.name }}> configuration)
{
if (settings is null)
if (configuration is null)
{
throw new ArgumentNullException(nameof(settings));
throw new ArgumentNullException(nameof(configuration));
}
// Note: TryAddEnumerable() is used here to ensure the initializer is registered only once.
Services.TryAddEnumerable(ServiceDescriptor.Singleton<
IConfigureOptions<OpenIddictClientOptions>, OpenIddictClientWebIntegrationConfiguration.{{ provider.name }}>());
configuration(Use{{ provider.name }}());
return Configure(options => options.Providers.Add(new OpenIddictClientWebIntegrationProvider(Providers.{{ provider.name }}, settings)));
return this;
}
{{~ end ~}}
{{~ for provider in providers ~}}
/// <summary>
/// Exposes the necessary methods required to configure the {{ provider.name }} integration.
/// </summary>
public class {{ provider.name }}
{
/// <summary>
/// Initializes a new instance of <see cref=""OpenIddictClientWebIntegrationBuilder.{{ provider.name }}""/>.
/// </summary>
/// <param name=""services"">The services collection.</param>
public {{ provider.name }}(IServiceCollection services)
=> Services = services ?? throw new ArgumentNullException(nameof(services));
/// <summary>
/// Gets the services collection.
/// </summary>
[EditorBrowsable(EditorBrowsableState.Never)]
public IServiceCollection Services { get; }
/// <summary>
/// Amends the default OpenIddict client {{ provider.name }} configuration.
/// </summary>
/// <param name=""configuration"">The delegate used to configure the OpenIddict options.</param>
/// <remarks>This extension can be safely called multiple times.</remarks>
/// <returns>The <see cref=""OpenIddictClientWebIntegrationBuilder.{{ provider.name }}""/>.</returns>
public {{ provider.name }} Configure(Action<OpenIddictClientWebIntegrationOptions.{{ provider.name }}> configuration)
{
if (configuration is null)
{
throw new ArgumentNullException(nameof(configuration));
}
Services.Configure(configuration);
return this;
}
/// <summary>
/// Sets the client identifier.
/// </summary>
/// <param name=""identifier"">The client identifier.</param>
/// <returns>The <see cref=""OpenIddictClientWebIntegrationBuilder.{{ provider.name }}""/>.</returns>
public {{ provider.name }} SetClientId(string identifier)
=> Configure(options => options.ClientId = identifier);
/// <summary>
/// Sets the client secret, if applicable.
/// </summary>
/// <param name=""secret"">The client secret.</param>
/// <returns>The <see cref=""OpenIddictClientWebIntegrationBuilder.{{ provider.name }}""/>.</returns>
public {{ provider.name }} SetClientSecret(string secret)
=> Configure(options => options.ClientSecret = secret);
/// <summary>
/// Sets the redirection URI, if applicable.
/// </summary>
/// <param name=""address"">The redirection URI.</param>
/// <returns>The <see cref=""OpenIddictClientWebIntegrationBuilder.{{ provider.name }}""/>.</returns>
public {{ provider.name }} SetRedirectUri(Uri? address)
=> Configure(options => options.RedirectUri = address);
/// <summary>
/// Sets the redirection URI, if applicable.
/// </summary>
/// <param name=""address"">The redirection URI.</param>
/// <returns>The <see cref=""OpenIddictClientWebIntegrationBuilder.{{ provider.name }}""/>.</returns>
public {{ provider.name }} SetRedirectUri(string? address)
=> SetRedirectUri(!string.IsNullOrEmpty(address) ? new Uri(address, UriKind.RelativeOrAbsolute) : null);
/// <summary>
/// Adds one or more scopes to the list of requested scopes, if applicable.
/// </summary>
/// <param name=""scopes"">The scopes.</param>
/// <returns>The <see cref=""OpenIddictClientWebIntegrationBuilder.{{ provider.name }}""/>.</returns>
public {{ provider.name }} AddScopes(params string[] scopes)
=> Configure(options => options.Scopes.UnionWith(scopes));
{{~ for environment in provider.environments ~}}
/// <summary>
/// Configures the provider to use the ""{{ environment.name }}"" environment.
/// </summary>
/// <returns>The <see cref=""OpenIddictClientWebIntegrationBuilder.{{ provider.name }}""/>.</returns>
public {{ provider.name }} Use{{ environment.name }}Environment()
=> Configure(options => options.Environment = OpenIddictClientWebIntegrationConstants.{{ provider.name }}.Environments.{{ environment.name }});
{{~ end ~}}
{{~ for setting in provider.settings ~}}
{{~ if setting.description ~}}
/// <summary>
/// Configures {{ setting.description }}.
/// </summary>
{{~ end ~}}
{{~ if setting.collection ~}}
public {{ provider.name }} Add{{ setting.name }}(params {{ setting.clr_type }}[] values)
=> Configure(options => options.{{ setting.name }}.UnionWith(values));
{{~ else ~}}
public {{ provider.name }} Set{{ setting.name }}({{ setting.clr_type }}? value)
=> Configure(options => options.{{ setting.name }} = value);
{{~ end ~}}
{{~ end ~}}
/// <inheritdoc/>
[EditorBrowsable(EditorBrowsableState.Never)]
public override bool Equals(object? obj) => base.Equals(obj);
/// <inheritdoc/>
[EditorBrowsable(EditorBrowsableState.Never)]
public override int GetHashCode() => base.GetHashCode();
/// <inheritdoc/>
[EditorBrowsable(EditorBrowsableState.Never)]
public override string? ToString() => base.ToString();
}
{{~ end ~}}
}
@ -93,7 +227,39 @@ public partial class OpenIddictClientWebIntegrationBuilder
.Select(provider => new
{
Name = (string) provider.Attribute("Name"),
Documentation = (string?) provider.Attribute("Documentation")
Documentation = (string?) provider.Attribute("Documentation"),
Environments = provider.Elements("Environment").Select(environment => new
{
Name = (string?) environment.Attribute("Name") ?? "Production"
})
.ToList(),
Settings = provider.Elements("Setting").Select(setting => new
{
Name = (string) setting.Attribute("Name"),
Collection = (bool?) setting.Attribute("Collection") ?? false,
Description = (string) setting.Attribute("Description") is string description ?
char.ToLower(description[0], CultureInfo.GetCultureInfo("en-US")) + description.Substring(1) : null,
ClrType = (string) setting.Attribute("Type") switch
{
"EncryptionKey" when (string) setting.Element("EncryptionAlgorithm").Attribute("Value")
is "RS256" or "RS384" or "RS512" => "RsaSecurityKey",
"SigningKey" when (string) setting.Element("SigningAlgorithm").Attribute("Value")
is "ES256" or "ES384" or "ES512" => "ECDsaSecurityKey",
"SigningKey" when (string) setting.Element("SigningAlgorithm").Attribute("Value")
is "PS256" or "PS384" or "PS512" or
"RS256" or "RS384" or "RS512" => "RsaSecurityKey",
"String" => "string",
"StringHashSet" => "HashSet<string>",
string value => value
}
})
.ToList()
})
.ToList()
});
@ -107,6 +273,18 @@ namespace OpenIddict.Client.WebIntegration;
public static partial class OpenIddictClientWebIntegrationConstants
{
{{~ for provider in providers ~}}
public static class {{ provider.name }}
{
public static class Environments
{
{{~ for environment in provider.environments ~}}
public const string {{ environment.name }} = ""{{ environment.name }}"";
{{~ end ~}}
}
}
{{~ end ~}}
public static class Providers
{
{{~ for provider in providers ~}}
@ -114,35 +292,6 @@ public static partial class OpenIddictClientWebIntegrationConstants
{{~ end ~}}
}
}
");
return template.Render(new
{
Providers = document.Root.Elements("Provider")
.Select(provider => new { Name = (string) provider.Attribute("Name") })
.ToList()
});
}
static string GenerateEnvironments(XDocument document)
{
var template = Template.Parse(@"#nullable enable
namespace OpenIddict.Client.WebIntegration;
public partial class OpenIddictClientWebIntegrationEnvironments
{
{{~ for provider in providers ~}}
/// <summary>
/// Exposes the environments supported by the {{ provider.name }} provider.
/// </summary>
public enum {{ provider.name }}
{
{{~ for environment in provider.environments ~}}
{{ environment.name }},
{{~ end ~}}
}
{{~ end ~}}
}
");
return template.Render(new
{
@ -155,7 +304,7 @@ public partial class OpenIddictClientWebIntegrationEnvironments
{
Name = (string?) environment.Attribute("Name") ?? "Production"
})
.ToList()
.ToList(),
})
.ToList()
});
@ -165,6 +314,7 @@ public partial class OpenIddictClientWebIntegrationEnvironments
{
var template = Template.Parse(@"#nullable enable
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;
using Microsoft.IdentityModel.Tokens;
using OpenIddict.Client;
@ -181,226 +331,224 @@ public partial class OpenIddictClientWebIntegrationConfiguration
/// <summary>
/// Contains the methods required to register the {{ provider.name }} integration in the OpenIddict client options.
/// </summary>
public class {{ provider.name }} : IConfigureOptions<OpenIddictClientOptions>
public class {{ provider.name }} : IConfigureOptions<OpenIddictClientOptions>,
IPostConfigureOptions<OpenIddictClientWebIntegrationOptions.{{ provider.name }}>
{
private readonly IOptions<OpenIddictClientWebIntegrationOptions> _options;
private readonly IServiceProvider _provider;
/// <summary>
/// Creates a new instance of the <see cref=""OpenIddictClientWebIntegrationConfiguration.{{ provider.name }}"" /> class.
/// </summary>
/// <param name=""options"">The OpenIddict client web integration options.</param>
/// <exception cref=""ArgumentException""><paramref name=""options""/> is null.</exception>
public {{ provider.name }}(IOptions<OpenIddictClientWebIntegrationOptions> options)
=> _options = options ?? throw new ArgumentNullException(nameof(options));
/// <param name=""provider"">The service provider.</param>
/// <exception cref=""ArgumentException""><paramref name=""provider""/> is null.</exception>
public {{ provider.name }}(IServiceProvider provider)
=> _provider = provider ?? throw new ArgumentNullException(nameof(provider));
/// <summary>
/// Ensures the {{ provider.name }} configuration is in a consistent and valid state
/// and registers the {{ provider.name }} integration in the OpenIddict client options.
/// Ensures the {{ provider.name }} configuration is in a consistent and valid state.
/// </summary>
/// <param name=""name"">The name of the options instance to configure, if applicable.</param>
/// <param name=""options"">The options instance to initialize.</param>
public void Configure(OpenIddictClientOptions options)
public void PostConfigure(string name, OpenIddictClientWebIntegrationOptions.{{ provider.name }} options)
{
foreach (var provider in _options.Value.Providers)
if (string.IsNullOrEmpty(options.ClientId))
{
if (provider.Name is not Providers.{{ provider.name }})
{
continue;
}
if (provider.Settings is not OpenIddictClientWebIntegrationSettings.{{ provider.name }} settings)
{
throw new InvalidOperationException(SR.FormatID0331(Providers.{{ provider.name }}));
}
if (string.IsNullOrEmpty(settings.ClientId))
{
throw new InvalidOperationException(SR.FormatID0332(nameof(settings.ClientId), Providers.{{ provider.name }}));
}
throw new InvalidOperationException(SR.FormatID0332(nameof(options.ClientId), Providers.{{ provider.name }}));
}
if (settings.RedirectUri is null)
{
throw new InvalidOperationException(SR.FormatID0332(nameof(settings.RedirectUri), Providers.{{ provider.name }}));
}
if (options.RedirectUri is null)
{
throw new InvalidOperationException(SR.FormatID0332(nameof(options.RedirectUri), Providers.{{ provider.name }}));
}
{{~ for setting in provider.settings ~}}
{{~ if setting.required ~}}
{{~ if setting.type == 'String' ~}}
if (string.IsNullOrEmpty(settings.{{ setting.name }}))
{{~ else ~}}
if (settings.{{ setting.name }} is null)
{{~ end ~}}
{
throw new InvalidOperationException(SR.FormatID0332(nameof(settings.{{ setting.name }}), Providers.{{ provider.name }}));
}
{{~ end ~}}
{{~ end ~}}
{{~ for setting in provider.settings ~}}
{{~ if setting.required ~}}
{{~ if setting.type == 'String' ~}}
if (string.IsNullOrEmpty(options.{{ setting.name }}))
{{~ else ~}}
if (options.{{ setting.name }} is null)
{{~ end ~}}
{
throw new InvalidOperationException(SR.FormatID0332(nameof(options.{{ setting.name }}), Providers.{{ provider.name }}));
}
{{~ end ~}}
{{~ end ~}}
{{~ for environment in provider.environments ~}}
if (settings.Environment is OpenIddictClientWebIntegrationEnvironments.{{ provider.name }}.{{ environment.name }})
{{~ for environment in provider.environments ~}}
if (options.Environment is OpenIddictClientWebIntegrationConstants.{{ provider.name }}.Environments.{{ environment.name }})
{
if (options.Scopes.Count is 0)
{
if (settings.Scopes.Count is 0)
{
{{~ for scope in environment.scopes ~}}
{{~ if scope.default && !scope.required ~}}
settings.Scopes.Add(""{{ scope.name }}"");
{{~ end ~}}
{{~ end ~}}
}
{{~ for scope in environment.scopes ~}}
{{~ if scope.required ~}}
settings.Scopes.Add(""{{ scope.name }}"");
{{~ if scope.default && !scope.required ~}}
options.Scopes.Add(""{{ scope.name }}"");
{{~ end ~}}
{{~ end ~}}
}
{{~ end ~}}
{{~ for setting in provider.settings ~}}
{{~ if setting.default_value && setting.type == 'String' ~}}
if (string.IsNullOrEmpty(settings.{{ setting.name }}))
{
settings.{{ setting.name }} = ""{{ setting.default_value }}"";
}
{{~ for scope in environment.scopes ~}}
{{~ if scope.required ~}}
options.Scopes.Add(""{{ scope.name }}"");
{{~ end ~}}
{{~ end ~}}
}
{{~ end ~}}
{{~ for setting in provider.settings ~}}
{{~ if setting.collection ~}}
if (settings.{{ setting.name }}.Count is 0)
{
{{~ for item in setting.collection_items ~}}
{{~ if item.default && !item.required ~}}
settings.{{ setting.name }}.Add(""{{ item.value }}"");
{{~ end ~}}
{{~ end ~}}
}
{{~ end ~}}
{{~ for setting in provider.settings ~}}
{{~ if setting.default_value && setting.type == 'String' ~}}
if (string.IsNullOrEmpty(options.{{ setting.name }}))
{
options.{{ setting.name }} = ""{{ setting.default_value }}"";
}
{{~ end ~}}
{{~ end ~}}
{{~ for setting in provider.settings ~}}
{{~ if setting.collection ~}}
if (options.{{ setting.name }}.Count is 0)
{
{{~ for item in setting.collection_items ~}}
{{~ if item.required ~}}
settings.{{ setting.name }}.Add(""{{ item.value }}"");
{{~ end ~}}
{{~ if item.default && !item.required ~}}
options.{{ setting.name }}.Add(""{{ item.value }}"");
{{~ end ~}}
{{~ end ~}}
}
{{~ end ~}}
{{~ for item in setting.collection_items ~}}
{{~ if item.required ~}}
options.{{ setting.name }}.Add(""{{ item.value }}"");
{{~ end ~}}
{{~ end ~}}
{{~ end ~}}
}
var formatter = Smart.CreateDefaultSmartFormat(new SmartSettings
/// <summary>
/// Registers the {{ provider.name }} integration in the OpenIddict client options.
/// </summary>
/// <param name=""options"">The options instance to initialize.</param>
public void Configure(OpenIddictClientOptions options)
{
var formatter = Smart.CreateDefaultSmartFormat(new SmartSettings
{
CaseSensitivity = CaseSensitivityType.CaseInsensitive
});
// Resolve the provider options from the service provider and create a registration based on the specified settings.
var settings = _provider.GetRequiredService<IOptionsMonitor<OpenIddictClientWebIntegrationOptions.{{ provider.name }}>>().CurrentValue;
var registration = new OpenIddictClientRegistration
{
Issuer = settings.Environment switch
{
CaseSensitivity = CaseSensitivityType.CaseInsensitive
});
{{~ for environment in provider.environments ~}}
OpenIddictClientWebIntegrationConstants.{{ provider.name }}.Environments.{{ environment.name }}
=> new Uri(formatter.Format(""{{ environment.issuer }}"", options), UriKind.Absolute),
{{~ end ~}}
_ => throw new InvalidOperationException(SR.FormatID0194(nameof(settings.Environment)))
},
var registration = new OpenIddictClientRegistration
ClientId = settings.ClientId,
ClientSecret = settings.ClientSecret,
RedirectUri = settings.RedirectUri,
Configuration = settings.Environment switch
{
Issuer = settings.Environment switch
{{~ for environment in provider.environments ~}}
{{~ if environment.configuration ~}}
OpenIddictClientWebIntegrationConstants.{{ provider.name }}.Environments.{{ environment.name }} => new OpenIddictConfiguration
{
{{~ for environment in provider.environments ~}}
OpenIddictClientWebIntegrationEnvironments.{{ provider.name }}.{{ environment.name }}
=> new Uri(formatter.Format(""{{ environment.issuer }}"", settings), UriKind.Absolute),
{{~ if environment.configuration.authorization_endpoint ~}}
AuthorizationEndpoint = new Uri(formatter.Format(""{{ environment.configuration.authorization_endpoint }}"", options), UriKind.Absolute),
{{~ end ~}}
_ => throw new InvalidOperationException(SR.FormatID0194(nameof(settings.Environment)))
},
{{~ if environment.configuration.token_endpoint ~}}
TokenEndpoint = new Uri(formatter.Format(""{{ environment.configuration.token_endpoint }}"", options), UriKind.Absolute),
{{~ end ~}}
ClientId = settings.ClientId,
ClientSecret = settings.ClientSecret,
RedirectUri = settings.RedirectUri,
{{~ if environment.configuration.userinfo_endpoint ~}}
UserinfoEndpoint = new Uri(formatter.Format(""{{ environment.configuration.userinfo_endpoint }}"", options), UriKind.Absolute),
{{~ end ~}}
Configuration = settings.Environment switch
{
{{~ for environment in provider.environments ~}}
{{~ if environment.configuration ~}}
OpenIddictClientWebIntegrationEnvironments.{{ provider.name }}.{{ environment.name }} => new OpenIddictConfiguration
CodeChallengeMethodsSupported =
{
{{~ if environment.configuration.authorization_endpoint ~}}
AuthorizationEndpoint = new Uri(formatter.Format(""{{ environment.configuration.authorization_endpoint }}"", settings), UriKind.Absolute),
{{~ for method in environment.configuration.code_challenge_methods_supported ~}}
""{{ method }}"",
{{~ end ~}}
},
{{~ if environment.configuration.token_endpoint ~}}
TokenEndpoint = new Uri(formatter.Format(""{{ environment.configuration.token_endpoint }}"", settings), UriKind.Absolute),
GrantTypesSupported =
{
{{~ for type in environment.configuration.grant_types_supported ~}}
""{{ type }}"",
{{~ end ~}}
},
{{~ if environment.configuration.userinfo_endpoint ~}}
UserinfoEndpoint = new Uri(formatter.Format(""{{ environment.configuration.userinfo_endpoint }}"", settings), UriKind.Absolute),
ResponseModesSupported =
{
{{~ for mode in environment.configuration.response_modes_supported ~}}
""{{ mode }}"",
{{~ end ~}}
},
CodeChallengeMethodsSupported =
{
{{~ for method in environment.configuration.code_challenge_methods_supported ~}}
""{{ method }}"",
{{~ end ~}}
},
GrantTypesSupported =
{
{{~ for type in environment.configuration.grant_types_supported ~}}
""{{ type }}"",
{{~ end ~}}
},
ResponseModesSupported =
{
{{~ for mode in environment.configuration.response_modes_supported ~}}
""{{ mode }}"",
{{~ end ~}}
},
ResponseTypesSupported =
{
{{~ for type in environment.configuration.response_types_supported ~}}
""{{ type }}"",
{{~ end ~}}
},
ScopesSupported =
{
{{~ for scope in environment.configuration.scopes_supported ~}}
""{{ scope }}"",
{{~ end ~}}
},
ResponseTypesSupported =
{
{{~ for type in environment.configuration.response_types_supported ~}}
""{{ type }}"",
{{~ end ~}}
},
TokenEndpointAuthMethodsSupported =
{
{{~ for method in environment.configuration.token_endpoint_auth_methods_supported ~}}
""{{ method }}"",
{{~ end ~}}
}
ScopesSupported =
{
{{~ for scope in environment.configuration.scopes_supported ~}}
""{{ scope }}"",
{{~ end ~}}
},
{{~ else ~}}
OpenIddictClientWebIntegrationEnvironments.{{ provider.name }}.{{ environment.name }} => null,
{{~ end ~}}
{{~ end ~}}
_ => throw new InvalidOperationException(SR.FormatID0194(nameof(settings.Environment)))
TokenEndpointAuthMethodsSupported =
{
{{~ for method in environment.configuration.token_endpoint_auth_methods_supported ~}}
""{{ method }}"",
{{~ end ~}}
}
},
{{~ else ~}}
OpenIddictClientWebIntegrationConstants.{{ provider.name }}.Environments.{{ environment.name }} => null,
{{~ end ~}}
{{~ end ~}}
EncryptionCredentials =
{
{{~ for setting in provider.settings ~}}
{{~ if setting.type == 'EncryptionKey' ~}}
new EncryptingCredentials(settings.{{ setting.name }}, ""{{ setting.encryption_algorithm }}"", SecurityAlgorithms.Aes256CbcHmacSha512),
{{~ end ~}}
{{~ end ~}}
},
_ => throw new InvalidOperationException(SR.FormatID0194(nameof(settings.Environment)))
},
SigningCredentials =
{
{{~ for setting in provider.settings ~}}
{{~ if setting.type == 'SigningKey' ~}}
new SigningCredentials(settings.{{ setting.name }}, ""{{ setting.signing_algorithm }}""),
{{~ end ~}}
{{~ end ~}}
},
EncryptionCredentials =
{
{{~ for setting in provider.settings ~}}
{{~ if setting.type == 'EncryptionKey' ~}}
new EncryptingCredentials(settings.{{ setting.name }}, ""{{ setting.encryption_algorithm }}"", SecurityAlgorithms.Aes256CbcHmacSha512),
{{~ end ~}}
{{~ end ~}}
},
Properties =
{
[Properties.ProviderName] = Providers.{{ provider.name }},
[Properties.ProviderSettings] = settings
}
};
SigningCredentials =
{
{{~ for setting in provider.settings ~}}
{{~ if setting.type == 'SigningKey' ~}}
new SigningCredentials(settings.{{ setting.name }}, ""{{ setting.signing_algorithm }}""),
{{~ end ~}}
{{~ end ~}}
},
Properties =
{
[Properties.ProviderName] = Providers.{{ provider.name }},
[Properties.ProviderOptions] = settings
}
};
registration.Scopes.UnionWith(settings.Scopes);
registration.Scopes.UnionWith(settings.Scopes);
options.Registrations.Add(registration);
}
options.Registrations.Add(registration);
}
}
{{~ end ~}}
@ -529,14 +677,15 @@ public partial class OpenIddictClientWebIntegrationHelpers
{
{{~ for provider in providers ~}}
/// <summary>
/// Resolves the {{ provider.name }} provider settings from the specified registration.
/// Resolves the {{ provider.name }} provider options from the specified registration.
/// </summary>
/// <param name=""registration"">The client registration.</param>
/// <returns>The {{ provider.name }} provider settings.</returns>
/// <exception cref=""InvalidOperationException"">The provider settings cannot be resolved.</exception>
public static OpenIddictClientWebIntegrationSettings.{{ provider.name }} Get{{ provider.name }}Settings(this OpenIddictClientRegistration registration)
=> registration.GetProviderSettings<OpenIddictClientWebIntegrationSettings.{{ provider.name }}>() ??
/// <returns>The {{ provider.name }} provider options.</returns>
/// <exception cref=""InvalidOperationException"">The provider options cannot be resolved.</exception>
public static OpenIddictClientWebIntegrationOptions.{{ provider.name }} Get{{ provider.name }}Options(this OpenIddictClientRegistration registration)
=> registration.GetProviderOptions<OpenIddictClientWebIntegrationOptions.{{ provider.name }}>() ??
throw new InvalidOperationException(SR.FormatID0333(Providers.{{ provider.name }}));
{{~ end ~}}
}
");
@ -548,7 +697,7 @@ public partial class OpenIddictClientWebIntegrationHelpers
});
}
static string GenerateSettings(XDocument document)
static string GenerateOptions(XDocument document)
{
var template = Template.Parse(@"#nullable enable
@ -556,18 +705,43 @@ using Microsoft.IdentityModel.Tokens;
namespace OpenIddict.Client.WebIntegration;
public partial class OpenIddictClientWebIntegrationSettings
public partial class OpenIddictClientWebIntegrationOptions
{
{{~ for provider in providers ~}}
/// <summary>
/// Provides various settings needed to configure the {{ provider.name }} integration.
/// Provides various options needed to configure the {{ provider.name }} integration.
/// </summary>
public class {{ provider.name }} : OpenIddictClientWebIntegrationSettings
public class {{ provider.name }}
{
/// <summary>
/// Gets or sets the client identifier.
/// </summary>
public string? ClientId { get; set; }
/// <summary>
/// Gets or sets the client secret, if applicable.
/// </summary>
public string? ClientSecret { get; set; }
/// <summary>
/// Gets or sets the redirection URL.
/// </summary>
public Uri? RedirectUri { get; set; }
/// <summary>
/// Gets the scopes requested to the authorization server.
/// </summary>
public HashSet<string> Scopes { get; } = new(StringComparer.Ordinal);
/// <summary>
/// Gets or sets the environment that determines the endpoints to use (by default, ""Production"").
/// </summary>
public string? Environment { get; set; } = OpenIddictClientWebIntegrationConstants.{{ provider.name }}.Environments.Production;
{{~ for setting in provider.settings ~}}
{{~ if setting.description ~}}
/// <summary>
/// {{ setting.description }}
/// Gets or sets {{ setting.description }}.
/// </summary>
{{~ end ~}}
{{~ if setting.collection ~}}
@ -575,12 +749,8 @@ public partial class OpenIddictClientWebIntegrationSettings
{{~ else ~}}
public {{ setting.clr_type }}? {{ setting.name }} { get; set; }
{{~ end ~}}
{{~ end ~}}
/// <summary>
/// Gets or sets the environment that determines the endpoints to use.
/// </summary>
public OpenIddictClientWebIntegrationEnvironments.{{ provider.name }} Environment { get; set; }
{{~ end ~}}
}
{{~ end ~}}
}
@ -596,7 +766,8 @@ public partial class OpenIddictClientWebIntegrationSettings
{
Name = (string) setting.Attribute("Name"),
Collection = (bool?) setting.Attribute("Collection") ?? false,
Description = (string) setting.Attribute("Description"),
Description = (string) setting.Attribute("Description") is string description ?
char.ToLower(description[0], CultureInfo.GetCultureInfo("en-US")) + description.Substring(1) : null,
ClrType = (string) setting.Attribute("Type") switch
{
"EncryptionKey" when (string) setting.Element("EncryptionAlgorithm").Attribute("Value")

31
sandbox/OpenIddict.Sandbox.AspNet.Client/Startup.cs

@ -92,8 +92,9 @@ namespace OpenIddict.Sandbox.AspNet.Client
.EnableRedirectionEndpointPassthrough()
.EnablePostLogoutRedirectionEndpointPassthrough();
// Register the System.Net.Http integration.
options.UseSystemNetHttp();
// Register the System.Net.Http integration and configure the HTTP options.
options.UseSystemNetHttp()
.SetProductInformation("DemoApp", "1.0.0");
// Add a client registration matching the client application definition in the server project.
options.AddRegistration(new OpenIddictClientRegistration
@ -110,24 +111,24 @@ namespace OpenIddict.Sandbox.AspNet.Client
// Register the Web providers integrations.
options.UseWebProviders()
.AddGitHub(new()
.UseGitHub(options =>
{
ClientId = "c4ade52327b01ddacff3",
ClientSecret = "da6bed851b75e317bf6b2cb67013679d9467c122",
RedirectUri = new Uri("https://localhost:44378/callback/login/github", UriKind.Absolute)
options.SetClientId("c4ade52327b01ddacff3")
.SetClientSecret("da6bed851b75e317bf6b2cb67013679d9467c122")
.SetRedirectUri("https://localhost:44378/callback/login/github");
})
.AddGoogle(new()
.UseGoogle(options =>
{
ClientId = "1016114395689-kgtgq2p6dj27d7v6e2kjkoj54dgrrckh.apps.googleusercontent.com",
ClientSecret = "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",
RedirectUri = new Uri("https://localhost:44378/callback/login/google", UriKind.Absolute),
Scopes = { Scopes.Profile }
options.SetClientId("1016114395689-kgtgq2p6dj27d7v6e2kjkoj54dgrrckh.apps.googleusercontent.com")
.SetClientSecret("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")
.SetRedirectUri("https://localhost:44378/callback/login/google")
.AddScopes(Scopes.Profile);
})
.AddTwitter(new()
.UseTwitter(options =>
{
ClientId = "bXgwc0U3N3A3YWNuaWVsdlRmRWE6MTpjaQ",
ClientSecret = "VcohOgBp-6yQCurngo4GAyKeZh0D6SUCCSjJgEo1uRzJarjIUS",
RedirectUri = new Uri("https://localhost:44378/callback/login/twitter", UriKind.Absolute)
options.SetClientId("bXgwc0U3N3A3YWNuaWVsdlRmRWE6MTpjaQ")
.SetClientSecret("VcohOgBp-6yQCurngo4GAyKeZh0D6SUCCSjJgEo1uRzJarjIUS")
.SetRedirectUri("https://localhost:44378/callback/login/twitter");
});
});

13
sandbox/OpenIddict.Sandbox.AspNet.Server/Startup.cs

@ -140,16 +140,17 @@ namespace OpenIddict.Sandbox.AspNet.Server
options.UseOwin()
.EnableRedirectionEndpointPassthrough();
// Register the System.Net.Http integration.
options.UseSystemNetHttp();
// Register the System.Net.Http integration and configure the HTTP options.
options.UseSystemNetHttp()
.SetProductInformation("DemoApp", "1.0.0");
// Register the Web providers integrations.
options.UseWebProviders()
.AddGitHub(new()
.UseGitHub(options =>
{
ClientId = "c4ade52327b01ddacff3",
ClientSecret = "da6bed851b75e317bf6b2cb67013679d9467c122",
RedirectUri = new Uri("https://localhost:44349/callback/login/github", UriKind.Absolute)
options.SetClientId("c4ade52327b01ddacff3")
.SetClientSecret("da6bed851b75e317bf6b2cb67013679d9467c122")
.SetRedirectUri("https://localhost:44349/callback/login/github");
});
})

41
sandbox/OpenIddict.Sandbox.AspNetCore.Client/Startup.cs

@ -102,8 +102,9 @@ public class Startup
.EnableRedirectionEndpointPassthrough()
.EnablePostLogoutRedirectionEndpointPassthrough();
// Register the System.Net.Http integration.
options.UseSystemNetHttp();
// Register the System.Net.Http integration and configure the HTTP options.
options.UseSystemNetHttp()
.SetProductInformation("DemoApp", "1.0.0");
// Add a client registration matching the client application definition in the server project.
options.AddRegistration(new OpenIddictClientRegistration
@ -120,32 +121,30 @@ public class Startup
// Register the Web providers integrations.
options.UseWebProviders()
.AddGitHub(new()
.UseGitHub(options =>
{
ClientId = "c4ade52327b01ddacff3",
ClientSecret = "da6bed851b75e317bf6b2cb67013679d9467c122",
RedirectUri = new Uri("https://localhost:44381/callback/login/github", UriKind.Absolute)
options.SetClientId("c4ade52327b01ddacff3")
.SetClientSecret("da6bed851b75e317bf6b2cb67013679d9467c122")
.SetRedirectUri("https://localhost:44381/callback/login/github");
})
.AddGoogle(new()
.UseGoogle(options =>
{
ClientId = "1016114395689-kgtgq2p6dj27d7v6e2kjkoj54dgrrckh.apps.googleusercontent.com",
ClientSecret = "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",
RedirectUri = new Uri("https://localhost:44381/callback/login/google", UriKind.Absolute),
Scopes = { Scopes.Profile }
options.SetClientId("1016114395689-kgtgq2p6dj27d7v6e2kjkoj54dgrrckh.apps.googleusercontent.com")
.SetClientSecret("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")
.SetRedirectUri("https://localhost:44381/callback/login/google")
.AddScopes(Scopes.Profile);
})
.AddReddit(new()
.UseReddit(options =>
{
ClientId = "vDLNqhrkwrvqHgnoBWF3og",
ClientSecret = "Tpab28Dz0upyZLqn7AN3GFD1O-zaAw",
RedirectUri = new Uri("https://localhost:44381/callback/login/reddit", UriKind.Absolute),
ProductName = "DemoApp",
ProductVersion = "1.0.0"
options.SetClientId("vDLNqhrkwrvqHgnoBWF3og")
.SetClientSecret("Tpab28Dz0upyZLqn7AN3GFD1O-zaAw")
.SetRedirectUri("https://localhost:44381/callback/login/reddit");
})
.AddTwitter(new()
.UseTwitter(options =>
{
ClientId = "bXgwc0U3N3A3YWNuaWVsdlRmRWE6MTpjaQ",
ClientSecret = "VcohOgBp-6yQCurngo4GAyKeZh0D6SUCCSjJgEo1uRzJarjIUS",
RedirectUri = new Uri("https://localhost:44381/callback/login/twitter", UriKind.Absolute)
options.SetClientId("bXgwc0U3N3A3YWNuaWVsdlRmRWE6MTpjaQ")
.SetClientSecret("VcohOgBp-6yQCurngo4GAyKeZh0D6SUCCSjJgEo1uRzJarjIUS")
.SetRedirectUri("https://localhost:44381/callback/login/twitter");
});
});

14
sandbox/OpenIddict.Sandbox.AspNetCore.Server/Startup.cs

@ -1,3 +1,4 @@
using System.Net.Http.Headers;
using Microsoft.AspNetCore.Identity;
using Microsoft.EntityFrameworkCore;
using OpenIddict.Sandbox.AspNetCore.Server.Models;
@ -86,16 +87,17 @@ public class Startup
.EnableStatusCodePagesIntegration()
.EnableRedirectionEndpointPassthrough();
// Register the System.Net.Http integration.
options.UseSystemNetHttp();
// Register the System.Net.Http integration and configure the HTTP options.
options.UseSystemNetHttp()
.SetProductInformation("DemoApp", "1.0.0");
// Register the Web providers integrations.
options.UseWebProviders()
.AddGitHub(new()
.UseGitHub(options =>
{
ClientId = "c4ade52327b01ddacff3",
ClientSecret = "da6bed851b75e317bf6b2cb67013679d9467c122",
RedirectUri = new Uri("https://localhost:44395/callback/login/github", UriKind.Absolute)
options.SetClientId("c4ade52327b01ddacff3")
.SetClientSecret("da6bed851b75e317bf6b2cb67013679d9467c122")
.SetRedirectUri("https://localhost:44395/callback/login/github");
});
})

16
src/OpenIddict.Client.AspNetCore/OpenIddictClientAspNetCoreBuilder.cs

@ -89,25 +89,15 @@ public class OpenIddictClientAspNetCoreBuilder
public OpenIddictClientAspNetCoreBuilder EnableStatusCodePagesIntegration()
=> Configure(options => options.EnableStatusCodePagesIntegration = true);
/// <summary>
/// Determines whether the specified object is equal to the current object.
/// </summary>
/// <param name="obj">The object to compare with the current object.</param>
/// <returns><see langword="true"/> if the specified object is equal to the current object; otherwise, false.</returns>
/// <inheritdoc/>
[EditorBrowsable(EditorBrowsableState.Never)]
public override bool Equals(object? obj) => base.Equals(obj);
/// <summary>
/// Serves as the default hash function.
/// </summary>
/// <returns>A hash code for the current object.</returns>
/// <inheritdoc/>
[EditorBrowsable(EditorBrowsableState.Never)]
public override int GetHashCode() => base.GetHashCode();
/// <summary>
/// Returns a string that represents the current object.
/// </summary>
/// <returns>A string that represents the current object.</returns>
/// <inheritdoc/>
[EditorBrowsable(EditorBrowsableState.Never)]
public override string? ToString() => base.ToString();
}

16
src/OpenIddict.Client.Owin/OpenIddictClientOwinBuilder.cs

@ -81,25 +81,15 @@ public class OpenIddictClientOwinBuilder
public OpenIddictClientOwinBuilder EnableErrorPassthrough()
=> Configure(options => options.EnableErrorPassthrough = true);
/// <summary>
/// Determines whether the specified object is equal to the current object.
/// </summary>
/// <param name="obj">The object to compare with the current object.</param>
/// <returns><see langword="true"/> if the specified object is equal to the current object; otherwise, false.</returns>
/// <inheritdoc/>
[EditorBrowsable(EditorBrowsableState.Never)]
public override bool Equals(object? obj) => base.Equals(obj);
/// <summary>
/// Serves as the default hash function.
/// </summary>
/// <returns>A hash code for the current object.</returns>
/// <inheritdoc/>
[EditorBrowsable(EditorBrowsableState.Never)]
public override int GetHashCode() => base.GetHashCode();
/// <summary>
/// Returns a string that represents the current object.
/// </summary>
/// <returns>A string that represents the current object.</returns>
/// <inheritdoc/>
[EditorBrowsable(EditorBrowsableState.Never)]
public override string? ToString() => base.ToString();
}

34
src/OpenIddict.Client.SystemNetHttp/OpenIddictClientSystemNetHttpBuilder.cs

@ -5,6 +5,7 @@
*/
using System.ComponentModel;
using System.Net.Http.Headers;
using OpenIddict.Client.SystemNetHttp;
using Polly;
@ -51,28 +52,37 @@ public class OpenIddictClientSystemNetHttpBuilder
/// </summary>
/// <param name="policy">The HTTP Polly error policy.</param>
/// <returns>The <see cref="OpenIddictClientSystemNetHttpBuilder"/>.</returns>
public OpenIddictClientSystemNetHttpBuilder SetHttpErrorPolicy(IAsyncPolicy<HttpResponseMessage> policy)
public OpenIddictClientSystemNetHttpBuilder SetHttpErrorPolicy(IAsyncPolicy<HttpResponseMessage>? policy)
=> Configure(options => options.HttpErrorPolicy = policy);
/// <summary>
/// Determines whether the specified object is equal to the current object.
/// Sets the product information used in the user agent header that is attached
/// to the backchannel HTTP requests sent to the authorization server.
/// </summary>
/// <param name="obj">The object to compare with the current object.</param>
/// <returns><see langword="true"/> if the specified object is equal to the current object; otherwise, false.</returns>
[EditorBrowsable(EditorBrowsableState.Never)]
public override bool Equals(object? obj) => base.Equals(obj);
/// <param name="information">The product information.</param>
/// <returns>The <see cref="OpenIddictClientSystemNetHttpBuilder"/>.</returns>
public OpenIddictClientSystemNetHttpBuilder SetProductInformation(ProductInfoHeaderValue? information)
=> Configure(options => options.ProductInformation = information);
/// <summary>
/// Serves as the default hash function.
/// Sets the product information used in the user agent header that is attached
/// to the backchannel HTTP requests sent to the authorization server.
/// </summary>
/// <returns>A hash code for the current object.</returns>
/// <param name="name">The product name.</param>
/// <param name="version">The product version.</param>
/// <returns>The <see cref="OpenIddictClientSystemNetHttpBuilder"/>.</returns>
public OpenIddictClientSystemNetHttpBuilder SetProductInformation(string? name, string? version)
=> SetProductInformation(!string.IsNullOrEmpty(name) ? new ProductInfoHeaderValue(name, version) : null);
/// <inheritdoc/>
[EditorBrowsable(EditorBrowsableState.Never)]
public override bool Equals(object? obj) => base.Equals(obj);
/// <inheritdoc/>
[EditorBrowsable(EditorBrowsableState.Never)]
public override int GetHashCode() => base.GetHashCode();
/// <summary>
/// Returns a string that represents the current object.
/// </summary>
/// <returns>A string that represents the current object.</returns>
/// <inheritdoc/>
[EditorBrowsable(EditorBrowsableState.Never)]
public override string? ToString() => base.ToString();
}

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

@ -18,10 +18,10 @@ public class OpenIddictClientSystemNetHttpConfiguration : IConfigureOptions<Open
IConfigureNamedOptions<HttpClientFactoryOptions>
{
#if !SUPPORTS_SERVICE_PROVIDER_IN_HTTP_MESSAGE_HANDLER_BUILDER
private readonly IServiceProvider _serviceProvider;
private readonly IServiceProvider _provider;
public OpenIddictClientSystemNetHttpConfiguration(IServiceProvider serviceProvider)
=> _serviceProvider = serviceProvider ?? throw new ArgumentNullException(nameof(serviceProvider));
public OpenIddictClientSystemNetHttpConfiguration(IServiceProvider provider)
=> _provider = provider ?? throw new ArgumentNullException(nameof(provider));
#endif
public void Configure(OpenIddictClientOptions options)
@ -35,8 +35,7 @@ public class OpenIddictClientSystemNetHttpConfiguration : IConfigureOptions<Open
options.Handlers.AddRange(OpenIddictClientSystemNetHttpHandlers.DefaultHandlers);
}
public void Configure(HttpClientFactoryOptions options)
=> Debug.Fail("This infrastructure method shouldn't be called.");
public void Configure(HttpClientFactoryOptions options) => Configure(Options.DefaultName, options);
public void Configure(string name, HttpClientFactoryOptions options)
{
@ -45,8 +44,8 @@ public class OpenIddictClientSystemNetHttpConfiguration : IConfigureOptions<Open
throw new ArgumentNullException(nameof(options));
}
// Only amend the HTTP client factory options if the instance is managed by OpenIddict.
var assembly = typeof(OpenIddictClientSystemNetHttpOptions).Assembly.GetName();
if (!string.Equals(name, assembly.Name, StringComparison.Ordinal))
{
return;
@ -57,7 +56,7 @@ public class OpenIddictClientSystemNetHttpConfiguration : IConfigureOptions<Open
#if SUPPORTS_SERVICE_PROVIDER_IN_HTTP_MESSAGE_HANDLER_BUILDER
var options = builder.Services.GetRequiredService<IOptionsMonitor<OpenIddictClientSystemNetHttpOptions>>();
#else
var options = _serviceProvider.GetRequiredService<IOptionsMonitor<OpenIddictClientSystemNetHttpOptions>>();
var options = _provider.GetRequiredService<IOptionsMonitor<OpenIddictClientSystemNetHttpOptions>>();
#endif
var policy = options.CurrentValue.HttpErrorPolicy;
if (policy is not null)

17
src/OpenIddict.Client.SystemNetHttp/OpenIddictClientSystemNetHttpHandlers.cs

@ -11,6 +11,7 @@ using System.Diagnostics.CodeAnalysis;
using System.Net.Http.Headers;
using System.Text;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using Microsoft.Extensions.Primitives;
using static OpenIddict.Client.SystemNetHttp.OpenIddictClientSystemNetHttpConstants;
@ -114,6 +115,11 @@ public static partial class OpenIddictClientSystemNetHttpHandlers
/// </summary>
public class AttachUserAgent<TContext> : IOpenIddictClientHandler<TContext> where TContext : BaseExternalContext
{
private readonly IOptionsMonitor<OpenIddictClientSystemNetHttpOptions> _options;
public AttachUserAgent(IOptionsMonitor<OpenIddictClientSystemNetHttpOptions> options)
=> _options = options ?? throw new ArgumentNullException(nameof(options));
/// <summary>
/// Gets the default descriptor definition assigned to this handler.
/// </summary>
@ -140,9 +146,18 @@ public static partial class OpenIddictClientSystemNetHttpHandlers
var request = context.Transaction.GetHttpRequestMessage() ??
throw new InvalidOperationException(SR.GetResourceString(SR.ID0173));
var assembly = typeof(OpenIddictClientSystemNetHttpHandlers).Assembly.GetName();
// Some authorization servers are known to aggressively check user agents and encourage
// developers to use unique user agents. While a default user agent is always added,
// the default value doesn't differ accross applications. To reduce the risks of seeing
// requests blocked, a more specific user agent header can be configured by the developer.
// In this case, the value specified by the developer always appears first in the list.
if (_options.CurrentValue.ProductInformation is ProductInfoHeaderValue information)
{
request.Headers.UserAgent.Add(information);
}
// Attach a user agent based on the assembly version of the System.Net.Http integration.
var assembly = typeof(OpenIddictClientSystemNetHttpHandlers).Assembly.GetName();
request.Headers.UserAgent.Add(new ProductInfoHeaderValue(
productName: assembly.Name!,
productVersion: assembly.Version!.ToString()));

9
src/OpenIddict.Client.SystemNetHttp/OpenIddictClientSystemNetHttpOptions.cs

@ -5,6 +5,7 @@
*/
using System.Net;
using System.Net.Http.Headers;
using Polly;
using Polly.Extensions.Http;
@ -18,8 +19,14 @@ public class OpenIddictClientSystemNetHttpOptions
/// <summary>
/// Gets or sets the HTTP Polly error policy used by the internal OpenIddict HTTP clients.
/// </summary>
public IAsyncPolicy<HttpResponseMessage> HttpErrorPolicy { get; set; }
public IAsyncPolicy<HttpResponseMessage>? HttpErrorPolicy { get; set; }
= HttpPolicyExtensions.HandleTransientHttpError()
.OrResult(response => response.StatusCode == HttpStatusCode.NotFound)
.WaitAndRetryAsync(4, attempt => TimeSpan.FromSeconds(Math.Pow(2, attempt)));
/// <summary>
/// Gets or sets the product information used in the user agent header that is
/// attached to the backchannel HTTP requests sent to the authorization server.
/// </summary>
public ProductInfoHeaderValue? ProductInformation { get; set; }
}

2
src/OpenIddict.Client.WebIntegration/OpenIddictClientWebIntegrationConstants.cs

@ -16,6 +16,6 @@ public static partial class OpenIddictClientWebIntegrationConstants
public static class Properties
{
public const string ProviderName = ".provider_name";
public const string ProviderSettings = ".provider_settings";
public const string ProviderOptions = ".provider_options";
}
}

15
src/OpenIddict.Client.WebIntegration/OpenIddictClientWebIntegrationEnvironments.cs

@ -1,15 +0,0 @@
/*
* Licensed under the Apache License, Version 2.0 (http://www.apache.org/licenses/LICENSE-2.0)
* See https://github.com/openiddict/openiddict-core for more information concerning
* the license and the contributors participating to this project.
*/
namespace OpenIddict.Client.WebIntegration;
/// <summary>
/// Exposes the provider-specific environments supported by the OpenIddict client Web integration services.
/// </summary>
public static partial class OpenIddictClientWebIntegrationEnvironments
{
// Note: environments are automatically generated by the source generator.
}

4
src/OpenIddict.Client.WebIntegration/OpenIddictClientWebIntegrationHandlers.Discovery.cs

@ -53,8 +53,8 @@ public static partial class OpenIddictClientWebIntegrationHandlers
// is replaced by this handler to always use "https://login.microsoftonline.com/common/v2.0".
if (context.Registration.GetProviderName() is Providers.Microsoft)
{
var settings = context.Registration.GetMicrosoftSettings();
if (string.Equals(settings.Tenant, "common", StringComparison.OrdinalIgnoreCase))
var options = context.Registration.GetMicrosoftOptions();
if (string.Equals(options.Tenant, "common", StringComparison.OrdinalIgnoreCase))
{
context.Response[Metadata.Issuer] = "https://login.microsoftonline.com/common/v2.0";
}

21
src/OpenIddict.Client.WebIntegration/OpenIddictClientWebIntegrationHandlers.Exchange.cs

@ -1,21 +0,0 @@
/*
* Licensed under the Apache License, Version 2.0 (http://www.apache.org/licenses/LICENSE-2.0)
* See https://github.com/openiddict/openiddict-core for more information concerning
* the license and the contributors participating to this project.
*/
using System.Collections.Immutable;
namespace OpenIddict.Client.WebIntegration;
public static partial class OpenIddictClientWebIntegrationHandlers
{
public static class Exchange
{
public static ImmutableArray<OpenIddictClientHandlerDescriptor> DefaultHandlers { get; } = ImmutableArray.Create(
/*
* Token request preparation:
*/
AddProductNameToUserAgentHeader<PrepareTokenRequestContext>.Descriptor);
}
}

2
src/OpenIddict.Client.WebIntegration/OpenIddictClientWebIntegrationHandlers.Protection.cs

@ -57,7 +57,7 @@ public static partial class OpenIddictClientWebIntegrationHandlers
// that is associated with the client application. Since the tenant cannot be
// inferred when targeting the common tenant instance, issuer validation is disabled.
Providers.Microsoft when string.Equals(
context.Registration.GetMicrosoftSettings().Tenant,
context.Registration.GetMicrosoftOptions().Tenant,
"common", StringComparison.OrdinalIgnoreCase)
=> false,

11
src/OpenIddict.Client.WebIntegration/OpenIddictClientWebIntegrationHandlers.Userinfo.cs

@ -19,7 +19,6 @@ public static partial class OpenIddictClientWebIntegrationHandlers
/*
* Userinfo request preparation:
*/
AddProductNameToUserAgentHeader<PrepareUserinfoRequestContext>.Descriptor,
AttachNonStandardFieldParameter.Descriptor,
/*
@ -59,11 +58,11 @@ public static partial class OpenIddictClientWebIntegrationHandlers
if (context.Registration.GetProviderName() is Providers.Twitter)
{
var settings = context.Registration.GetTwitterSettings();
var options = context.Registration.GetTwitterOptions();
context.Request["expansions"] = string.Join(",", settings.Expansions);
context.Request["tweet.fields"] = string.Join(",", settings.TweetFields);
context.Request["user.fields"] = string.Join(",", settings.UserFields);
context.Request["expansions"] = string.Join(",", options.Expansions);
context.Request["tweet.fields"] = string.Join(",", options.TweetFields);
context.Request["user.fields"] = string.Join(",", options.UserFields);
}
return default;
@ -97,7 +96,7 @@ public static partial class OpenIddictClientWebIntegrationHandlers
Debug.Assert(context.Response is not null, SR.GetResourceString(SR.ID4007));
// Some providers are known to wrap their userinfo payloads in top-level JSON nodes
// (generally named "d", "data" or "content"), which prevents the default extraction
// (generally named "d", "data" or "response"), which prevents the default extraction
// logic from mapping the parameters to CLR claims. To work around that, this handler
// is responsible for extracting the nested payload and replacing the userinfo response.

59
src/OpenIddict.Client.WebIntegration/OpenIddictClientWebIntegrationHandlers.cs

@ -7,10 +7,7 @@
using System.Collections.Immutable;
using System.ComponentModel;
using System.Diagnostics;
using System.Net.Http.Headers;
using System.Security.Claims;
using static OpenIddict.Client.SystemNetHttp.OpenIddictClientSystemNetHttpHandlerFilters;
using static OpenIddict.Client.SystemNetHttp.OpenIddictClientSystemNetHttpHandlers;
using static OpenIddict.Client.WebIntegration.OpenIddictClientWebIntegrationConstants;
namespace OpenIddict.Client.WebIntegration;
@ -31,7 +28,6 @@ public static partial class OpenIddictClientWebIntegrationHandlers
AttachNonDefaultResponseMode.Descriptor,
FormatNonStandardScopeParameter.Descriptor)
.AddRange(Discovery.DefaultHandlers)
.AddRange(Exchange.DefaultHandlers)
.AddRange(Protection.DefaultHandlers)
.AddRange(Userinfo.DefaultHandlers);
@ -70,8 +66,8 @@ public static partial class OpenIddictClientWebIntegrationHandlers
// see https://developer.apple.com/documentation/sign_in_with_apple/generate_and_validate_tokens.
if (context.Registration.GetProviderName() is Providers.Apple)
{
var settings = context.Registration.GetAppleSettings();
context.ClientAssertionTokenPrincipal.SetClaim(Claims.Private.Issuer, settings.TeamId);
var options = context.Registration.GetAppleOptions();
context.ClientAssertionTokenPrincipal.SetClaim(Claims.Private.Issuer, options.TeamId);
context.ClientAssertionTokenPrincipal.SetAudiences("https://appleid.apple.com");
}
@ -200,55 +196,4 @@ public static partial class OpenIddictClientWebIntegrationHandlers
return default;
}
}
/// <summary>
/// Contains the logic responsible for enriching the user agent with an optional product name/product version.
/// </summary>
public class AddProductNameToUserAgentHeader<TContext> : IOpenIddictClientHandler<TContext>
where TContext : BaseExternalContext
{
/// <summary>
/// Gets the default descriptor definition assigned to this handler.
/// </summary>
public static OpenIddictClientHandlerDescriptor Descriptor { get; }
= OpenIddictClientHandlerDescriptor.CreateBuilder<TContext>()
.AddFilter<RequireHttpMetadataAddress>()
.UseSingletonHandler<AddProductNameToUserAgentHeader<TContext>>()
.SetOrder(AttachUserAgent<TContext>.Descriptor.Order + 500)
.SetType(OpenIddictClientHandlerType.BuiltIn)
.Build();
/// <inheritdoc/>
public ValueTask HandleAsync(TContext context)
{
if (context is null)
{
throw new ArgumentNullException(nameof(context));
}
// This handler only applies to System.Net.Http requests. If the HTTP request cannot be resolved,
// this may indicate that the request was incorrectly processed by another client stack.
var request = context.Transaction.GetHttpRequestMessage() ??
throw new InvalidOperationException(SR.GetResourceString(SR.ID0173));
// A few providers (like Reddit) are known to aggressively check user agents and encourage
// developers to use unique user agents. While OpenIddict itself always adds a user agent,
// the default value doesn't differ accross applications. To reduce the risks of seeing
// requests blocked by these providers, a more specific user agent header containing the
// product name/version set by the user (or the client identifier if unset) is appended.
var settings = context.Registration.GetProviderSettings();
if (settings is not null)
{
var name = settings.ProductName ?? context.Registration.ClientId;
if (!string.IsNullOrEmpty(name))
{
request.Headers.UserAgent.Add(new ProductInfoHeaderValue(
productName: name,
productVersion: settings.ProductVersion));
}
}
return default;
}
}
}

31
src/OpenIddict.Client.WebIntegration/OpenIddictClientWebIntegrationHelpers.cs

@ -32,35 +32,24 @@ public static partial class OpenIddictClientWebIntegrationHelpers
}
/// <summary>
/// Resolves the provider settings associated with the client registration or
/// <see langword="null" /> if no provider information is attached to the registration.
/// Resolves the provider options associated with the client registration or
/// <see langword="null" /> if no provider information is attached to the registration or if
/// the actual setting information doesn't match the specified <typeparamref name="TOptions"/>.
/// </summary>
/// <typeparam name="TOptions">The type of the provider options.</typeparam>
/// <param name="registration">The client registration.</param>
/// <returns>The provider settings, if applicable.</returns>
/// <returns>The provider options, if applicable.</returns>
/// <exception cref="ArgumentNullException"><paramref name="registration"/> is null.</exception>
public static OpenIddictClientWebIntegrationSettings? GetProviderSettings(this OpenIddictClientRegistration registration)
public static TOptions? GetProviderOptions<TOptions>(this OpenIddictClientRegistration registration)
{
if (registration is null)
{
throw new ArgumentNullException(nameof(registration));
}
return registration.Properties.TryGetValue(Properties.ProviderSettings, out var value)
&& value is OpenIddictClientWebIntegrationSettings settings ? settings : null;
}
/// <summary>
/// Resolves the provider settings associated with the client registration or
/// <see langword="null" /> if no provider information is attached to the registration or if
/// the actual setting information doesn't match the specified <typeparamref name="TSettings"/>.
/// </summary>
/// <typeparam name="TSettings">The type of the provider settings.</typeparam>
/// <param name="registration">The client registration.</param>
/// <returns>The provider settings, if applicable.</returns>
/// <exception cref="ArgumentNullException"><paramref name="registration"/> is null.</exception>
public static TSettings? GetProviderSettings<TSettings>(this OpenIddictClientRegistration registration)
where TSettings : OpenIddictClientWebIntegrationSettings
=> registration.GetProviderSettings() is TSettings settings ? settings : null;
return registration.Properties.TryGetValue(Properties.ProviderOptions, out var value)
&& value is TOptions options ? options : default;
// Note: provider-specific helpers are automatically generated by the source generator.
// Note: provider-specific helpers are automatically generated by the source generator.
}
}

7
src/OpenIddict.Client.WebIntegration/OpenIddictClientWebIntegrationOptions.cs

@ -9,10 +9,7 @@ namespace OpenIddict.Client.WebIntegration;
/// <summary>
/// Provides various settings needed to configure the OpenIddict client Web integration.
/// </summary>
public class OpenIddictClientWebIntegrationOptions
public partial class OpenIddictClientWebIntegrationOptions
{
/// <summary>
/// Gets the list of provider integrations enabled for this application.
/// </summary>
public List<OpenIddictClientWebIntegrationProvider> Providers { get; } = new();
// Note: provider options are automatically generated by the source generator.
}

46
src/OpenIddict.Client.WebIntegration/OpenIddictClientWebIntegrationProvider.cs

@ -1,46 +0,0 @@
/*
* Licensed under the Apache License, Version 2.0 (http://www.apache.org/licenses/LICENSE-2.0)
* See https://github.com/openiddict/openiddict-core for more information concerning
* the license and the contributors participating to this project.
*/
using System.Diagnostics;
namespace OpenIddict.Client.WebIntegration;
/// <summary>
/// Represents an OpenIddict client web integration provider.
/// </summary>
[DebuggerDisplay("{Name,nq}")]
public class OpenIddictClientWebIntegrationProvider
{
/// <summary>
/// Creates a new instance of the <see cref="OpenIddictClientWebIntegrationProvider"/> class.
/// </summary>
/// <param name="name">The provider name.</param>
/// <param name="settings">The provider settings.</param>
/// <exception cref="ArgumentException"><paramref name="name"/> is null or empty.</exception>
/// <exception cref="ArgumentNullException"><paramref name="settings"/> are null.</exception>
public OpenIddictClientWebIntegrationProvider(
string name,
OpenIddictClientWebIntegrationSettings settings)
{
if (string.IsNullOrEmpty(name))
{
throw new ArgumentException(SR.GetResourceString(SR.ID0330), nameof(name));
}
Name = name;
Settings = settings ?? throw new ArgumentNullException(nameof(settings));
}
/// <summary>
/// Gets the provider name associated with the current instance.
/// </summary>
public string Name { get; }
/// <summary>
/// Gets the provider settings associated with the current instance.
/// </summary>
public OpenIddictClientWebIntegrationSettings Settings { get; }
}

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

@ -5,12 +5,12 @@
<Environment Issuer="https://appleid.apple.com/" />
<Setting Name="SigningKey" Type="SigningKey" Required="true"
Description="Gets or sets the ECDSA signing key associated with the developer account.">
Description="The Elliptic Curve Digital Signature Algorithm (ECDSA) signing key associated with the developer account">
<SigningAlgorithm Value="ES256" />
</Setting>
<Setting Name="TeamId" Type="String" Required="true"
Description="Gets or sets the Team ID associated with the developer account." />
Description="The team ID associated with the developer account" />
</Provider>
<Provider Name="GitHub" Documentation="https://docs.github.com/en/developers/apps/building-oauth-apps/authorizing-oauth-apps">
@ -36,7 +36,7 @@
<Environment Issuer="https://login.microsoftonline.com/{tenant}/v2.0" />
<Setting Name="Tenant" Type="String" Required="false" DefaultValue="common"
Description="Gets or sets the tenant used to identify the Azure AD instance (by default, the common tenant is used)." />
Description="The tenant used to identify the Azure AD instance (by default, the common tenant is used)" />
</Provider>
<Provider Name="Reddit" Documentation="https://github.com/reddit-archive/reddit/wiki/OAuth2">
@ -79,12 +79,12 @@
</Environment>
<Setting Name="Expansions" Collection="true" Type="String"
Description="Gets the list of data objects to expand from the userinfo endpoint (by default, all known expansions are requested).">
Description="The list of data objects to expand from the userinfo endpoint (by default, all known expansions are requested)">
<CollectionItem Value="pinned_tweet_id" Default="true" Required="false" />
</Setting>
<Setting Name="TweetFields" Collection="true" Type="String"
Description="Gets the tweet fields that should be retrieved from the userinfo endpoint (by default, all known tweet fields are requested).">
Description="The tweet fields that should be retrieved from the userinfo endpoint (by default, all known tweet fields are requested)">
<CollectionItem Value="attachments" Default="true" Required="false" />
<CollectionItem Value="author_id" Default="true" Required="false" />
<CollectionItem Value="context_annotations" Default="true" Required="false" />
@ -108,7 +108,7 @@
</Setting>
<Setting Name="UserFields" Collection="true" Type="String"
Description="Gets the user fields that should be retrieved from the userinfo endpoint (by default, all known user fields are requested).">
Description="The user fields that should be retrieved from the userinfo endpoint (by default, all known user fields are requested)">
<CollectionItem Value="created_at" Default="true" Required="false" />
<CollectionItem Value="description" Default="true" Required="false" />
<CollectionItem Value="entities" Default="true" Required="false" />

43
src/OpenIddict.Client.WebIntegration/OpenIddictClientWebIntegrationSettings.cs

@ -1,43 +0,0 @@
/*
* Licensed under the Apache License, Version 2.0 (http://www.apache.org/licenses/LICENSE-2.0)
* See https://github.com/openiddict/openiddict-core for more information concerning
* the license and the contributors participating to this project.
*/
namespace OpenIddict.Client.WebIntegration;
/// <summary>
/// Provides various settings needed to configure the OpenIddict client Web providers.
/// </summary>
public abstract partial class OpenIddictClientWebIntegrationSettings
{
/// <summary>
/// Gets or sets the client identifier.
/// </summary>
public virtual string? ClientId { get; set; }
/// <summary>
/// Gets or sets the client secret, if applicable.
/// </summary>
public virtual string? ClientSecret { get; set; }
/// <summary>
/// Gets or sets the product name used in the user agent header.
/// </summary>
public string? ProductName { get; set; }
/// <summary>
/// Gets or sets the product version used in the user agent header.
/// </summary>
public string? ProductVersion { get; set; }
/// <summary>
/// Gets or sets the redirection URL.
/// </summary>
public virtual Uri? RedirectUri { get; set; }
/// <summary>
/// Gets the scopes requested to the authorization server.
/// </summary>
public virtual HashSet<string> Scopes { get; } = new(StringComparer.Ordinal);
}

16
src/OpenIddict.Client/OpenIddictClientBuilder.cs

@ -1096,25 +1096,15 @@ public class OpenIddictClientBuilder
public OpenIddictClientBuilder SetStateTokenLifetime(TimeSpan? lifetime)
=> Configure(options => options.StateTokenLifetime = lifetime);
/// <summary>
/// Determines whether the specified object is equal to the current object.
/// </summary>
/// <param name="obj">The object to compare with the current object.</param>
/// <returns><see langword="true"/> if the specified object is equal to the current object; otherwise, false.</returns>
/// <inheritdoc/>
[EditorBrowsable(EditorBrowsableState.Never)]
public override bool Equals(object? obj) => base.Equals(obj);
/// <summary>
/// Serves as the default hash function.
/// </summary>
/// <returns>A hash code for the current object.</returns>
/// <inheritdoc/>
[EditorBrowsable(EditorBrowsableState.Never)]
public override int GetHashCode() => base.GetHashCode();
/// <summary>
/// Returns a string that represents the current object.
/// </summary>
/// <returns>A string that represents the current object.</returns>
/// <inheritdoc/>
[EditorBrowsable(EditorBrowsableState.Never)]
public override string? ToString() => base.ToString();
}

22
src/OpenIddict.Validation.SystemNetHttp/OpenIddictValidationSystemNetHttpBuilder.cs

@ -5,6 +5,7 @@
*/
using System.ComponentModel;
using System.Net.Http.Headers;
using OpenIddict.Validation.SystemNetHttp;
using Polly;
@ -51,9 +52,28 @@ public class OpenIddictValidationSystemNetHttpBuilder
/// </summary>
/// <param name="policy">The HTTP Polly error policy.</param>
/// <returns>The <see cref="OpenIddictValidationSystemNetHttpBuilder"/>.</returns>
public OpenIddictValidationSystemNetHttpBuilder SetHttpErrorPolicy(IAsyncPolicy<HttpResponseMessage> policy)
public OpenIddictValidationSystemNetHttpBuilder SetHttpErrorPolicy(IAsyncPolicy<HttpResponseMessage>? policy)
=> Configure(options => options.HttpErrorPolicy = policy);
/// <summary>
/// Sets the product information used in the user agent header that is attached
/// to the backchannel HTTP requests sent to the authorization server.
/// </summary>
/// <param name="information">The product information.</param>
/// <returns>The <see cref="OpenIddictValidationSystemNetHttpBuilder"/>.</returns>
public OpenIddictValidationSystemNetHttpBuilder SetProductInformation(ProductInfoHeaderValue? information)
=> Configure(options => options.ProductInformation = information);
/// <summary>
/// Sets the product information used in the user agent header that is attached
/// to the backchannel HTTP requests sent to the authorization server.
/// </summary>
/// <param name="name">The product name.</param>
/// <param name="version">The product version.</param>
/// <returns>The <see cref="OpenIddictValidationSystemNetHttpBuilder"/>.</returns>
public OpenIddictValidationSystemNetHttpBuilder SetProductInformation(string? name, string? version)
=> SetProductInformation(!string.IsNullOrEmpty(name) ? new ProductInfoHeaderValue(name, version) : null);
/// <inheritdoc/>
[EditorBrowsable(EditorBrowsableState.Never)]
public override bool Equals(object? obj) => base.Equals(obj);

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

@ -35,8 +35,7 @@ public class OpenIddictValidationSystemNetHttpConfiguration : IConfigureOptions<
options.Handlers.AddRange(OpenIddictValidationSystemNetHttpHandlers.DefaultHandlers);
}
public void Configure(HttpClientFactoryOptions options)
=> Debug.Fail("This infrastructure method shouldn't be called.");
public void Configure(HttpClientFactoryOptions options) => Configure(Options.DefaultName, options);
public void Configure(string name, HttpClientFactoryOptions options)
{
@ -45,8 +44,8 @@ public class OpenIddictValidationSystemNetHttpConfiguration : IConfigureOptions<
throw new ArgumentNullException(nameof(options));
}
// Only amend the HTTP client factory options if the instance is managed by OpenIddict.
var assembly = typeof(OpenIddictValidationSystemNetHttpOptions).Assembly.GetName();
if (!string.Equals(name, assembly.Name, StringComparison.Ordinal))
{
return;

17
src/OpenIddict.Validation.SystemNetHttp/OpenIddictValidationSystemNetHttpHandlers.cs

@ -11,6 +11,7 @@ using System.Diagnostics.CodeAnalysis;
using System.Net.Http.Headers;
using System.Text;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using Microsoft.Extensions.Primitives;
using static OpenIddict.Validation.SystemNetHttp.OpenIddictValidationSystemNetHttpConstants;
@ -113,6 +114,11 @@ public static partial class OpenIddictValidationSystemNetHttpHandlers
/// </summary>
public class AttachUserAgent<TContext> : IOpenIddictValidationHandler<TContext> where TContext : BaseExternalContext
{
private readonly IOptionsMonitor<OpenIddictValidationSystemNetHttpOptions> _options;
public AttachUserAgent(IOptionsMonitor<OpenIddictValidationSystemNetHttpOptions> options)
=> _options = options ?? throw new ArgumentNullException(nameof(options));
/// <summary>
/// Gets the default descriptor definition assigned to this handler.
/// </summary>
@ -139,9 +145,18 @@ public static partial class OpenIddictValidationSystemNetHttpHandlers
var request = context.Transaction.GetHttpRequestMessage() ??
throw new InvalidOperationException(SR.GetResourceString(SR.ID0173));
var assembly = typeof(OpenIddictValidationSystemNetHttpHandlers).Assembly.GetName();
// Some authorization servers are known to aggressively check user agents and encourage
// developers to use unique user agents. While a default user agent is always added,
// the default value doesn't differ accross applications. To reduce the risks of seeing
// requests blocked, a more specific user agent header can be configured by the developer.
// In this case, the value specified by the developer always appears first in the list.
if (_options.CurrentValue.ProductInformation is ProductInfoHeaderValue information)
{
request.Headers.UserAgent.Add(information);
}
// Attach a user agent based on the assembly version of the System.Net.Http integration.
var assembly = typeof(OpenIddictValidationSystemNetHttpHandlers).Assembly.GetName();
request.Headers.UserAgent.Add(new ProductInfoHeaderValue(
productName: assembly.Name!,
productVersion: assembly.Version!.ToString()));

7
src/OpenIddict.Validation.SystemNetHttp/OpenIddictValidationSystemNetHttpOptions.cs

@ -5,6 +5,7 @@
*/
using System.Net;
using System.Net.Http.Headers;
using Polly;
using Polly.Extensions.Http;
@ -22,4 +23,10 @@ public class OpenIddictValidationSystemNetHttpOptions
= HttpPolicyExtensions.HandleTransientHttpError()
.OrResult(response => response.StatusCode == HttpStatusCode.NotFound)
.WaitAndRetryAsync(3, attempt => TimeSpan.FromSeconds(Math.Pow(2, attempt)));
/// <summary>
/// Gets or sets the product information used in the user agent header that is
/// attached to the backchannel HTTP requests sent to the authorization server.
/// </summary>
public ProductInfoHeaderValue? ProductInformation { get; set; }
}

Loading…
Cancel
Save