diff --git a/.editorconfig b/.editorconfig index 5bcacc92d..547eadea8 100644 --- a/.editorconfig +++ b/.editorconfig @@ -6,6 +6,8 @@ root=true #### Core EditorConfig Options #### +resharper_replace_auto_property_with_computed_property_highlighting = none + # Indentation and spacing indent_size=4 indent_style=space @@ -1307,8 +1309,6 @@ resharper_parameter_doesnt_make_any_sense_highlighting=warning resharper_parameter_hides_member_highlighting=warning resharper_parameter_only_used_for_precondition_check_global_highlighting=suggestion resharper_parameter_only_used_for_precondition_check_local_highlighting=warning -resharper_parameter_type_can_be_enumerable_global_highlighting=hint -resharper_parameter_type_can_be_enumerable_local_highlighting=hint resharper_parameter_value_is_not_used_highlighting=warning resharper_partial_method_parameter_name_mismatch_highlighting=warning resharper_partial_method_with_single_part_highlighting=warning @@ -1517,8 +1517,6 @@ resharper_resource_item_not_resolved_highlighting=error resharper_resource_not_resolved_highlighting=error resharper_resx_not_resolved_highlighting=warning resharper_return_from_global_scopet_with_value_highlighting=warning -resharper_return_type_can_be_enumerable_global_highlighting=hint -resharper_return_type_can_be_enumerable_local_highlighting=hint resharper_return_value_of_pure_method_is_not_used_highlighting=warning resharper_safe_cast_is_used_as_type_check_highlighting=suggestion resharper_same_imports_with_different_name_highlighting=warning diff --git a/src/Directory.Build.targets b/src/Directory.Build.targets index 485aa1932..8a9cab353 100644 --- a/src/Directory.Build.targets +++ b/src/Directory.Build.targets @@ -2,7 +2,7 @@ net8.0 - 8.0.18 + 8.0.19 @@ -46,12 +46,12 @@ - + - - + + diff --git a/src/IdentityServer4/host/Extensions/ExtensionGrantValidator.cs b/src/IdentityServer4/host/Extensions/ExtensionGrantValidator.cs index 10ed45ace..c84e1929d 100644 --- a/src/IdentityServer4/host/Extensions/ExtensionGrantValidator.cs +++ b/src/IdentityServer4/host/Extensions/ExtensionGrantValidator.cs @@ -27,8 +27,5 @@ public Task ValidateAsync(ExtensionGrantValidationContext context) return Task.CompletedTask; } - public string GrantType - { - get { return "custom"; } - } + public string GrantType { get; } = "custom"; } \ No newline at end of file diff --git a/src/IdentityServer4/src/Extensions/IEnumerableExtensions.cs b/src/IdentityServer4/src/Extensions/IEnumerableExtensions.cs index aae250e6b..0983d45ef 100644 --- a/src/IdentityServer4/src/Extensions/IEnumerableExtensions.cs +++ b/src/IdentityServer4/src/Extensions/IEnumerableExtensions.cs @@ -5,6 +5,7 @@ using System; using System.Collections.Generic; using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; using System.Linq; #pragma warning disable 1591 @@ -14,19 +15,10 @@ namespace IdentityServer4.Extensions; public static class IEnumerableExtensions { [DebuggerStepThrough] - public static bool IsNullOrEmpty(this IEnumerable list) + public static bool IsNullOrEmpty([NotNullWhen(false)] this IEnumerable? list) { - if (list == null) - { - return true; - } - - if (!list.Any()) - { - return true; - } - - return false; + if (list is null) return true; + return !list.Any(); } public static bool HasDuplicates(this IEnumerable list, Func selector) @@ -39,6 +31,7 @@ public static bool HasDuplicates(this IEnumerable list, Func "DistributedCacheStateDataFormatter"; + private string CacheKeyPrefix { get; } = "DistributedCacheStateDataFormatter"; private IDistributedCache Cache => _httpContext.HttpContext.RequestServices.GetRequiredService(); private IDataProtector Protector => _httpContext.HttpContext.RequestServices.GetRequiredService().CreateProtector(CacheKeyPrefix, _name); diff --git a/src/IdentityServer4/src/Models/GrantTypes.cs b/src/IdentityServer4/src/Models/GrantTypes.cs index d32768357..8b326db74 100644 --- a/src/IdentityServer4/src/Models/GrantTypes.cs +++ b/src/IdentityServer4/src/Models/GrantTypes.cs @@ -2,41 +2,29 @@ // Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information. -using System.Collections.Generic; - #pragma warning disable 1591 namespace IdentityServer4.Models; -public class GrantTypes +public static class GrantTypes { - public static ICollection Implicit => - [GrantType.Implicit]; + public static string[] Implicit => [GrantType.Implicit]; - public static ICollection ImplicitAndClientCredentials => - [GrantType.Implicit, GrantType.ClientCredentials]; + public static string[] ImplicitAndClientCredentials => [GrantType.Implicit, GrantType.ClientCredentials]; - public static ICollection Code => - [GrantType.AuthorizationCode]; + public static string[] Code => [GrantType.AuthorizationCode]; - public static ICollection CodeAndClientCredentials => - [GrantType.AuthorizationCode, GrantType.ClientCredentials]; + public static string[] CodeAndClientCredentials => [GrantType.AuthorizationCode, GrantType.ClientCredentials]; - public static ICollection Hybrid => - [GrantType.Hybrid]; + public static string[] Hybrid => [GrantType.Hybrid]; - public static ICollection HybridAndClientCredentials => - [GrantType.Hybrid, GrantType.ClientCredentials]; + public static string[] HybridAndClientCredentials => [GrantType.Hybrid, GrantType.ClientCredentials]; - public static ICollection ClientCredentials => - [GrantType.ClientCredentials]; + public static string[] ClientCredentials => [GrantType.ClientCredentials]; - public static ICollection ResourceOwnerPassword => - [GrantType.ResourceOwnerPassword]; + public static string[] ResourceOwnerPassword => [GrantType.ResourceOwnerPassword]; - public static ICollection ResourceOwnerPasswordAndClientCredentials => - [GrantType.ResourceOwnerPassword, GrantType.ClientCredentials]; + public static string[] ResourceOwnerPasswordAndClientCredentials => [GrantType.ResourceOwnerPassword, GrantType.ClientCredentials]; - public static ICollection DeviceFlow => - [GrantType.DeviceFlow]; + public static string[] DeviceFlow => [GrantType.DeviceFlow]; } \ No newline at end of file diff --git a/src/IdentityServer4/src/Services/Default/OidcReturnUrlParser.cs b/src/IdentityServer4/src/Services/Default/OidcReturnUrlParser.cs index 35eb15470..3b13fa767 100644 --- a/src/IdentityServer4/src/Services/Default/OidcReturnUrlParser.cs +++ b/src/IdentityServer4/src/Services/Default/OidcReturnUrlParser.cs @@ -3,14 +3,12 @@ using System; -using System.Collections.Generic; using System.Threading.Tasks; using IdentityServer4.Models; using IdentityServer4.Extensions; using IdentityServer4.Validation; using Microsoft.Extensions.Logging; using IdentityServer4.Stores; -using System.Collections.Specialized; using System.IdentityModel.Tokens.Jwt; using System.Linq; diff --git a/src/IdentityServer4/src/Test/TestUser.cs b/src/IdentityServer4/src/Test/TestUser.cs index c5760d4c5..afd6d144a 100644 --- a/src/IdentityServer4/src/Test/TestUser.cs +++ b/src/IdentityServer4/src/Test/TestUser.cs @@ -46,5 +46,5 @@ public class TestUser /// /// Gets or sets the claims. /// - public ICollection Claims { get; set; } = new HashSet(new ClaimComparer()); + public HashSet Claims { get; set; } = new(new ClaimComparer()); } \ No newline at end of file diff --git a/src/IdentityServer4/src/Test/TestUserResourceOwnerPasswordValidator.cs b/src/IdentityServer4/src/Test/TestUserResourceOwnerPasswordValidator.cs index f0fd0131e..1bcd54c94 100644 --- a/src/IdentityServer4/src/Test/TestUserResourceOwnerPasswordValidator.cs +++ b/src/IdentityServer4/src/Test/TestUserResourceOwnerPasswordValidator.cs @@ -6,6 +6,7 @@ using IdentityServer4.Validation; using System.Threading.Tasks; using System; +using System.Linq; using Microsoft.AspNetCore.Authentication; namespace IdentityServer4.Test; @@ -43,7 +44,7 @@ public Task ValidateAsync(ResourceOwnerPasswordValidationContext context) context.Result = new GrantValidationResult( user.SubjectId ?? throw new ArgumentException("Subject ID not set", nameof(user.SubjectId)), OidcConstants.AuthenticationMethods.Password, _clock.UtcNow.UtcDateTime, - user.Claims); + user.Claims.ToArray()); } return Task.CompletedTask; diff --git a/src/IdentityServer4/src/Test/TestUserStore.cs b/src/IdentityServer4/src/Test/TestUserStore.cs index c9de09630..30fa155e5 100644 --- a/src/IdentityServer4/src/Test/TestUserStore.cs +++ b/src/IdentityServer4/src/Test/TestUserStore.cs @@ -115,7 +115,7 @@ public TestUser AutoProvisionUser(string provider, string userId, List cl } // if no display name was provided, try to construct by first and/or last name - if (!filtered.Any(x => x.Type == JwtClaimTypes.Name)) + if (filtered.All(x => x.Type != JwtClaimTypes.Name)) { var first = filtered.FirstOrDefault(x => x.Type == JwtClaimTypes.GivenName)?.Value; var last = filtered.FirstOrDefault(x => x.Type == JwtClaimTypes.FamilyName)?.Value; @@ -146,7 +146,7 @@ public TestUser AutoProvisionUser(string provider, string userId, List cl Username = name, ProviderName = provider, ProviderSubjectId = userId, - Claims = filtered + Claims = filtered.ToHashSet() }; // add user to in-memory store diff --git a/src/IdentityServer4/src/Validation/Default/ExtensionGrantValidator.cs b/src/IdentityServer4/src/Validation/Default/ExtensionGrantValidator.cs index 99bb8d29b..eb9a574c8 100644 --- a/src/IdentityServer4/src/Validation/Default/ExtensionGrantValidator.cs +++ b/src/IdentityServer4/src/Validation/Default/ExtensionGrantValidator.cs @@ -5,8 +5,8 @@ using IdentityServer4.Models; using Microsoft.Extensions.Logging; using System; +using System.Collections.Frozen; using System.Collections.Generic; -using System.Linq; using System.Threading.Tasks; namespace IdentityServer4.Validation; @@ -17,7 +17,8 @@ namespace IdentityServer4.Validation; public class ExtensionGrantValidator { private readonly ILogger _logger; - private readonly IEnumerable _validators; + private readonly FrozenSet _availableGrantTypes; + private readonly FrozenDictionary _validators; /// /// Initializes a new instance of the class. @@ -26,26 +27,17 @@ public class ExtensionGrantValidator /// The logger. public ExtensionGrantValidator(IEnumerable validators, ILogger logger) { - if (validators == null) - { - _validators = []; - } - else - { - _validators = validators; - } - _logger = logger; + + _validators = validators.ToFrozenDictionary(x => x.GrantType); + _availableGrantTypes = _validators.Keys.ToFrozenSet(); } /// /// Gets the available grant types. /// /// - public IEnumerable GetAvailableGrantTypes() - { - return _validators.Select(v => v.GrantType); - } + public FrozenSet GetAvailableGrantTypes() => _availableGrantTypes; /// /// Validates the request. @@ -54,9 +46,7 @@ public IEnumerable GetAvailableGrantTypes() /// public async Task ValidateAsync(ValidatedTokenRequest request) { - var validator = _validators.FirstOrDefault(v => v.GrantType.Equals(request.GrantType, StringComparison.Ordinal)); - - if (validator == null) + if (!_validators.TryGetValue(request.GrantType, out var validator)) { _logger.LogError("No validator found for grant type"); return new GrantValidationResult(TokenRequestErrors.UnsupportedGrantType); @@ -64,19 +54,19 @@ public async Task ValidateAsync(ValidatedTokenRequest req try { - _logger.LogTrace("Calling into custom grant validator: {type}", validator.GetType().FullName); + _logger.LogTrace("Calling into custom grant validator: {Type}", validator.GetType().FullName); var context = new ExtensionGrantValidationContext { Request = request }; - + await validator.ValidateAsync(context); return context.Result; } catch (Exception e) { - _logger.LogError(1, e, "Grant validation error: {message}", e.Message); + _logger.LogError(1, e, "Grant validation error: {Message}", e.Message); return new GrantValidationResult(TokenRequestErrors.InvalidGrant); } } diff --git a/src/IdentityServer4/src/Validation/Default/TokenRequestValidator.cs b/src/IdentityServer4/src/Validation/Default/TokenRequestValidator.cs index d406b2dda..65fb86b1c 100644 --- a/src/IdentityServer4/src/Validation/Default/TokenRequestValidator.cs +++ b/src/IdentityServer4/src/Validation/Default/TokenRequestValidator.cs @@ -589,7 +589,7 @@ private async Task ValidateExtensionGrantRequestAs ///////////////////////////////////////////// // check if a validator is registered for the grant type ///////////////////////////////////////////// - if (!_extensionGrantValidator.GetAvailableGrantTypes().Contains(_validatedRequest.GrantType, StringComparer.Ordinal)) + if (!_extensionGrantValidator.GetAvailableGrantTypes().Contains(_validatedRequest.GrantType)) { LogError("No validator is registered for the grant type", new { grantType = _validatedRequest.GrantType }); return Invalid(OidcConstants.TokenErrors.UnsupportedGrantType); diff --git a/src/IdentityServer4/src/Validation/Models/GrantValidationResult.cs b/src/IdentityServer4/src/Validation/Models/GrantValidationResult.cs index 103c79d40..795721331 100644 --- a/src/IdentityServer4/src/Validation/Models/GrantValidationResult.cs +++ b/src/IdentityServer4/src/Validation/Models/GrantValidationResult.cs @@ -28,13 +28,13 @@ public class GrantValidationResult : ValidationResult /// /// Custom fields for the token response /// - public Dictionary CustomResponse { get; set; } = new(); + public Dictionary? CustomResponse { get; set; } /// /// Initializes a new instance of the class with no subject. /// Warning: the resulting access token will only contain the client identity. /// - public GrantValidationResult(Dictionary customResponse = null) + public GrantValidationResult(Dictionary? customResponse = null) { IsError = false; CustomResponse = customResponse; @@ -44,15 +44,19 @@ public GrantValidationResult(Dictionary customResponse = null) /// Initializes a new instance of the class with a given principal. /// Warning: the principal needs to include the required claims - it is recommended to use the other constructor that does validation. /// - public GrantValidationResult(ClaimsPrincipal principal, Dictionary customResponse = null) + public GrantValidationResult(ClaimsPrincipal principal, Dictionary? customResponse = null) { IsError = false; if (principal.Identities.Count() != 1) throw new InvalidOperationException("only a single identity supported"); - if (principal.FindFirst(JwtClaimTypes.Subject) == null) throw new InvalidOperationException("sub claim is missing"); - if (principal.FindFirst(JwtClaimTypes.IdentityProvider) == null) throw new InvalidOperationException("idp claim is missing"); - if (principal.FindFirst(JwtClaimTypes.AuthenticationMethod) == null) throw new InvalidOperationException("amr claim is missing"); - if (principal.FindFirst(JwtClaimTypes.AuthenticationTime) == null) throw new InvalidOperationException("auth_time claim is missing"); + if (principal.FindFirst(JwtClaimTypes.Subject) == null) + throw new InvalidOperationException("sub claim is missing"); + if (principal.FindFirst(JwtClaimTypes.IdentityProvider) == null) + throw new InvalidOperationException("idp claim is missing"); + if (principal.FindFirst(JwtClaimTypes.AuthenticationMethod) == null) + throw new InvalidOperationException("amr claim is missing"); + if (principal.FindFirst(JwtClaimTypes.AuthenticationTime) == null) + throw new InvalidOperationException("auth_time claim is missing"); Subject = principal; CustomResponse = customResponse; @@ -64,7 +68,8 @@ public GrantValidationResult(ClaimsPrincipal principal, DictionaryThe error. /// The error description. /// Custom response elements - public GrantValidationResult(TokenRequestErrors error, string errorDescription = null, Dictionary customResponse = null) + public GrantValidationResult(TokenRequestErrors error, string? errorDescription = null, + Dictionary? customResponse = null) { Error = ConvertTokenErrorEnumToString(error); ErrorDescription = errorDescription; @@ -83,9 +88,9 @@ public GrantValidationResult(TokenRequestErrors error, string errorDescription = public GrantValidationResult( string subject, string authenticationMethod, - IEnumerable claims = null, + Claim[]? claims = null, string identityProvider = IdentityServerConstants.LocalIdentityProvider, - Dictionary customResponse = null) + Dictionary? customResponse = null) : this(subject, authenticationMethod, DateTime.UtcNow, claims, identityProvider, customResponse) { } @@ -104,9 +109,9 @@ public GrantValidationResult( string subject, string authenticationMethod, DateTime authTime, - IEnumerable claims = null, + Claim[]? claims = null, string identityProvider = IdentityServerConstants.LocalIdentityProvider, - Dictionary customResponse = null) + Dictionary? customResponse = null) { IsError = false; @@ -130,18 +135,15 @@ public GrantValidationResult( CustomResponse = customResponse; } - private string ConvertTokenErrorEnumToString(TokenRequestErrors error) + private static string ConvertTokenErrorEnumToString(TokenRequestErrors error) => error switch { - return error switch - { - TokenRequestErrors.InvalidClient => OidcConstants.TokenErrors.InvalidClient, - TokenRequestErrors.InvalidGrant => OidcConstants.TokenErrors.InvalidGrant, - TokenRequestErrors.InvalidRequest => OidcConstants.TokenErrors.InvalidRequest, - TokenRequestErrors.InvalidScope => OidcConstants.TokenErrors.InvalidScope, - TokenRequestErrors.UnauthorizedClient => OidcConstants.TokenErrors.UnauthorizedClient, - TokenRequestErrors.UnsupportedGrantType => OidcConstants.TokenErrors.UnsupportedGrantType, - TokenRequestErrors.InvalidTarget => OidcConstants.TokenErrors.InvalidTarget, - _ => throw new InvalidOperationException("invalid token error") - }; - } + TokenRequestErrors.InvalidClient => OidcConstants.TokenErrors.InvalidClient, + TokenRequestErrors.InvalidGrant => OidcConstants.TokenErrors.InvalidGrant, + TokenRequestErrors.InvalidRequest => OidcConstants.TokenErrors.InvalidRequest, + TokenRequestErrors.InvalidScope => OidcConstants.TokenErrors.InvalidScope, + TokenRequestErrors.UnauthorizedClient => OidcConstants.TokenErrors.UnauthorizedClient, + TokenRequestErrors.UnsupportedGrantType => OidcConstants.TokenErrors.UnsupportedGrantType, + TokenRequestErrors.InvalidTarget => OidcConstants.TokenErrors.InvalidTarget, + _ => throw new InvalidOperationException("invalid token error") + }; } \ No newline at end of file diff --git a/src/IdentityServer4/src/Validation/Models/ValidationResult.cs b/src/IdentityServer4/src/Validation/Models/ValidationResult.cs index 0b1c15d31..502c9da57 100644 --- a/src/IdentityServer4/src/Validation/Models/ValidationResult.cs +++ b/src/IdentityServer4/src/Validation/Models/ValidationResult.cs @@ -31,5 +31,5 @@ public class ValidationResult /// /// The error description. /// - public string ErrorDescription { get; set; } + public string? ErrorDescription { get; set; } } \ No newline at end of file diff --git a/src/IdentityServer4/test/IdentityServer.IntegrationTests/Clients/Setup/DynamicParameterExtensionGrantValidator.cs b/src/IdentityServer4/test/IdentityServer.IntegrationTests/Clients/Setup/DynamicParameterExtensionGrantValidator.cs index eb6835632..5cc35d762 100644 --- a/src/IdentityServer4/test/IdentityServer.IntegrationTests/Clients/Setup/DynamicParameterExtensionGrantValidator.cs +++ b/src/IdentityServer4/test/IdentityServer.IntegrationTests/Clients/Setup/DynamicParameterExtensionGrantValidator.cs @@ -58,5 +58,5 @@ public Task ValidateAsync(ExtensionGrantValidationContext context) return Task.CompletedTask; } - public string GrantType => "dynamic"; + public string GrantType { get; } = "dynamic"; } \ No newline at end of file diff --git a/src/IdentityServer4/test/IdentityServer.IntegrationTests/Clients/Setup/ExtensionGrantValidator.cs b/src/IdentityServer4/test/IdentityServer.IntegrationTests/Clients/Setup/ExtensionGrantValidator.cs index db4f83648..34c3e6992 100644 --- a/src/IdentityServer4/test/IdentityServer.IntegrationTests/Clients/Setup/ExtensionGrantValidator.cs +++ b/src/IdentityServer4/test/IdentityServer.IntegrationTests/Clients/Setup/ExtensionGrantValidator.cs @@ -16,28 +16,20 @@ public Task ValidateAsync(ExtensionGrantValidationContext context) var credential = context.Request.Raw.Get("custom_credential"); var extraClaim = context.Request.Raw.Get("extra_claim"); - if (credential != null) - { - if (extraClaim != null) - { - context.Result = new GrantValidationResult( - "818727", - claims: [new Claim("extra_claim", extraClaim)], - authenticationMethod: GrantType); - } - else - { - context.Result = new GrantValidationResult("818727", GrantType); - } - } - else + if (credential is null) { // custom error message context.Result = new GrantValidationResult(TokenRequestErrors.InvalidGrant, "invalid_custom_credential"); + return Task.CompletedTask; } + context.Result = context.Result = new GrantValidationResult( + "818727", + claims: extraClaim is not null ? [new Claim("extra_claim", extraClaim)] : null, + authenticationMethod: GrantType); + return Task.CompletedTask; } - public string GrantType => "custom"; + public string GrantType { get; } = "custom"; } \ No newline at end of file diff --git a/src/IdentityServer4/test/IdentityServer.IntegrationTests/Clients/Setup/ExtensionGrantValidator2.cs b/src/IdentityServer4/test/IdentityServer.IntegrationTests/Clients/Setup/ExtensionGrantValidator2.cs index 9588368d0..bccbd9407 100644 --- a/src/IdentityServer4/test/IdentityServer.IntegrationTests/Clients/Setup/ExtensionGrantValidator2.cs +++ b/src/IdentityServer4/test/IdentityServer.IntegrationTests/Clients/Setup/ExtensionGrantValidator2.cs @@ -27,5 +27,5 @@ public Task ValidateAsync(ExtensionGrantValidationContext context) return Task.CompletedTask; } - public string GrantType => "custom2"; + public string GrantType { get; } = "custom2"; } \ No newline at end of file diff --git a/src/IdentityServer4/test/IdentityServer.IntegrationTests/Clients/Setup/NoSubjectExtensionGrantValidator.cs b/src/IdentityServer4/test/IdentityServer.IntegrationTests/Clients/Setup/NoSubjectExtensionGrantValidator.cs index 6829d5269..7a3785a76 100644 --- a/src/IdentityServer4/test/IdentityServer.IntegrationTests/Clients/Setup/NoSubjectExtensionGrantValidator.cs +++ b/src/IdentityServer4/test/IdentityServer.IntegrationTests/Clients/Setup/NoSubjectExtensionGrantValidator.cs @@ -27,5 +27,5 @@ public Task ValidateAsync(ExtensionGrantValidationContext context) return Task.CompletedTask; } - public string GrantType => "custom.nosubject"; + public string GrantType { get; } = "custom.nosubject"; } \ No newline at end of file diff --git a/src/IdentityServer4/test/IdentityServer.IntegrationTests/Endpoints/Authorize/JwtRequestAuthorizeTests.cs b/src/IdentityServer4/test/IdentityServer.IntegrationTests/Endpoints/Authorize/JwtRequestAuthorizeTests.cs index a27c72f38..f0ba24129 100644 --- a/src/IdentityServer4/test/IdentityServer.IntegrationTests/Endpoints/Authorize/JwtRequestAuthorizeTests.cs +++ b/src/IdentityServer4/test/IdentityServer.IntegrationTests/Endpoints/Authorize/JwtRequestAuthorizeTests.cs @@ -19,7 +19,6 @@ using IdentityServer4.Test; using Microsoft.IdentityModel.Logging; using Microsoft.IdentityModel.Tokens; -using Newtonsoft.Json; using Xunit; using JsonSerializer = System.Text.Json.JsonSerializer;