From 91a68c161a08dd17d36d8b05da1f83274a133a70 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A9vin=20Chalet?= Date: Mon, 9 Jun 2025 23:17:19 +0200 Subject: [PATCH] Implement client authentication support for the PAR endpoint --- .../Worker.cs | 3 +- .../OpenIddictClientSystemNetHttpHandlers.cs | 142 +++++++++++++- .../OpenIddictClientHandlers.cs | 13 +- .../OpenIddictServerHandlers.cs | 17 +- ...ctServerIntegrationTests.Authentication.cs | 175 +++++++++++++++++- 5 files changed, 339 insertions(+), 11 deletions(-) diff --git a/sandbox/OpenIddict.Sandbox.AspNetCore.Server/Worker.cs b/sandbox/OpenIddict.Sandbox.AspNetCore.Server/Worker.cs index 437944d7..724e60b3 100644 --- a/sandbox/OpenIddict.Sandbox.AspNetCore.Server/Worker.cs +++ b/sandbox/OpenIddict.Sandbox.AspNetCore.Server/Worker.cs @@ -139,7 +139,6 @@ public class Worker : IHostedService { ApplicationType = ApplicationTypes.Web, ClientId = "mvc", - ClientSecret = "901564A5-E7FE-42CB-B10D-61EF6A8F3654", ClientType = ClientTypes.Confidential, ConsentType = ConsentTypes.Systematic, DisplayName = "MVC client application", @@ -165,6 +164,8 @@ public class Worker : IHostedService """)) } }, +#else + ClientSecret = "901564A5-E7FE-42CB-B10D-61EF6A8F3654", #endif RedirectUris = { diff --git a/src/OpenIddict.Client.SystemNetHttp/OpenIddictClientSystemNetHttpHandlers.cs b/src/OpenIddict.Client.SystemNetHttp/OpenIddictClientSystemNetHttpHandlers.cs index c782c380..acd78464 100644 --- a/src/OpenIddict.Client.SystemNetHttp/OpenIddictClientSystemNetHttpHandlers.cs +++ b/src/OpenIddict.Client.SystemNetHttp/OpenIddictClientSystemNetHttpHandlers.cs @@ -36,6 +36,7 @@ public static partial class OpenIddictClientSystemNetHttpHandlers * Challenge processing: */ AttachNonDefaultDeviceAuthorizationEndpointClientAuthenticationMethod.Descriptor, + AttachNonDefaultPushedAuthorizationEndpointClientAuthenticationMethod.Descriptor, /* * Introspection processing: @@ -290,7 +291,15 @@ public static partial class OpenIddictClientSystemNetHttpHandlers _ => context.Options.ClientAuthenticationMethods.Intersect(context.Registration.ClientAuthenticationMethods, StringComparer.Ordinal).ToList() }, - Server: context.Configuration.DeviceAuthorizationEndpointAuthMethodsSupported) switch + // Note: if the authorization server doesn't support the OpenIddict-specific + // "device_authorization_request_endpoint_auth_methods_supported" node, + // fall back to the "token_endpoint_auth_methods_supported" node, + // which is the same logic as for the pushed authorization endpoint. + Server: context.Configuration.DeviceAuthorizationEndpointAuthMethodsSupported.Count switch + { + 0 => context.Configuration.TokenEndpointAuthMethodsSupported, + _ => context.Configuration.DeviceAuthorizationEndpointAuthMethodsSupported, + }) switch { // If a TLS client authentication certificate could be resolved and both the // client and the server explicitly support tls_client_auth, always prefer it. @@ -365,6 +374,137 @@ public static partial class OpenIddictClientSystemNetHttpHandlers } } + /// + /// Contains the logic responsible for negotiating the best pushed authorization endpoint + /// client authentication method supported by both the client and the authorization server. + /// + public sealed class AttachNonDefaultPushedAuthorizationEndpointClientAuthenticationMethod : IOpenIddictClientHandler + { + private readonly IOptionsMonitor _options; + + public AttachNonDefaultPushedAuthorizationEndpointClientAuthenticationMethod( + IOptionsMonitor options) + => _options = options ?? throw new ArgumentNullException(nameof(options)); + + /// + /// Gets the default descriptor definition assigned to this handler. + /// + public static OpenIddictClientHandlerDescriptor Descriptor { get; } + = OpenIddictClientHandlerDescriptor.CreateBuilder() + .AddFilter() + .UseSingletonHandler() + .SetOrder(AttachPushedAuthorizationEndpointClientAuthenticationMethod.Descriptor.Order - 500) + .SetType(OpenIddictClientHandlerType.BuiltIn) + .Build(); + + /// + public ValueTask HandleAsync(ProcessChallengeContext context) + { + if (context is null) + { + throw new ArgumentNullException(nameof(context)); + } + + // If an explicit client authentication method was attached, don't overwrite it. + if (!string.IsNullOrEmpty(context.PushedAuthorizationEndpointClientAuthenticationMethod)) + { + return default; + } + + context.PushedAuthorizationEndpointClientAuthenticationMethod = ( + // Note: if client authentication methods are explicitly listed in the client registration, only use + // the client authentication methods that are both listed and enabled in the global client options. + // Otherwise, always default to the client authentication methods that have been enabled globally. + Client: context.Registration.ClientAuthenticationMethods.Count switch + { + 0 => context.Options.ClientAuthenticationMethods as ICollection, + _ => context.Options.ClientAuthenticationMethods.Intersect(context.Registration.ClientAuthenticationMethods, StringComparer.Ordinal).ToList() + }, + + // Note: if the authorization server doesn't support the OpenIddict-specific + // "pushed_authorization_request_endpoint_auth_methods_supported" node, fall back to + // the "token_endpoint_auth_methods_supported" node, as required by the specification. + // + // See https://datatracker.ietf.org/doc/html/rfc9126#section-2 for more information. + Server: context.Configuration.PushedAuthorizationEndpointAuthMethodsSupported.Count switch + { + 0 => context.Configuration.TokenEndpointAuthMethodsSupported, + _ => context.Configuration.PushedAuthorizationEndpointAuthMethodsSupported, + }) switch + { + // If a TLS client authentication certificate could be resolved and both the + // client and the server explicitly support tls_client_auth, always prefer it. + ({ Count: > 0 } client, { Count: > 0 } server) when + client.Contains(ClientAuthenticationMethods.TlsClientAuth) && + server.Contains(ClientAuthenticationMethods.TlsClientAuth) && + (context.Configuration.MtlsPushedAuthorizationEndpoint ?? context.Configuration.PushedAuthorizationEndpoint) is Uri endpoint && + string.Equals(endpoint.Scheme, Uri.UriSchemeHttps, StringComparison.OrdinalIgnoreCase) && + _options.CurrentValue.TlsClientAuthenticationCertificateSelector(context.Registration) is not null + => ClientAuthenticationMethods.TlsClientAuth, + + // If a self-signed TLS client authentication certificate could be resolved and both + // the client and the server explicitly support self_signed_tls_client_auth, use it. + ({ Count: > 0 } client, { Count: > 0 } server) when + client.Contains(ClientAuthenticationMethods.SelfSignedTlsClientAuth) && + server.Contains(ClientAuthenticationMethods.SelfSignedTlsClientAuth) && + (context.Configuration.MtlsPushedAuthorizationEndpoint ?? context.Configuration.PushedAuthorizationEndpoint) is Uri endpoint && + string.Equals(endpoint.Scheme, Uri.UriSchemeHttps, StringComparison.OrdinalIgnoreCase) && + _options.CurrentValue.SelfSignedTlsClientAuthenticationCertificateSelector(context.Registration) is not null + => ClientAuthenticationMethods.SelfSignedTlsClientAuth, + + // If at least one asymmetric signing key was attached to the client registration + // and both the client and the server explicitly support private_key_jwt, use it. + ({ Count: > 0 } client, { Count: > 0 } server) when + client.Contains(ClientAuthenticationMethods.PrivateKeyJwt) && + server.Contains(ClientAuthenticationMethods.PrivateKeyJwt) && + context.Registration.SigningCredentials.Exists(static credentials => credentials.Key is AsymmetricSecurityKey) + => ClientAuthenticationMethods.PrivateKeyJwt, + + // If a client secret was attached to the client registration and both the client and + // the server explicitly support client_secret_post, prefer it to basic authentication. + ({ Count: > 0 } client, { Count: > 0 } server) when !string.IsNullOrEmpty(context.Registration.ClientSecret) && + client.Contains(ClientAuthenticationMethods.ClientSecretPost) && + server.Contains(ClientAuthenticationMethods.ClientSecretPost) + => ClientAuthenticationMethods.ClientSecretPost, + + // The OAuth 2.0 specification recommends sending the client credentials using basic authentication. + // However, this authentication method is known to have severe compatibility/interoperability issues: + // + // - While restricted to clients that have been given a secret (i.e confidential clients) by the + // specification, basic authentication is also sometimes required by server implementations for + // public clients that don't have a client secret: in this case, an empty password is used and + // the client identifier is sent alone in the Authorization header (instead of being sent using + // the standard "client_id" parameter present in the request body). + // + // - While the OAuth 2.0 specification requires that the client credentials be formURL-encoded + // before being base64-encoded, many implementations are known to implement a non-standard + // encoding scheme, where neither the client_id nor the client_secret are formURL-encoded. + // + // To guarantee that the OpenIddict implementation can be used with most servers implementions, + // basic authentication is only used when a client secret is present and the server configuration + // doesn't list any supported client authentication method or doesn't support client_secret_post. + // + // If client_secret_post is not listed or if the server returned an empty methods list, + // client_secret_basic is always used, as it MUST be implemented by all OAuth 2.0 servers. + // + // See https://tools.ietf.org/html/rfc8414#section-2 + // and https://tools.ietf.org/html/rfc6749#section-2.3.1 for more information. + ({ Count: > 0 } client, { Count: > 0 } server) when !string.IsNullOrEmpty(context.Registration.ClientSecret) && + client.Contains(ClientAuthenticationMethods.ClientSecretBasic) && + server.Contains(ClientAuthenticationMethods.ClientSecretBasic) + => ClientAuthenticationMethods.ClientSecretBasic, + + ({ Count: > 0 } client, { Count: 0 }) when !string.IsNullOrEmpty(context.Registration.ClientSecret) && + client.Contains(ClientAuthenticationMethods.ClientSecretBasic) + => ClientAuthenticationMethods.ClientSecretBasic, + + _ => null + }; + + return default; + } + } + /// /// Contains the logic responsible for negotiating the best introspection endpoint client /// authentication method supported by both the client and the authorization server. diff --git a/src/OpenIddict.Client/OpenIddictClientHandlers.cs b/src/OpenIddict.Client/OpenIddictClientHandlers.cs index fa6bbb4f..dc8691ca 100644 --- a/src/OpenIddict.Client/OpenIddictClientHandlers.cs +++ b/src/OpenIddict.Client/OpenIddictClientHandlers.cs @@ -5847,7 +5847,15 @@ public static partial class OpenIddictClientHandlers _ => context.Options.ClientAuthenticationMethods.Intersect(context.Registration.ClientAuthenticationMethods, StringComparer.Ordinal).ToList() }, - Server: context.Configuration.DeviceAuthorizationEndpointAuthMethodsSupported) switch + // Note: if the authorization server doesn't support the OpenIddict-specific + // "device_authorization_request_endpoint_auth_methods_supported" node, + // fall back to the "token_endpoint_auth_methods_supported" node, + // which is the same logic as for the pushed authorization endpoint. + Server: context.Configuration.DeviceAuthorizationEndpointAuthMethodsSupported.Count switch + { + 0 => context.Configuration.TokenEndpointAuthMethodsSupported, + _ => context.Configuration.DeviceAuthorizationEndpointAuthMethodsSupported, + }) switch { // If at least one signing key was attached to the client registration and both // the client and the server explicitly support private_key_jwt, always prefer it. @@ -6163,9 +6171,8 @@ public static partial class OpenIddictClientHandlers /// public static OpenIddictClientHandlerDescriptor Descriptor { get; } = OpenIddictClientHandlerDescriptor.CreateBuilder() - .AddFilter() .UseSingletonHandler() - .SetOrder(AttachDeviceAuthorizationRequestParameters.Descriptor.Order + 1_000) + .SetOrder(AttachPushedAuthorizationRequestParameters.Descriptor.Order + 1_000) .SetType(OpenIddictClientHandlerType.BuiltIn) .Build(); diff --git a/src/OpenIddict.Server/OpenIddictServerHandlers.cs b/src/OpenIddict.Server/OpenIddictServerHandlers.cs index b132de94..266aaf5d 100644 --- a/src/OpenIddict.Server/OpenIddictServerHandlers.cs +++ b/src/OpenIddict.Server/OpenIddictServerHandlers.cs @@ -332,7 +332,8 @@ public static partial class OpenIddictServerHandlers // By default, client assertions are not required, but they are extracted and validated if // present and invalid client assertions are always automatically rejected by OpenIddict. OpenIddictServerEndpointType.DeviceAuthorization or OpenIddictServerEndpointType.Introspection or - OpenIddictServerEndpointType.Revocation or OpenIddictServerEndpointType.Token + OpenIddictServerEndpointType.PushedAuthorization or OpenIddictServerEndpointType.Revocation or + OpenIddictServerEndpointType.Token => (true, false, true, true), _ => (false, false, false, false) @@ -483,7 +484,8 @@ public static partial class OpenIddictServerHandlers (context.ClientAssertion, context.ClientAssertionType) = context.EndpointType switch { OpenIddictServerEndpointType.DeviceAuthorization or OpenIddictServerEndpointType.Introspection or - OpenIddictServerEndpointType.Revocation or OpenIddictServerEndpointType.Token + OpenIddictServerEndpointType.PushedAuthorization or OpenIddictServerEndpointType.Revocation or + OpenIddictServerEndpointType.Token when context.ExtractClientAssertion => (context.Request.ClientAssertion, context.Request.ClientAssertionType), @@ -1032,6 +1034,12 @@ public static partial class OpenIddictServerHandlers case OpenIddictServerEndpointType.Revocation when context.Options.AcceptAnonymousClients: case OpenIddictServerEndpointType.Token when context.Options.AcceptAnonymousClients: return; + + // Note: despite being conceptually similar to the token endpoint, the pushed authorization + // endpoint deliberately doesn't allow anonymous clients, as a client_id is always required + // for both regular authorization requests and pushed authorization requests. + // + // See https://datatracker.ietf.org/doc/html/rfc9126#section-2.1 for more information. } context.Logger.LogInformation(6220, SR.GetResourceString(SR.ID6220), Parameters.ClientId); @@ -1063,7 +1071,8 @@ public static partial class OpenIddictServerHandlers { // For non-interactive endpoints, return "invalid_client" instead of "invalid_request". OpenIddictServerEndpointType.DeviceAuthorization or OpenIddictServerEndpointType.Introspection or - OpenIddictServerEndpointType.Revocation or OpenIddictServerEndpointType.Token + OpenIddictServerEndpointType.PushedAuthorization or OpenIddictServerEndpointType.Revocation or + OpenIddictServerEndpointType.Token => Errors.InvalidClient, _ => Errors.InvalidRequest @@ -1117,7 +1126,6 @@ public static partial class OpenIddictServerHandlers if (context.EndpointType is OpenIddictServerEndpointType.Authorization or OpenIddictServerEndpointType.EndSession or OpenIddictServerEndpointType.EndUserVerification or - OpenIddictServerEndpointType.PushedAuthorization or OpenIddictServerEndpointType.UserInfo) { return; @@ -1227,7 +1235,6 @@ public static partial class OpenIddictServerHandlers if (context.EndpointType is OpenIddictServerEndpointType.Authorization or OpenIddictServerEndpointType.EndSession or OpenIddictServerEndpointType.EndUserVerification or - OpenIddictServerEndpointType.PushedAuthorization or OpenIddictServerEndpointType.UserInfo) { return; diff --git a/test/OpenIddict.Server.IntegrationTests/OpenIddictServerIntegrationTests.Authentication.cs b/test/OpenIddict.Server.IntegrationTests/OpenIddictServerIntegrationTests.Authentication.cs index c80206e7..48a2df2b 100644 --- a/test/OpenIddict.Server.IntegrationTests/OpenIddictServerIntegrationTests.Authentication.cs +++ b/test/OpenIddict.Server.IntegrationTests/OpenIddictServerIntegrationTests.Authentication.cs @@ -3214,6 +3214,130 @@ public abstract partial class OpenIddictServerIntegrationTests Assert.Equal(SR.FormatID8000(SR.ID2029), response.ErrorUri); } + [Fact] + public async Task ValidatePushedAuthorizationRequest_ClientSecretCannotBeUsedByPublicClients() + { + // Arrange + var application = new OpenIddictApplication(); + + var manager = CreateApplicationManager(mock => + { + mock.Setup(manager => manager.FindByClientIdAsync("Fabrikam", It.IsAny())) + .ReturnsAsync(application); + + mock.Setup(manager => manager.HasClientTypeAsync(application, ClientTypes.Public, It.IsAny())) + .ReturnsAsync(true); + }); + + await using var server = await CreateServerAsync(options => + { + options.Services.AddSingleton(manager); + }); + + await using var client = await server.CreateClientAsync(); + + // Act + var response = await client.PostAsync("/connect/par", new OpenIddictRequest + { + ClientId = "Fabrikam", + ClientSecret = "7Fjfp0ZBr1KtDRbnfVdmIw", + RedirectUri = "http://www.fabrikam.com/path", + ResponseType = ResponseTypes.Code + }); + + // Assert + Assert.Equal(Errors.InvalidClient, response.Error); + Assert.Equal(SR.FormatID2053(Parameters.ClientSecret), response.ErrorDescription); + Assert.Equal(SR.FormatID8000(SR.ID2053), response.ErrorUri); + + Mock.Get(manager).Verify(manager => manager.FindByClientIdAsync("Fabrikam", It.IsAny()), Times.AtLeastOnce()); + Mock.Get(manager).Verify(manager => manager.HasClientTypeAsync(application, ClientTypes.Public, It.IsAny()), Times.Once()); + } + + [Fact] + public async Task ValidatePushedAuthorizationRequest_ClientSecretIsRequiredForNonPublicClients() + { + // Arrange + var application = new OpenIddictApplication(); + + var manager = CreateApplicationManager(mock => + { + mock.Setup(manager => manager.FindByClientIdAsync("Fabrikam", It.IsAny())) + .ReturnsAsync(application); + + mock.Setup(manager => manager.HasClientTypeAsync(application, ClientTypes.Public, It.IsAny())) + .ReturnsAsync(false); + }); + + await using var server = await CreateServerAsync(options => + { + options.Services.AddSingleton(manager); + }); + + await using var client = await server.CreateClientAsync(); + + // Act + var response = await client.PostAsync("/connect/par", new OpenIddictRequest + { + ClientId = "Fabrikam", + ClientSecret = null, + RedirectUri = "http://www.fabrikam.com/path", + ResponseType = ResponseTypes.Code + }); + + // Assert + Assert.Equal(Errors.InvalidClient, response.Error); + Assert.Equal(SR.FormatID2054(Parameters.ClientSecret), response.ErrorDescription); + Assert.Equal(SR.FormatID8000(SR.ID2054), response.ErrorUri); + + Mock.Get(manager).Verify(manager => manager.FindByClientIdAsync("Fabrikam", It.IsAny()), Times.AtLeastOnce()); + Mock.Get(manager).Verify(manager => manager.HasClientTypeAsync(application, ClientTypes.Public, It.IsAny()), Times.Once()); + } + + [Fact] + public async Task ValidatePushedAuthorizationRequest_RequestIsRejectedWhenClientCredentialsAreInvalid() + { + // Arrange + var application = new OpenIddictApplication(); + + var manager = CreateApplicationManager(mock => + { + mock.Setup(manager => manager.FindByClientIdAsync("Fabrikam", It.IsAny())) + .ReturnsAsync(application); + + mock.Setup(manager => manager.HasClientTypeAsync(application, ClientTypes.Public, It.IsAny())) + .ReturnsAsync(false); + + mock.Setup(manager => manager.ValidateClientSecretAsync(application, "7Fjfp0ZBr1KtDRbnfVdmIw", It.IsAny())) + .ReturnsAsync(false); + }); + + await using var server = await CreateServerAsync(options => + { + options.Services.AddSingleton(manager); + }); + + await using var client = await server.CreateClientAsync(); + + // Act + var response = await client.PostAsync("/connect/par", new OpenIddictRequest + { + ClientId = "Fabrikam", + ClientSecret = "7Fjfp0ZBr1KtDRbnfVdmIw", + RedirectUri = "http://www.fabrikam.com/path", + ResponseType = ResponseTypes.Code + }); + + // Assert + Assert.Equal(Errors.InvalidClient, response.Error); + Assert.Equal(SR.GetResourceString(SR.ID2055), response.ErrorDescription); + Assert.Equal(SR.FormatID8000(SR.ID2055), response.ErrorUri); + + Mock.Get(manager).Verify(manager => manager.FindByClientIdAsync("Fabrikam", It.IsAny()), Times.AtLeastOnce()); + Mock.Get(manager).Verify(manager => manager.HasClientTypeAsync(application, ClientTypes.Public, It.IsAny()), Times.AtLeastOnce()); + Mock.Get(manager).Verify(manager => manager.ValidateClientSecretAsync(application, "7Fjfp0ZBr1KtDRbnfVdmIw", It.IsAny()), Times.Once()); + } + [Fact] public async Task ValidatePushedAuthorizationRequest_MissingRedirectUriCausesAnErrorForOpenIdRequests() { @@ -4186,7 +4310,7 @@ public abstract partial class OpenIddictServerIntegrationTests }); // Assert - Assert.Equal(Errors.InvalidRequest, response.Error); + Assert.Equal(Errors.InvalidClient, response.Error); Assert.Equal(SR.FormatID2052(Parameters.ClientId), response.ErrorDescription); Assert.Equal(SR.FormatID8000(SR.ID2052), response.ErrorUri); @@ -4233,6 +4357,9 @@ public abstract partial class OpenIddictServerIntegrationTests mock.Setup(manager => manager.FindByClientIdAsync("Fabrikam", It.IsAny())) .ReturnsAsync(application); + mock.Setup(manager => manager.HasClientTypeAsync(application, ClientTypes.Public, It.IsAny())) + .ReturnsAsync(true); + mock.Setup(manager => manager.ValidateRedirectUriAsync(application, "http://www.fabrikam.com/path", It.IsAny())) .ReturnsAsync(true); @@ -4274,6 +4401,9 @@ public abstract partial class OpenIddictServerIntegrationTests mock.Setup(manager => manager.FindByClientIdAsync("Fabrikam", It.IsAny())) .ReturnsAsync(application); + mock.Setup(manager => manager.HasClientTypeAsync(application, ClientTypes.Public, It.IsAny())) + .ReturnsAsync(true); + mock.Setup(manager => manager.GetPermissionsAsync(application, It.IsAny())) .ReturnsAsync(["rst:" + type]); @@ -4337,6 +4467,9 @@ public abstract partial class OpenIddictServerIntegrationTests mock.Setup(manager => manager.HasClientTypeAsync(application, ClientTypes.Confidential, It.IsAny())) .ReturnsAsync(true); + mock.Setup(manager => manager.ValidateClientSecretAsync(application, "7Fjfp0ZBr1KtDRbnfVdmIw", It.IsAny())) + .ReturnsAsync(true); + mock.Setup(manager => manager.GetPermissionsAsync(application, It.IsAny())) .ReturnsAsync([]); }); @@ -4352,6 +4485,7 @@ public abstract partial class OpenIddictServerIntegrationTests var response = await client.PostAsync("/connect/par", new OpenIddictRequest { ClientId = "Fabrikam", + ClientSecret = "7Fjfp0ZBr1KtDRbnfVdmIw", Nonce = "n-0S6_WzA2Mj", RedirectUri = "http://www.fabrikam.com/path", ResponseType = type, @@ -4378,6 +4512,9 @@ public abstract partial class OpenIddictServerIntegrationTests mock.Setup(manager => manager.FindByClientIdAsync("Fabrikam", It.IsAny())) .ReturnsAsync(application); + mock.Setup(manager => manager.HasClientTypeAsync(application, ClientTypes.Public, It.IsAny())) + .ReturnsAsync(true); + mock.Setup(manager => manager.ValidateRedirectUriAsync(application, "http://www.fabrikam.com/path", It.IsAny())) .ReturnsAsync(true); @@ -4453,6 +4590,9 @@ public abstract partial class OpenIddictServerIntegrationTests mock.Setup(manager => manager.FindByClientIdAsync("Fabrikam", It.IsAny())) .ReturnsAsync(application); + mock.Setup(manager => manager.HasClientTypeAsync(application, ClientTypes.Public, It.IsAny())) + .ReturnsAsync(true); + mock.Setup(manager => manager.ValidateRedirectUriAsync(application, "http://www.fabrikam.com/path", It.IsAny())) .ReturnsAsync(true); @@ -4510,6 +4650,9 @@ public abstract partial class OpenIddictServerIntegrationTests mock.Setup(manager => manager.FindByClientIdAsync("Fabrikam", It.IsAny())) .ReturnsAsync(application); + mock.Setup(manager => manager.HasClientTypeAsync(application, ClientTypes.Public, It.IsAny())) + .ReturnsAsync(true); + mock.Setup(manager => manager.ValidateRedirectUriAsync(application, "http://www.fabrikam.com/path", It.IsAny())) .ReturnsAsync(true); @@ -4560,6 +4703,9 @@ public abstract partial class OpenIddictServerIntegrationTests mock.Setup(manager => manager.FindByClientIdAsync("Fabrikam", It.IsAny())) .ReturnsAsync(application); + mock.Setup(manager => manager.HasClientTypeAsync(application, ClientTypes.Public, It.IsAny())) + .ReturnsAsync(true); + mock.Setup(manager => manager.GetPermissionsAsync(application, It.IsAny())) .ReturnsAsync(["rst:" + type]); @@ -4617,6 +4763,9 @@ public abstract partial class OpenIddictServerIntegrationTests mock.Setup(manager => manager.FindByClientIdAsync("Fabrikam", It.IsAny())) .ReturnsAsync(application); + mock.Setup(manager => manager.HasClientTypeAsync(application, ClientTypes.Public, It.IsAny())) + .ReturnsAsync(true); + mock.Setup(manager => manager.ValidateRedirectUriAsync(application, "http://www.fabrikam.com/path", It.IsAny())) .ReturnsAsync(true); @@ -4667,6 +4816,9 @@ public abstract partial class OpenIddictServerIntegrationTests mock.Setup(manager => manager.FindByClientIdAsync("Fabrikam", It.IsAny())) .ReturnsAsync(application); + mock.Setup(manager => manager.HasClientTypeAsync(application, ClientTypes.Public, It.IsAny())) + .ReturnsAsync(true); + mock.Setup(manager => manager.ValidateRedirectUriAsync(application, "http://www.fabrikam.com/path", It.IsAny())) .ReturnsAsync(false); }); @@ -4706,6 +4858,9 @@ public abstract partial class OpenIddictServerIntegrationTests mock.Setup(manager => manager.FindByClientIdAsync("Fabrikam", It.IsAny())) .ReturnsAsync(application); + mock.Setup(manager => manager.HasClientTypeAsync(application, ClientTypes.Public, It.IsAny())) + .ReturnsAsync(true); + mock.Setup(manager => manager.ValidateRedirectUriAsync(application, "http://www.fabrikam.com/path", It.IsAny())) .ReturnsAsync(true); @@ -4762,6 +4917,9 @@ public abstract partial class OpenIddictServerIntegrationTests mock.Setup(manager => manager.FindByClientIdAsync("Fabrikam", It.IsAny())) .ReturnsAsync(application); + mock.Setup(manager => manager.HasClientTypeAsync(application, ClientTypes.Public, It.IsAny())) + .ReturnsAsync(true); + mock.Setup(manager => manager.ValidateRedirectUriAsync(application, "http://www.fabrikam.com/path", It.IsAny())) .ReturnsAsync(true); @@ -4814,6 +4972,9 @@ public abstract partial class OpenIddictServerIntegrationTests mock.Setup(manager => manager.FindByClientIdAsync("Fabrikam", It.IsAny())) .ReturnsAsync(application); + mock.Setup(manager => manager.HasClientTypeAsync(application, ClientTypes.Public, It.IsAny())) + .ReturnsAsync(true); + mock.Setup(manager => manager.ValidateRedirectUriAsync(application, "http://www.fabrikam.com/path", It.IsAny())) .ReturnsAsync(true); @@ -4860,6 +5021,9 @@ public abstract partial class OpenIddictServerIntegrationTests mock.Setup(manager => manager.FindByClientIdAsync("Fabrikam", It.IsAny())) .ReturnsAsync(application); + mock.Setup(manager => manager.HasClientTypeAsync(application, ClientTypes.Public, It.IsAny())) + .ReturnsAsync(true); + mock.Setup(manager => manager.ValidateRedirectUriAsync(application, "http://www.fabrikam.com/path", It.IsAny())) .ReturnsAsync(true); @@ -4914,6 +5078,9 @@ public abstract partial class OpenIddictServerIntegrationTests mock.Setup(manager => manager.FindByClientIdAsync("Fabrikam", It.IsAny())) .ReturnsAsync(application); + mock.Setup(manager => manager.HasClientTypeAsync(application, ClientTypes.Public, It.IsAny())) + .ReturnsAsync(true); + mock.Setup(manager => manager.ValidateRedirectUriAsync(application, "http://www.fabrikam.com/path", It.IsAny())) .ReturnsAsync(true); @@ -4968,6 +5135,9 @@ public abstract partial class OpenIddictServerIntegrationTests mock.Setup(manager => manager.FindByClientIdAsync("Fabrikam", It.IsAny())) .ReturnsAsync(application); + mock.Setup(manager => manager.HasClientTypeAsync(application, ClientTypes.Public, It.IsAny())) + .ReturnsAsync(true); + mock.Setup(manager => manager.ValidateRedirectUriAsync(application, "http://www.fabrikam.com/path", It.IsAny())) .ReturnsAsync(true); @@ -5137,6 +5307,9 @@ public abstract partial class OpenIddictServerIntegrationTests mock.Setup(manager => manager.FindByClientIdAsync("Fabrikam", It.IsAny())) .ReturnsAsync(application); + mock.Setup(manager => manager.HasClientTypeAsync(application, ClientTypes.Public, It.IsAny())) + .ReturnsAsync(true); + mock.Setup(manager => manager.ValidateRedirectUriAsync(application, "http://www.fabrikam.com/path", It.IsAny())) .ReturnsAsync(true);