diff --git a/samples/Mvc.Server/Controllers/AuthorizationController.cs b/samples/Mvc.Server/Controllers/AuthorizationController.cs index c28d8730..88176d36 100644 --- a/samples/Mvc.Server/Controllers/AuthorizationController.cs +++ b/samples/Mvc.Server/Controllers/AuthorizationController.cs @@ -249,17 +249,16 @@ namespace Mvc.Server // Note: the same check is already made in the other action but is repeated // here to ensure a malicious user can't abuse this POST-only endpoint and // force it to return a valid response without the external authorization. - switch (await _applicationManager.GetConsentTypeAsync(application)) + if (!authorizations.Any() && await _applicationManager.HasConsentTypeAsync(application, ConsentTypes.External)) { - case ConsentTypes.External when !authorizations.Any(): - return Forbid( - authenticationSchemes: OpenIddictServerAspNetCoreDefaults.AuthenticationScheme, - properties: new AuthenticationProperties(new Dictionary - { - [OpenIddictServerAspNetCoreConstants.Properties.Error] = Errors.ConsentRequired, - [OpenIddictServerAspNetCoreConstants.Properties.ErrorDescription] = - "The logged in user is not allowed to access this client application." - })); + return Forbid( + authenticationSchemes: OpenIddictServerAspNetCoreDefaults.AuthenticationScheme, + properties: new AuthenticationProperties(new Dictionary + { + [OpenIddictServerAspNetCoreConstants.Properties.Error] = Errors.ConsentRequired, + [OpenIddictServerAspNetCoreConstants.Properties.ErrorDescription] = + "The logged in user is not allowed to access this client application." + })); } var principal = await _signInManager.CreateUserPrincipalAsync(user); diff --git a/src/OpenIddict.Abstractions/Managers/IOpenIddictApplicationManager.cs b/src/OpenIddict.Abstractions/Managers/IOpenIddictApplicationManager.cs index 74f3bb21..7e3b99e6 100644 --- a/src/OpenIddict.Abstractions/Managers/IOpenIddictApplicationManager.cs +++ b/src/OpenIddict.Abstractions/Managers/IOpenIddictApplicationManager.cs @@ -254,46 +254,40 @@ namespace OpenIddict.Abstractions ValueTask> GetRequirementsAsync([NotNull] object application, CancellationToken cancellationToken = default); /// - /// Determines whether the specified permission has been granted to the application. + /// Determines whether a given application has the specified client type. /// /// The application. - /// The permission. + /// The expected client type. /// The that can be used to abort the operation. - /// true if the application has been granted the specified permission, false otherwise. - ValueTask HasPermissionAsync([NotNull] object application, [NotNull] string permission, CancellationToken cancellationToken = default); + /// true if the application has the specified client type, false otherwise. + ValueTask HasClientTypeAsync([NotNull] object application, [NotNull] string type, CancellationToken cancellationToken = default); /// - /// Determines whether the specified requirement has been enforced for the specified application. + /// Determines whether a given application has the specified consent type. /// /// The application. - /// The requirement. + /// The expected consent type. /// The that can be used to abort the operation. - /// true if the requirement has been enforced for the specified application, false otherwise. - ValueTask HasRequirementAsync([NotNull] object application, [NotNull] string requirement, CancellationToken cancellationToken = default); + /// true if the application has the specified consent type, false otherwise. + ValueTask HasConsentTypeAsync([NotNull] object application, [NotNull] string type, CancellationToken cancellationToken = default); /// - /// Determines whether an application is a confidential client. - /// - /// The application. - /// The that can be used to abort the operation. - /// true if the application is a confidential client, false otherwise. - ValueTask IsConfidentialAsync([NotNull] object application, CancellationToken cancellationToken = default); - - /// - /// Determines whether an application is a hybrid client. + /// Determines whether the specified permission has been granted to the application. /// /// The application. + /// The permission. /// The that can be used to abort the operation. - /// true if the application is a hybrid client, false otherwise. - ValueTask IsHybridAsync([NotNull] object application, CancellationToken cancellationToken = default); + /// true if the application has been granted the specified permission, false otherwise. + ValueTask HasPermissionAsync([NotNull] object application, [NotNull] string permission, CancellationToken cancellationToken = default); /// - /// Determines whether an application is a public client. + /// Determines whether the specified requirement has been enforced for the specified application. /// /// The application. + /// The requirement. /// The that can be used to abort the operation. - /// true if the application is a public client, false otherwise. - ValueTask IsPublicAsync([NotNull] object application, CancellationToken cancellationToken = default); + /// true if the requirement has been enforced for the specified application, false otherwise. + ValueTask HasRequirementAsync([NotNull] object application, [NotNull] string requirement, CancellationToken cancellationToken = default); /// /// Executes the specified query and returns all the corresponding elements. diff --git a/src/OpenIddict.Abstractions/Managers/IOpenIddictAuthorizationManager.cs b/src/OpenIddict.Abstractions/Managers/IOpenIddictAuthorizationManager.cs index 9f33d09a..3d62c5ef 100644 --- a/src/OpenIddict.Abstractions/Managers/IOpenIddictAuthorizationManager.cs +++ b/src/OpenIddict.Abstractions/Managers/IOpenIddictAuthorizationManager.cs @@ -278,6 +278,15 @@ namespace OpenIddict.Abstractions /// true if the authorization has the specified status, false otherwise. ValueTask HasStatusAsync([NotNull] object authorization, [NotNull] string status, CancellationToken cancellationToken = default); + /// + /// Determines whether a given authorization has the specified type. + /// + /// The authorization. + /// The expected type. + /// The that can be used to abort the operation. + /// true if the authorization has the specified type, false otherwise. + ValueTask HasTypeAsync([NotNull] object authorization, [NotNull] string type, CancellationToken cancellationToken = default); + /// /// Executes the specified query and returns all the corresponding elements. /// diff --git a/src/OpenIddict.Core/Managers/OpenIddictApplicationManager.cs b/src/OpenIddict.Core/Managers/OpenIddictApplicationManager.cs index eeda48f1..b9df145e 100644 --- a/src/OpenIddict.Core/Managers/OpenIddictApplicationManager.cs +++ b/src/OpenIddict.Core/Managers/OpenIddictApplicationManager.cs @@ -605,116 +605,95 @@ namespace OpenIddict.Core } /// - /// Determines whether the specified permission has been granted to the application. + /// Determines whether a given application has the specified client type. /// /// The application. - /// The permission. + /// The expected client type. /// The that can be used to abort the operation. - /// true if the application has been granted the specified permission, false otherwise. - public virtual async ValueTask HasPermissionAsync( - [NotNull] TApplication application, [NotNull] string permission, CancellationToken cancellationToken = default) + /// true if the application has the specified client type, false otherwise. + public virtual async ValueTask HasClientTypeAsync( + [NotNull] TApplication application, [NotNull] string type, CancellationToken cancellationToken = default) { if (application == null) { throw new ArgumentNullException(nameof(application)); } - if (string.IsNullOrEmpty(permission)) - { - throw new ArgumentException("The permission name cannot be null or empty.", nameof(permission)); - } - - return (await GetPermissionsAsync(application, cancellationToken)).Contains(permission, StringComparer.Ordinal); - } - - /// - /// Determines whether the specified requirement has been enforced for the specified application. - /// - /// The application. - /// The requirement. - /// The that can be used to abort the operation. - /// true if the requirement has been enforced for the specified application, false otherwise. - public virtual async ValueTask HasRequirementAsync( - [NotNull] TApplication application, [NotNull] string requirement, CancellationToken cancellationToken = default) - { - if (application == null) - { - throw new ArgumentNullException(nameof(application)); - } - - if (string.IsNullOrEmpty(requirement)) + if (string.IsNullOrEmpty(type)) { - throw new ArgumentException("The requirement name cannot be null or empty.", nameof(requirement)); + throw new ArgumentException("The client type cannot be null or empty.", nameof(type)); } - return (await GetRequirementsAsync(application, cancellationToken)).Contains(requirement, StringComparer.Ordinal); + return string.Equals(await GetClientTypeAsync(application, cancellationToken), type, StringComparison.OrdinalIgnoreCase); } /// - /// Determines whether an application is a confidential client. + /// Determines whether a given application has the specified consent type. /// /// The application. + /// The expected consent type. /// The that can be used to abort the operation. - /// true if the application is a confidential client, false otherwise. - public async ValueTask IsConfidentialAsync([NotNull] TApplication application, CancellationToken cancellationToken = default) + /// true if the application has the specified consent type, false otherwise. + public virtual async ValueTask HasConsentTypeAsync( + [NotNull] TApplication application, [NotNull] string type, CancellationToken cancellationToken = default) { if (application == null) { throw new ArgumentNullException(nameof(application)); } - var type = await GetClientTypeAsync(application, cancellationToken); if (string.IsNullOrEmpty(type)) { - return false; + throw new ArgumentException("The consent type cannot be null or empty.", nameof(type)); } - return string.Equals(type, ClientTypes.Confidential, StringComparison.OrdinalIgnoreCase); + return string.Equals(await GetConsentTypeAsync(application, cancellationToken), type, StringComparison.OrdinalIgnoreCase); } /// - /// Determines whether an application is a hybrid client. + /// Determines whether the specified permission has been granted to the application. /// /// The application. + /// The permission. /// The that can be used to abort the operation. - /// true if the application is a hybrid client, false otherwise. - public async ValueTask IsHybridAsync([NotNull] TApplication application, CancellationToken cancellationToken = default) + /// true if the application has been granted the specified permission, false otherwise. + public virtual async ValueTask HasPermissionAsync( + [NotNull] TApplication application, [NotNull] string permission, CancellationToken cancellationToken = default) { if (application == null) { throw new ArgumentNullException(nameof(application)); } - var type = await GetClientTypeAsync(application, cancellationToken); - if (string.IsNullOrEmpty(type)) + if (string.IsNullOrEmpty(permission)) { - return false; + throw new ArgumentException("The permission name cannot be null or empty.", nameof(permission)); } - return string.Equals(type, ClientTypes.Hybrid, StringComparison.OrdinalIgnoreCase); + return (await GetPermissionsAsync(application, cancellationToken)).Contains(permission, StringComparer.Ordinal); } /// - /// Determines whether an application is a public client. + /// Determines whether the specified requirement has been enforced for the specified application. /// /// The application. + /// The requirement. /// The that can be used to abort the operation. - /// true if the application is a public client, false otherwise. - public async ValueTask IsPublicAsync([NotNull] TApplication application, CancellationToken cancellationToken = default) + /// true if the requirement has been enforced for the specified application, false otherwise. + public virtual async ValueTask HasRequirementAsync( + [NotNull] TApplication application, [NotNull] string requirement, CancellationToken cancellationToken = default) { if (application == null) { throw new ArgumentNullException(nameof(application)); } - // Assume client applications are public if their type is not explicitly set. - var type = await GetClientTypeAsync(application, cancellationToken); - if (string.IsNullOrEmpty(type)) + if (string.IsNullOrEmpty(requirement)) { - return true; + throw new ArgumentException("The requirement name cannot be null or empty.", nameof(requirement)); } - return string.Equals(type, ClientTypes.Public, StringComparison.OrdinalIgnoreCase); + return (await GetRequirementsAsync(application, cancellationToken)).Contains(requirement, StringComparer.Ordinal); } /// @@ -1104,7 +1083,7 @@ namespace OpenIddict.Core throw new ArgumentException("The secret cannot be null or empty.", nameof(secret)); } - if (await IsPublicAsync(application, cancellationToken)) + if (await HasClientTypeAsync(application, ClientTypes.Public, cancellationToken)) { Logger.LogWarning("Client authentication cannot be enforced for public applications."); @@ -1463,21 +1442,18 @@ namespace OpenIddict.Core ValueTask> IOpenIddictApplicationManager.GetRequirementsAsync(object application, CancellationToken cancellationToken) => GetRequirementsAsync((TApplication) application, cancellationToken); + ValueTask IOpenIddictApplicationManager.HasClientTypeAsync(object application, string type, CancellationToken cancellationToken) + => HasClientTypeAsync((TApplication) application, type, cancellationToken); + + ValueTask IOpenIddictApplicationManager.HasConsentTypeAsync(object application, string type, CancellationToken cancellationToken) + => HasConsentTypeAsync((TApplication) application, type, cancellationToken); + ValueTask IOpenIddictApplicationManager.HasPermissionAsync(object application, string permission, CancellationToken cancellationToken) => HasPermissionAsync((TApplication) application, permission, cancellationToken); ValueTask IOpenIddictApplicationManager.HasRequirementAsync(object application, string requirement, CancellationToken cancellationToken) => HasRequirementAsync((TApplication) application, requirement, cancellationToken); - ValueTask IOpenIddictApplicationManager.IsConfidentialAsync(object application, CancellationToken cancellationToken) - => IsConfidentialAsync((TApplication) application, cancellationToken); - - ValueTask IOpenIddictApplicationManager.IsHybridAsync(object application, CancellationToken cancellationToken) - => IsHybridAsync((TApplication) application, cancellationToken); - - ValueTask IOpenIddictApplicationManager.IsPublicAsync(object application, CancellationToken cancellationToken) - => IsPublicAsync((TApplication) application, cancellationToken); - IAsyncEnumerable IOpenIddictApplicationManager.ListAsync(int? count, int? offset, CancellationToken cancellationToken) => ListAsync(count, offset, cancellationToken).OfType(); diff --git a/src/OpenIddict.Core/Managers/OpenIddictAuthorizationManager.cs b/src/OpenIddict.Core/Managers/OpenIddictAuthorizationManager.cs index f0e84bed..c6dc1b42 100644 --- a/src/OpenIddict.Core/Managers/OpenIddictAuthorizationManager.cs +++ b/src/OpenIddict.Core/Managers/OpenIddictAuthorizationManager.cs @@ -724,6 +724,29 @@ namespace OpenIddict.Core return string.Equals(await Store.GetStatusAsync(authorization, cancellationToken), status, StringComparison.OrdinalIgnoreCase); } + /// + /// Determines whether a given authorization has the specified type. + /// + /// The authorization. + /// The expected type. + /// The that can be used to abort the operation. + /// true if the authorization has the specified type, false otherwise. + public virtual async ValueTask HasTypeAsync( + [NotNull] TAuthorization authorization, [NotNull] string type, CancellationToken cancellationToken = default) + { + if (authorization == null) + { + throw new ArgumentNullException(nameof(authorization)); + } + + if (string.IsNullOrEmpty(type)) + { + throw new ArgumentException("The type cannot be null or empty.", nameof(type)); + } + + return string.Equals(await Store.GetTypeAsync(authorization, cancellationToken), type, StringComparison.OrdinalIgnoreCase); + } + /// /// Executes the specified query and returns all the corresponding elements. /// @@ -1097,6 +1120,9 @@ namespace OpenIddict.Core ValueTask IOpenIddictAuthorizationManager.HasStatusAsync(object authorization, string status, CancellationToken cancellationToken) => HasStatusAsync((TAuthorization) authorization, status, cancellationToken); + ValueTask IOpenIddictAuthorizationManager.HasTypeAsync(object authorization, string type, CancellationToken cancellationToken) + => HasTypeAsync((TAuthorization) authorization, type, cancellationToken); + IAsyncEnumerable IOpenIddictAuthorizationManager.ListAsync(int? count, int? offset, CancellationToken cancellationToken) => ListAsync(count, offset, cancellationToken).OfType(); diff --git a/src/OpenIddict.Server/OpenIddictServerHandlers.Authentication.cs b/src/OpenIddict.Server/OpenIddictServerHandlers.Authentication.cs index b646f391..ffab6279 100644 --- a/src/OpenIddict.Server/OpenIddictServerHandlers.Authentication.cs +++ b/src/OpenIddict.Server/OpenIddictServerHandlers.Authentication.cs @@ -1122,7 +1122,8 @@ namespace OpenIddict.Server // from the authorization endpoint are rejected if the client_id corresponds to a confidential application. // Note: when using the authorization code grant, the ValidateClientSecret handler is responsible of rejecting // the token request if the client_id corresponds to an unauthenticated confidential client. - if (context.Request.HasResponseType(ResponseTypes.Token) && await _applicationManager.IsConfidentialAsync(application)) + if (context.Request.HasResponseType(ResponseTypes.Token) && + await _applicationManager.HasClientTypeAsync(application, ClientTypes.Confidential)) { context.Logger.LogError("The authorization request was rejected because the confidential application '{ClientId}' " + "was not allowed to retrieve an access token from the authorization endpoint.", context.ClientId); diff --git a/src/OpenIddict.Server/OpenIddictServerHandlers.Device.cs b/src/OpenIddict.Server/OpenIddictServerHandlers.Device.cs index cedb2b7b..72f41558 100644 --- a/src/OpenIddict.Server/OpenIddictServerHandlers.Device.cs +++ b/src/OpenIddict.Server/OpenIddictServerHandlers.Device.cs @@ -601,7 +601,7 @@ namespace OpenIddict.Server throw new InvalidOperationException("The client application details cannot be found in the database."); } - if (await _applicationManager.IsPublicAsync(application)) + if (await _applicationManager.HasClientTypeAsync(application, ClientTypes.Public)) { // Reject device requests containing a client_secret when the client is a public application. if (!string.IsNullOrEmpty(context.ClientSecret)) @@ -685,7 +685,7 @@ namespace OpenIddict.Server } // If the application is not a public client, validate the client secret. - if (!await _applicationManager.IsPublicAsync(application) && + if (!await _applicationManager.HasClientTypeAsync(application, ClientTypes.Public) && !await _applicationManager.ValidateClientSecretAsync(application, context.ClientSecret)) { context.Logger.LogError("The device request was rejected because the confidential or hybrid application " + diff --git a/src/OpenIddict.Server/OpenIddictServerHandlers.Exchange.cs b/src/OpenIddict.Server/OpenIddictServerHandlers.Exchange.cs index 5d1a69c3..1a08649b 100644 --- a/src/OpenIddict.Server/OpenIddictServerHandlers.Exchange.cs +++ b/src/OpenIddict.Server/OpenIddictServerHandlers.Exchange.cs @@ -909,7 +909,7 @@ namespace OpenIddict.Server throw new InvalidOperationException("The client application details cannot be found in the database."); } - if (await _applicationManager.IsPublicAsync(application)) + if (await _applicationManager.HasClientTypeAsync(application, ClientTypes.Public)) { // Public applications are not allowed to use the client credentials grant. if (context.Request.IsClientCredentialsGrantType()) @@ -1006,7 +1006,7 @@ namespace OpenIddict.Server } // If the application is not a public client, validate the client secret. - if (!await _applicationManager.IsPublicAsync(application) && + if (!await _applicationManager.HasClientTypeAsync(application, ClientTypes.Public) && !await _applicationManager.ValidateClientSecretAsync(application, context.ClientSecret)) { context.Logger.LogError("The token request was rejected because the confidential or hybrid application " + diff --git a/src/OpenIddict.Server/OpenIddictServerHandlers.Introspection.cs b/src/OpenIddict.Server/OpenIddictServerHandlers.Introspection.cs index cc696796..a9abca64 100644 --- a/src/OpenIddict.Server/OpenIddictServerHandlers.Introspection.cs +++ b/src/OpenIddict.Server/OpenIddictServerHandlers.Introspection.cs @@ -581,7 +581,7 @@ namespace OpenIddict.Server throw new InvalidOperationException("The client application details cannot be found in the database."); } - if (await _applicationManager.IsPublicAsync(application)) + if (await _applicationManager.HasClientTypeAsync(application, ClientTypes.Public)) { // Reject introspection requests containing a client_secret when the client is a public application. if (!string.IsNullOrEmpty(context.ClientSecret)) @@ -665,7 +665,7 @@ namespace OpenIddict.Server } // If the application is not a public client, validate the client secret. - if (!await _applicationManager.IsPublicAsync(application) && + if (!await _applicationManager.HasClientTypeAsync(application, ClientTypes.Public) && !await _applicationManager.ValidateClientSecretAsync(application, context.ClientSecret)) { context.Logger.LogError("The introspection request was rejected because the confidential or hybrid application " + @@ -1126,7 +1126,7 @@ namespace OpenIddict.Server } // Public clients are not allowed to access sensitive claims as authentication cannot be enforced. - if (await _applicationManager.IsPublicAsync(application)) + if (await _applicationManager.HasClientTypeAsync(application, ClientTypes.Public)) { return; } diff --git a/src/OpenIddict.Server/OpenIddictServerHandlers.Revocation.cs b/src/OpenIddict.Server/OpenIddictServerHandlers.Revocation.cs index a4cc9067..9814ecbd 100644 --- a/src/OpenIddict.Server/OpenIddictServerHandlers.Revocation.cs +++ b/src/OpenIddict.Server/OpenIddictServerHandlers.Revocation.cs @@ -527,7 +527,7 @@ namespace OpenIddict.Server throw new InvalidOperationException("The client application details cannot be found in the database."); } - if (await _applicationManager.IsPublicAsync(application)) + if (await _applicationManager.HasClientTypeAsync(application, ClientTypes.Public)) { // Reject revocation requests containing a client_secret when the client is a public application. if (!string.IsNullOrEmpty(context.ClientSecret)) @@ -611,7 +611,7 @@ namespace OpenIddict.Server } // If the application is not a public client, validate the client secret. - if (!await _applicationManager.IsPublicAsync(application) && + if (!await _applicationManager.HasClientTypeAsync(application, ClientTypes.Public) && !await _applicationManager.ValidateClientSecretAsync(application, context.ClientSecret)) { context.Logger.LogError("The revocation request was rejected because the confidential or hybrid application " + diff --git a/test/OpenIddict.Server.IntegrationTests/OpenIddictServerIntegrationTests.Authentication.cs b/test/OpenIddict.Server.IntegrationTests/OpenIddictServerIntegrationTests.Authentication.cs index a4b883f9..a273e878 100644 --- a/test/OpenIddict.Server.IntegrationTests/OpenIddictServerIntegrationTests.Authentication.cs +++ b/test/OpenIddict.Server.IntegrationTests/OpenIddictServerIntegrationTests.Authentication.cs @@ -555,8 +555,8 @@ namespace OpenIddict.Server.FunctionalTests mock.Setup(manager => manager.ValidateRedirectUriAsync(application, "http://www.fabrikam.com/path", It.IsAny())) .ReturnsAsync(true); - mock.Setup(manager => manager.GetClientTypeAsync(application, It.IsAny())) - .ReturnsAsync(ClientTypes.Public); + mock.Setup(manager => manager.HasClientTypeAsync(application, ClientTypes.Public, It.IsAny())) + .ReturnsAsync(true); })); options.Services.AddSingleton(CreateScopeManager(mock => @@ -600,8 +600,8 @@ namespace OpenIddict.Server.FunctionalTests mock.Setup(manager => manager.ValidateRedirectUriAsync(application, "http://www.fabrikam.com/path", It.IsAny())) .ReturnsAsync(true); - mock.Setup(manager => manager.GetClientTypeAsync(application, It.IsAny())) - .ReturnsAsync(ClientTypes.Public); + mock.Setup(manager => manager.HasClientTypeAsync(application, ClientTypes.Public, It.IsAny())) + .ReturnsAsync(true); })); options.Services.AddSingleton(CreateApplicationManager(mock => @@ -614,8 +614,8 @@ namespace OpenIddict.Server.FunctionalTests mock.Setup(manager => manager.ValidateRedirectUriAsync(application, "http://www.fabrikam.com/path", It.IsAny())) .ReturnsAsync(true); - mock.Setup(manager => manager.GetClientTypeAsync(application, It.IsAny())) - .ReturnsAsync(ClientTypes.Public); + mock.Setup(manager => manager.HasClientTypeAsync(application, ClientTypes.Public, It.IsAny())) + .ReturnsAsync(true); })); options.AddEventHandler(builder => @@ -665,8 +665,8 @@ namespace OpenIddict.Server.FunctionalTests mock.Setup(manager => manager.ValidateRedirectUriAsync(application, "http://www.fabrikam.com/path", It.IsAny())) .ReturnsAsync(true); - mock.Setup(manager => manager.GetClientTypeAsync(application, It.IsAny())) - .ReturnsAsync(ClientTypes.Public); + mock.Setup(manager => manager.HasClientTypeAsync(application, ClientTypes.Public, It.IsAny())) + .ReturnsAsync(true); })); options.Services.AddSingleton(CreateScopeManager(mock => @@ -1002,8 +1002,8 @@ namespace OpenIddict.Server.FunctionalTests mock.Setup(manager => manager.FindByClientIdAsync("Fabrikam", It.IsAny())) .ReturnsAsync(application); - mock.Setup(manager => manager.GetClientTypeAsync(application, It.IsAny())) - .ReturnsAsync(ClientTypes.Confidential); + mock.Setup(manager => manager.HasClientTypeAsync(application, ClientTypes.Confidential, It.IsAny())) + .ReturnsAsync(true); }); var client = CreateClient(options => @@ -1026,7 +1026,7 @@ namespace OpenIddict.Server.FunctionalTests Assert.Equal("The specified 'response_type' parameter is not valid for this client application.", response.ErrorDescription); Mock.Get(manager).Verify(manager => manager.FindByClientIdAsync("Fabrikam", It.IsAny()), Times.AtLeastOnce()); - Mock.Get(manager).Verify(manager => manager.GetClientTypeAsync(application, It.IsAny()), Times.Once()); + Mock.Get(manager).Verify(manager => manager.HasClientTypeAsync(application, ClientTypes.Confidential, It.IsAny()), Times.Once()); } [Fact] diff --git a/test/OpenIddict.Server.IntegrationTests/OpenIddictServerIntegrationTests.Exchange.cs b/test/OpenIddict.Server.IntegrationTests/OpenIddictServerIntegrationTests.Exchange.cs index e2e36286..c7b36bfa 100644 --- a/test/OpenIddict.Server.IntegrationTests/OpenIddictServerIntegrationTests.Exchange.cs +++ b/test/OpenIddict.Server.IntegrationTests/OpenIddictServerIntegrationTests.Exchange.cs @@ -1259,8 +1259,8 @@ namespace OpenIddict.Server.FunctionalTests mock.Setup(manager => manager.FindByClientIdAsync("Fabrikam", It.IsAny())) .ReturnsAsync(application); - mock.Setup(manager => manager.GetClientTypeAsync(application, It.IsAny())) - .ReturnsAsync(ClientTypes.Public); + mock.Setup(manager => manager.HasClientTypeAsync(application, ClientTypes.Public, It.IsAny())) + .ReturnsAsync(true); }); var client = CreateClient(options => @@ -1281,7 +1281,7 @@ namespace OpenIddict.Server.FunctionalTests Assert.Equal("The specified 'grant_type' parameter is not valid for this client application.", response.ErrorDescription); Mock.Get(manager).Verify(manager => manager.FindByClientIdAsync("Fabrikam", It.IsAny()), Times.AtLeastOnce()); - Mock.Get(manager).Verify(manager => manager.GetClientTypeAsync(application, It.IsAny()), Times.Once()); + Mock.Get(manager).Verify(manager => manager.HasClientTypeAsync(application, ClientTypes.Public, It.IsAny()), Times.Once()); } [Fact] @@ -1295,8 +1295,8 @@ namespace OpenIddict.Server.FunctionalTests mock.Setup(manager => manager.FindByClientIdAsync("Fabrikam", It.IsAny())) .ReturnsAsync(application); - mock.Setup(manager => manager.GetClientTypeAsync(application, It.IsAny())) - .ReturnsAsync(ClientTypes.Public); + mock.Setup(manager => manager.HasClientTypeAsync(application, ClientTypes.Public, It.IsAny())) + .ReturnsAsync(true); }); var client = CreateClient(options => @@ -1319,49 +1319,11 @@ namespace OpenIddict.Server.FunctionalTests Assert.Equal("The 'client_secret' parameter is not valid for this client application.", response.ErrorDescription); Mock.Get(manager).Verify(manager => manager.FindByClientIdAsync("Fabrikam", It.IsAny()), Times.AtLeastOnce()); - Mock.Get(manager).Verify(manager => manager.GetClientTypeAsync(application, It.IsAny()), Times.Once()); - } - - [Fact] - public async Task ValidateTokenRequest_ClientSecretIsRequiredForConfidentialClients() - { - // Arrange - var application = new OpenIddictApplication(); - - var manager = CreateApplicationManager(mock => - { - mock.Setup(manager => manager.FindByClientIdAsync("Fabrikam", It.IsAny())) - .ReturnsAsync(application); - - mock.Setup(manager => manager.GetClientTypeAsync(application, It.IsAny())) - .ReturnsAsync(ClientTypes.Confidential); - }); - - var client = CreateClient(options => - { - options.Services.AddSingleton(manager); - }); - - // Act - var response = await client.PostAsync("/connect/token", new OpenIddictRequest - { - ClientId = "Fabrikam", - ClientSecret = null, - GrantType = GrantTypes.Password, - Username = "johndoe", - Password = "A3ddj3w" - }); - - // Assert - Assert.Equal(Errors.InvalidClient, response.Error); - Assert.Equal("The 'client_secret' parameter required for this client application is missing.", response.ErrorDescription); - - Mock.Get(manager).Verify(manager => manager.FindByClientIdAsync("Fabrikam", It.IsAny()), Times.AtLeastOnce()); - Mock.Get(manager).Verify(manager => manager.GetClientTypeAsync(application, It.IsAny()), Times.Once()); + Mock.Get(manager).Verify(manager => manager.HasClientTypeAsync(application, ClientTypes.Public, It.IsAny()), Times.Once()); } [Fact] - public async Task ValidateTokenRequest_ClientSecretIsRequiredForHybridClients() + public async Task ValidateTokenRequest_ClientSecretIsRequiredForNonPublicClients() { // Arrange var application = new OpenIddictApplication(); @@ -1371,8 +1333,8 @@ namespace OpenIddict.Server.FunctionalTests mock.Setup(manager => manager.FindByClientIdAsync("Fabrikam", It.IsAny())) .ReturnsAsync(application); - mock.Setup(manager => manager.GetClientTypeAsync(application, It.IsAny())) - .ReturnsAsync(ClientTypes.Hybrid); + mock.Setup(manager => manager.HasClientTypeAsync(application, ClientTypes.Public, It.IsAny())) + .ReturnsAsync(false); }); var client = CreateClient(options => @@ -1395,7 +1357,7 @@ namespace OpenIddict.Server.FunctionalTests Assert.Equal("The 'client_secret' parameter required for this client application is missing.", response.ErrorDescription); Mock.Get(manager).Verify(manager => manager.FindByClientIdAsync("Fabrikam", It.IsAny()), Times.AtLeastOnce()); - Mock.Get(manager).Verify(manager => manager.GetClientTypeAsync(application, It.IsAny()), Times.Once()); + Mock.Get(manager).Verify(manager => manager.HasClientTypeAsync(application, ClientTypes.Public, It.IsAny()), Times.Once()); } [Fact] @@ -1409,8 +1371,8 @@ namespace OpenIddict.Server.FunctionalTests mock.Setup(manager => manager.FindByClientIdAsync("Fabrikam", It.IsAny())) .ReturnsAsync(application); - mock.Setup(manager => manager.GetClientTypeAsync(application, It.IsAny())) - .ReturnsAsync(ClientTypes.Confidential); + mock.Setup(manager => manager.HasClientTypeAsync(application, ClientTypes.Public, It.IsAny())) + .ReturnsAsync(false); mock.Setup(manager => manager.ValidateClientSecretAsync(application, "7Fjfp0ZBr1KtDRbnfVdmIw", It.IsAny())) .ReturnsAsync(false); @@ -1436,7 +1398,7 @@ namespace OpenIddict.Server.FunctionalTests Assert.Equal("The specified client credentials are invalid.", response.ErrorDescription); Mock.Get(manager).Verify(manager => manager.FindByClientIdAsync("Fabrikam", It.IsAny()), Times.AtLeastOnce()); - Mock.Get(manager).Verify(manager => manager.GetClientTypeAsync(application, 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()); } @@ -1451,6 +1413,9 @@ namespace OpenIddict.Server.FunctionalTests 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.HasPermissionAsync(application, Permissions.Endpoints.Token, It.IsAny())) .ReturnsAsync(false); @@ -1492,6 +1457,9 @@ namespace OpenIddict.Server.FunctionalTests 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.HasPermissionAsync(application, Permissions.GrantTypes.Password, It.IsAny())) .ReturnsAsync(false); @@ -1533,6 +1501,9 @@ namespace OpenIddict.Server.FunctionalTests 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.HasPermissionAsync(application, Permissions.GrantTypes.Password, It.IsAny())) .ReturnsAsync(true); @@ -1578,8 +1549,8 @@ namespace OpenIddict.Server.FunctionalTests mock.Setup(manager => manager.FindByClientIdAsync("Fabrikam", It.IsAny())) .ReturnsAsync(application); - mock.Setup(manager => manager.GetClientTypeAsync(application, It.IsAny())) - .ReturnsAsync(ClientTypes.Public); + mock.Setup(manager => manager.HasClientTypeAsync(application, ClientTypes.Public, It.IsAny())) + .ReturnsAsync(true); mock.Setup(manager => manager.HasPermissionAsync(application, Permissions.Prefixes.Scope + Scopes.Profile, It.IsAny())) @@ -1633,8 +1604,8 @@ namespace OpenIddict.Server.FunctionalTests mock.Setup(manager => manager.FindByClientIdAsync("Fabrikam", It.IsAny())) .ReturnsAsync(application); - mock.Setup(manager => manager.GetClientTypeAsync(application, It.IsAny())) - .ReturnsAsync(ClientTypes.Public); + mock.Setup(manager => manager.HasClientTypeAsync(application, ClientTypes.Public, It.IsAny())) + .ReturnsAsync(true); mock.Setup(manager => manager.HasRequirementAsync(application, Requirements.Features.ProofKeyForCodeExchange, It.IsAny())) @@ -1675,8 +1646,8 @@ namespace OpenIddict.Server.FunctionalTests mock.Setup(manager => manager.FindByClientIdAsync("Fabrikam", It.IsAny())) .ReturnsAsync(application); - mock.Setup(manager => manager.GetClientTypeAsync(application, It.IsAny())) - .ReturnsAsync(ClientTypes.Public); + mock.Setup(manager => manager.HasClientTypeAsync(application, ClientTypes.Public, It.IsAny())) + .ReturnsAsync(true); mock.Setup(manager => manager.HasRequirementAsync(application, Requirements.Features.ProofKeyForCodeExchange, It.IsAny())) @@ -1739,8 +1710,8 @@ namespace OpenIddict.Server.FunctionalTests mock.Setup(manager => manager.FindByClientIdAsync("Fabrikam", It.IsAny())) .ReturnsAsync(application); - mock.Setup(manager => manager.GetClientTypeAsync(application, It.IsAny())) - .ReturnsAsync(ClientTypes.Public); + mock.Setup(manager => manager.HasClientTypeAsync(application, ClientTypes.Public, It.IsAny())) + .ReturnsAsync(true); mock.Setup(manager => manager.HasRequirementAsync(application, Requirements.Features.ProofKeyForCodeExchange, It.IsAny())) @@ -1936,8 +1907,8 @@ namespace OpenIddict.Server.FunctionalTests mock.Setup(manager => manager.FindByClientIdAsync("Fabrikam", It.IsAny())) .ReturnsAsync(application); - mock.Setup(manager => manager.GetClientTypeAsync(application, It.IsAny())) - .ReturnsAsync(ClientTypes.Public); + mock.Setup(manager => manager.HasClientTypeAsync(application, ClientTypes.Public, It.IsAny())) + .ReturnsAsync(true); })); options.SetRevocationEndpointUris(Array.Empty()); @@ -2053,8 +2024,8 @@ namespace OpenIddict.Server.FunctionalTests mock.Setup(manager => manager.FindByClientIdAsync("Fabrikam", It.IsAny())) .ReturnsAsync(application); - mock.Setup(manager => manager.GetClientTypeAsync(application, It.IsAny())) - .ReturnsAsync(ClientTypes.Public); + mock.Setup(manager => manager.HasClientTypeAsync(application, ClientTypes.Public, It.IsAny())) + .ReturnsAsync(true); })); options.Services.AddSingleton(manager); @@ -2188,8 +2159,8 @@ namespace OpenIddict.Server.FunctionalTests mock.Setup(manager => manager.FindByClientIdAsync("Fabrikam", It.IsAny())) .ReturnsAsync(application); - mock.Setup(manager => manager.GetClientTypeAsync(application, It.IsAny())) - .ReturnsAsync(ClientTypes.Public); + mock.Setup(manager => manager.HasClientTypeAsync(application, ClientTypes.Public, It.IsAny())) + .ReturnsAsync(true); })); options.Services.AddSingleton(manager); @@ -2327,8 +2298,8 @@ namespace OpenIddict.Server.FunctionalTests mock.Setup(manager => manager.FindByClientIdAsync("Fabrikam", It.IsAny())) .ReturnsAsync(application); - mock.Setup(manager => manager.GetClientTypeAsync(application, It.IsAny())) - .ReturnsAsync(ClientTypes.Public); + mock.Setup(manager => manager.HasClientTypeAsync(application, ClientTypes.Public, It.IsAny())) + .ReturnsAsync(true); })); options.Services.AddSingleton(CreateTokenManager(mock => @@ -2522,8 +2493,8 @@ namespace OpenIddict.Server.FunctionalTests mock.Setup(manager => manager.FindByClientIdAsync("Fabrikam", It.IsAny())) .ReturnsAsync(application); - mock.Setup(manager => manager.GetClientTypeAsync(application, It.IsAny())) - .ReturnsAsync(ClientTypes.Public); + mock.Setup(manager => manager.HasClientTypeAsync(application, ClientTypes.Public, It.IsAny())) + .ReturnsAsync(true); })); options.Services.AddSingleton(manager); @@ -2693,8 +2664,8 @@ namespace OpenIddict.Server.FunctionalTests mock.Setup(manager => manager.FindByClientIdAsync("Fabrikam", It.IsAny())) .ReturnsAsync(application); - mock.Setup(manager => manager.GetClientTypeAsync(application, It.IsAny())) - .ReturnsAsync(ClientTypes.Public); + mock.Setup(manager => manager.HasClientTypeAsync(application, ClientTypes.Public, It.IsAny())) + .ReturnsAsync(true); })); options.Services.AddSingleton(manager); @@ -2836,8 +2807,8 @@ namespace OpenIddict.Server.FunctionalTests mock.Setup(manager => manager.FindByClientIdAsync("Fabrikam", It.IsAny())) .ReturnsAsync(application); - mock.Setup(manager => manager.GetClientTypeAsync(application, It.IsAny())) - .ReturnsAsync(ClientTypes.Public); + mock.Setup(manager => manager.HasClientTypeAsync(application, ClientTypes.Public, It.IsAny())) + .ReturnsAsync(true); })); options.Services.AddSingleton(CreateTokenManager(mock => @@ -3011,8 +2982,8 @@ namespace OpenIddict.Server.FunctionalTests mock.Setup(manager => manager.FindByClientIdAsync("Fabrikam", It.IsAny())) .ReturnsAsync(application); - mock.Setup(manager => manager.GetClientTypeAsync(application, It.IsAny())) - .ReturnsAsync(ClientTypes.Public); + mock.Setup(manager => manager.HasClientTypeAsync(application, ClientTypes.Public, It.IsAny())) + .ReturnsAsync(true); })); options.Services.AddSingleton(CreateTokenManager(mock => @@ -3107,8 +3078,8 @@ namespace OpenIddict.Server.FunctionalTests mock.Setup(manager => manager.FindByClientIdAsync("Fabrikam", It.IsAny())) .ReturnsAsync(application); - mock.Setup(manager => manager.GetClientTypeAsync(application, It.IsAny())) - .ReturnsAsync(ClientTypes.Public); + mock.Setup(manager => manager.HasClientTypeAsync(application, ClientTypes.Public, It.IsAny())) + .ReturnsAsync(true); })); options.Services.AddSingleton(CreateTokenManager(mock => @@ -3379,8 +3350,8 @@ namespace OpenIddict.Server.FunctionalTests mock.Setup(manager => manager.FindByClientIdAsync("Fabrikam", It.IsAny())) .ReturnsAsync(application); - mock.Setup(manager => manager.GetClientTypeAsync(application, It.IsAny())) - .ReturnsAsync(ClientTypes.Confidential); + mock.Setup(manager => manager.HasClientTypeAsync(application, ClientTypes.Confidential, It.IsAny())) + .ReturnsAsync(true); mock.Setup(manager => manager.ValidateClientSecretAsync(application, "7Fjfp0ZBr1KtDRbnfVdmIw", It.IsAny())) .ReturnsAsync(true); diff --git a/test/OpenIddict.Server.IntegrationTests/OpenIddictServerIntegrationTests.Introspection.cs b/test/OpenIddict.Server.IntegrationTests/OpenIddictServerIntegrationTests.Introspection.cs index bd306eee..4fe8a18a 100644 --- a/test/OpenIddict.Server.IntegrationTests/OpenIddictServerIntegrationTests.Introspection.cs +++ b/test/OpenIddict.Server.IntegrationTests/OpenIddictServerIntegrationTests.Introspection.cs @@ -469,8 +469,8 @@ namespace OpenIddict.Server.FunctionalTests mock.Setup(manager => manager.FindByClientIdAsync("Fabrikam", It.IsAny())) .ReturnsAsync(application); - mock.Setup(manager => manager.GetClientTypeAsync(application, It.IsAny())) - .ReturnsAsync(ClientTypes.Public); + mock.Setup(manager => manager.HasClientTypeAsync(application, ClientTypes.Public, It.IsAny())) + .ReturnsAsync(true); mock.Setup(manager => manager.HasPermissionAsync(application, Permissions.Endpoints.Introspection, It.IsAny())) @@ -500,6 +500,78 @@ namespace OpenIddict.Server.FunctionalTests Permissions.Endpoints.Introspection, It.IsAny()), Times.Once()); } + [Fact] + public async Task ValidateIntrospectionRequest_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); + }); + + var client = CreateClient(builder => + { + builder.Services.AddSingleton(manager); + }); + + // Act + var response = await client.PostAsync("/connect/introspect", new OpenIddictRequest + { + ClientId = "Fabrikam", + ClientSecret = "7Fjfp0ZBr1KtDRbnfVdmIw", + Token = "2YotnFZFEjr1zCsicMWpAA" + }); + + // Assert + Assert.Equal(Errors.InvalidClient, response.Error); + Assert.Equal("The 'client_secret' parameter is not valid for this client application.", response.ErrorDescription); + + 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 ValidateIntrospectionRequest_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); + }); + + var client = CreateClient(builder => + { + builder.Services.AddSingleton(manager); + }); + + // Act + var response = await client.PostAsync("/connect/introspect", new OpenIddictRequest + { + ClientId = "Fabrikam", + ClientSecret = null, + Token = "2YotnFZFEjr1zCsicMWpAA" + }); + + // Assert + Assert.Equal(Errors.InvalidClient, response.Error); + Assert.Equal("The 'client_secret' parameter required for this client application is missing.", response.ErrorDescription); + + 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 ValidateIntrospectionRequest_RequestIsRejectedWhenClientCredentialsAreInvalid() { @@ -511,8 +583,8 @@ namespace OpenIddict.Server.FunctionalTests mock.Setup(manager => manager.FindByClientIdAsync("Fabrikam", It.IsAny())) .ReturnsAsync(application); - mock.Setup(manager => manager.GetClientTypeAsync(application, It.IsAny())) - .ReturnsAsync(ClientTypes.Confidential); + mock.Setup(manager => manager.HasClientTypeAsync(application, ClientTypes.Public, It.IsAny())) + .ReturnsAsync(false); mock.Setup(manager => manager.ValidateClientSecretAsync(application, "7Fjfp0ZBr1KtDRbnfVdmIw", It.IsAny())) .ReturnsAsync(false); @@ -534,7 +606,7 @@ namespace OpenIddict.Server.FunctionalTests // Assert Mock.Get(manager).Verify(manager => manager.FindByClientIdAsync("Fabrikam", It.IsAny()), Times.AtLeastOnce()); - Mock.Get(manager).Verify(manager => manager.GetClientTypeAsync(application, 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()); } @@ -825,8 +897,8 @@ namespace OpenIddict.Server.FunctionalTests mock.Setup(manager => manager.FindByClientIdAsync("Fabrikam", It.IsAny())) .ReturnsAsync(application); - mock.Setup(manager => manager.GetClientTypeAsync(application, It.IsAny())) - .ReturnsAsync(ClientTypes.Confidential); + mock.Setup(manager => manager.HasClientTypeAsync(application, ClientTypes.Confidential, It.IsAny())) + .ReturnsAsync(true); mock.Setup(manager => manager.ValidateClientSecretAsync(application, "7Fjfp0ZBr1KtDRbnfVdmIw", It.IsAny())) .ReturnsAsync(true); @@ -883,8 +955,8 @@ namespace OpenIddict.Server.FunctionalTests mock.Setup(manager => manager.FindByClientIdAsync("Fabrikam", It.IsAny())) .ReturnsAsync(application); - mock.Setup(manager => manager.GetClientTypeAsync(application, It.IsAny())) - .ReturnsAsync(ClientTypes.Confidential); + mock.Setup(manager => manager.HasClientTypeAsync(application, ClientTypes.Confidential, It.IsAny())) + .ReturnsAsync(true); mock.Setup(manager => manager.ValidateClientSecretAsync(application, "7Fjfp0ZBr1KtDRbnfVdmIw", It.IsAny())) .ReturnsAsync(true); @@ -938,8 +1010,8 @@ namespace OpenIddict.Server.FunctionalTests mock.Setup(manager => manager.FindByClientIdAsync("Fabrikam", It.IsAny())) .ReturnsAsync(application); - mock.Setup(manager => manager.GetClientTypeAsync(application, It.IsAny())) - .ReturnsAsync(ClientTypes.Confidential); + mock.Setup(manager => manager.HasClientTypeAsync(application, ClientTypes.Confidential, It.IsAny())) + .ReturnsAsync(true); mock.Setup(manager => manager.ValidateClientSecretAsync(application, "7Fjfp0ZBr1KtDRbnfVdmIw", It.IsAny())) .ReturnsAsync(true); @@ -1003,8 +1075,8 @@ namespace OpenIddict.Server.FunctionalTests mock.Setup(manager => manager.FindByClientIdAsync("Fabrikam", It.IsAny())) .ReturnsAsync(application); - mock.Setup(manager => manager.GetClientTypeAsync(application, It.IsAny())) - .ReturnsAsync(ClientTypes.Confidential); + mock.Setup(manager => manager.HasClientTypeAsync(application, ClientTypes.Confidential, It.IsAny())) + .ReturnsAsync(true); mock.Setup(manager => manager.ValidateClientSecretAsync(application, "7Fjfp0ZBr1KtDRbnfVdmIw", It.IsAny())) .ReturnsAsync(true); @@ -1100,8 +1172,8 @@ namespace OpenIddict.Server.FunctionalTests mock.Setup(manager => manager.FindByClientIdAsync("Fabrikam", It.IsAny())) .ReturnsAsync(application); - mock.Setup(manager => manager.GetClientTypeAsync(application, It.IsAny())) - .ReturnsAsync(ClientTypes.Confidential); + mock.Setup(manager => manager.HasClientTypeAsync(application, ClientTypes.Confidential, It.IsAny())) + .ReturnsAsync(true); mock.Setup(manager => manager.ValidateClientSecretAsync(application, "7Fjfp0ZBr1KtDRbnfVdmIw", It.IsAny())) .ReturnsAsync(true); @@ -1165,8 +1237,8 @@ namespace OpenIddict.Server.FunctionalTests mock.Setup(manager => manager.FindByClientIdAsync("Fabrikam", It.IsAny())) .ReturnsAsync(application); - mock.Setup(manager => manager.GetClientTypeAsync(application, It.IsAny())) - .ReturnsAsync(ClientTypes.Confidential); + mock.Setup(manager => manager.HasClientTypeAsync(application, ClientTypes.Confidential, It.IsAny())) + .ReturnsAsync(true); mock.Setup(manager => manager.ValidateClientSecretAsync(application, "7Fjfp0ZBr1KtDRbnfVdmIw", It.IsAny())) .ReturnsAsync(true); @@ -1256,8 +1328,8 @@ namespace OpenIddict.Server.FunctionalTests mock.Setup(manager => manager.FindByClientIdAsync("Fabrikam", It.IsAny())) .ReturnsAsync(application); - mock.Setup(manager => manager.GetClientTypeAsync(application, It.IsAny())) - .ReturnsAsync(ClientTypes.Confidential); + mock.Setup(manager => manager.HasClientTypeAsync(application, ClientTypes.Confidential, It.IsAny())) + .ReturnsAsync(true); mock.Setup(manager => manager.ValidateClientSecretAsync(application, "7Fjfp0ZBr1KtDRbnfVdmIw", It.IsAny())) .ReturnsAsync(true); @@ -1354,8 +1426,8 @@ namespace OpenIddict.Server.FunctionalTests mock.Setup(manager => manager.FindByClientIdAsync("Fabrikam", It.IsAny())) .ReturnsAsync(application); - mock.Setup(manager => manager.GetClientTypeAsync(application, It.IsAny())) - .ReturnsAsync(ClientTypes.Confidential); + mock.Setup(manager => manager.HasClientTypeAsync(application, ClientTypes.Confidential, It.IsAny())) + .ReturnsAsync(true); mock.Setup(manager => manager.ValidateClientSecretAsync(application, "7Fjfp0ZBr1KtDRbnfVdmIw", It.IsAny())) .ReturnsAsync(true); @@ -1465,8 +1537,8 @@ namespace OpenIddict.Server.FunctionalTests mock.Setup(manager => manager.FindByClientIdAsync("Fabrikam", It.IsAny())) .ReturnsAsync(application); - mock.Setup(manager => manager.GetClientTypeAsync(application, It.IsAny())) - .ReturnsAsync(ClientTypes.Confidential); + mock.Setup(manager => manager.HasClientTypeAsync(application, ClientTypes.Confidential, It.IsAny())) + .ReturnsAsync(true); mock.Setup(manager => manager.ValidateClientSecretAsync(application, "7Fjfp0ZBr1KtDRbnfVdmIw", It.IsAny())) .ReturnsAsync(true); diff --git a/test/OpenIddict.Server.IntegrationTests/OpenIddictServerIntegrationTests.Revocation.cs b/test/OpenIddict.Server.IntegrationTests/OpenIddictServerIntegrationTests.Revocation.cs index e9d3bfc6..6bff9aaf 100644 --- a/test/OpenIddict.Server.IntegrationTests/OpenIddictServerIntegrationTests.Revocation.cs +++ b/test/OpenIddict.Server.IntegrationTests/OpenIddictServerIntegrationTests.Revocation.cs @@ -409,8 +409,8 @@ namespace OpenIddict.Server.FunctionalTests mock.Setup(manager => manager.FindByClientIdAsync("Fabrikam", It.IsAny())) .ReturnsAsync(application); - mock.Setup(manager => manager.GetClientTypeAsync(application, It.IsAny())) - .ReturnsAsync(ClientTypes.Public); + mock.Setup(manager => manager.HasClientTypeAsync(application, ClientTypes.Public, It.IsAny())) + .ReturnsAsync(true); mock.Setup(manager => manager.HasPermissionAsync(application, Permissions.Endpoints.Revocation, It.IsAny())) @@ -452,8 +452,8 @@ namespace OpenIddict.Server.FunctionalTests mock.Setup(manager => manager.FindByClientIdAsync("Fabrikam", It.IsAny())) .ReturnsAsync(application); - mock.Setup(manager => manager.GetClientTypeAsync(application, It.IsAny())) - .ReturnsAsync(ClientTypes.Public); + mock.Setup(manager => manager.HasClientTypeAsync(application, ClientTypes.Public, It.IsAny())) + .ReturnsAsync(true); }); var client = CreateClient(builder => @@ -475,11 +475,11 @@ namespace OpenIddict.Server.FunctionalTests Assert.Equal("The 'client_secret' parameter is not valid for this client application.", response.ErrorDescription); Mock.Get(manager).Verify(manager => manager.FindByClientIdAsync("Fabrikam", It.IsAny()), Times.AtLeastOnce()); - Mock.Get(manager).Verify(manager => manager.GetClientTypeAsync(application, It.IsAny()), Times.Once()); + Mock.Get(manager).Verify(manager => manager.HasClientTypeAsync(application, ClientTypes.Public, It.IsAny()), Times.Once()); } [Fact] - public async Task ValidateRevocationRequest_ClientSecretIsRequiredForConfidentialClients() + public async Task ValidateRevocationRequest_ClientSecretIsRequiredForNonPublicClients() { // Arrange var application = new OpenIddictApplication(); @@ -489,45 +489,8 @@ namespace OpenIddict.Server.FunctionalTests mock.Setup(manager => manager.FindByClientIdAsync("Fabrikam", It.IsAny())) .ReturnsAsync(application); - mock.Setup(manager => manager.GetClientTypeAsync(application, It.IsAny())) - .ReturnsAsync(ClientTypes.Confidential); - }); - - var client = CreateClient(builder => - { - builder.Services.AddSingleton(manager); - }); - - // Act - var response = await client.PostAsync("/connect/revoke", new OpenIddictRequest - { - ClientId = "Fabrikam", - ClientSecret = null, - Token = "SlAV32hkKG", - TokenTypeHint = TokenTypeHints.RefreshToken - }); - - // Assert - Assert.Equal(Errors.InvalidClient, response.Error); - Assert.Equal("The 'client_secret' parameter required for this client application is missing.", response.ErrorDescription); - - Mock.Get(manager).Verify(manager => manager.FindByClientIdAsync("Fabrikam", It.IsAny()), Times.AtLeastOnce()); - Mock.Get(manager).Verify(manager => manager.GetClientTypeAsync(application, It.IsAny()), Times.Once()); - } - - [Fact] - public async Task ValidateRevocationRequest_ClientSecretIsRequiredForHybridClients() - { - // Arrange - var application = new OpenIddictApplication(); - - var manager = CreateApplicationManager(mock => - { - mock.Setup(manager => manager.FindByClientIdAsync("Fabrikam", It.IsAny())) - .ReturnsAsync(application); - - mock.Setup(manager => manager.GetClientTypeAsync(application, It.IsAny())) - .ReturnsAsync(ClientTypes.Hybrid); + mock.Setup(manager => manager.HasClientTypeAsync(application, ClientTypes.Public, It.IsAny())) + .ReturnsAsync(false); }); var client = CreateClient(builder => @@ -549,7 +512,7 @@ namespace OpenIddict.Server.FunctionalTests Assert.Equal("The 'client_secret' parameter required for this client application is missing.", response.ErrorDescription); Mock.Get(manager).Verify(manager => manager.FindByClientIdAsync("Fabrikam", It.IsAny()), Times.AtLeastOnce()); - Mock.Get(manager).Verify(manager => manager.GetClientTypeAsync(application, It.IsAny()), Times.Once()); + Mock.Get(manager).Verify(manager => manager.HasClientTypeAsync(application, ClientTypes.Public, It.IsAny()), Times.Once()); } [Fact] @@ -563,8 +526,8 @@ namespace OpenIddict.Server.FunctionalTests mock.Setup(manager => manager.FindByClientIdAsync("Fabrikam", It.IsAny())) .ReturnsAsync(application); - mock.Setup(manager => manager.GetClientTypeAsync(application, It.IsAny())) - .ReturnsAsync(ClientTypes.Confidential); + mock.Setup(manager => manager.HasClientTypeAsync(application, ClientTypes.Public, It.IsAny())) + .ReturnsAsync(false); mock.Setup(manager => manager.ValidateClientSecretAsync(application, "7Fjfp0ZBr1KtDRbnfVdmIw", It.IsAny())) .ReturnsAsync(false); @@ -589,7 +552,7 @@ namespace OpenIddict.Server.FunctionalTests Assert.Equal("The specified client credentials are invalid.", response.ErrorDescription); Mock.Get(manager).Verify(manager => manager.FindByClientIdAsync("Fabrikam", It.IsAny()), Times.AtLeastOnce()); - Mock.Get(manager).Verify(manager => manager.GetClientTypeAsync(application, 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()); } diff --git a/test/OpenIddict.Server.IntegrationTests/OpenIddictServerIntegrationTests.cs b/test/OpenIddict.Server.IntegrationTests/OpenIddictServerIntegrationTests.cs index a8ed0e43..c5fac53c 100644 --- a/test/OpenIddict.Server.IntegrationTests/OpenIddictServerIntegrationTests.cs +++ b/test/OpenIddict.Server.IntegrationTests/OpenIddictServerIntegrationTests.cs @@ -2679,8 +2679,8 @@ namespace OpenIddict.Server.FunctionalTests mock.Setup(manager => manager.FindByClientIdAsync("Fabrikam", It.IsAny())) .ReturnsAsync(application); - mock.Setup(manager => manager.GetClientTypeAsync(application, It.IsAny())) - .ReturnsAsync(ClientTypes.Public); + mock.Setup(manager => manager.HasClientTypeAsync(application, ClientTypes.Public, It.IsAny())) + .ReturnsAsync(true); })); options.Services.AddSingleton(manager); @@ -2749,8 +2749,11 @@ namespace OpenIddict.Server.FunctionalTests mock.Setup(manager => manager.FindByClientIdAsync("Fabrikam", It.IsAny())) .ReturnsAsync(application); - mock.Setup(manager => manager.GetClientTypeAsync(application, It.IsAny())) - .ReturnsAsync(ClientTypes.Public); + mock.Setup(manager => manager.HasClientTypeAsync(application, ClientTypes.Public, It.IsAny())) + .ReturnsAsync(true); + + mock.Setup(manager => manager.HasClientTypeAsync(application, ClientTypes.Public, It.IsAny())) + .ReturnsAsync(true); })); options.Services.AddSingleton(manager); @@ -3465,8 +3468,8 @@ namespace OpenIddict.Server.FunctionalTests mock.Setup(manager => manager.ValidateRedirectUriAsync(application, "http://www.fabrikam.com/path", It.IsAny())) .ReturnsAsync(true); - mock.Setup(manager => manager.GetClientTypeAsync(application, It.IsAny())) - .ReturnsAsync(ClientTypes.Public); + mock.Setup(manager => manager.HasClientTypeAsync(application, ClientTypes.Public, It.IsAny())) + .ReturnsAsync(true); mock.Setup(manager => manager.GetIdAsync(application, It.IsAny())) .ReturnsAsync("3E228451-1555-46F7-A471-951EFBA23A56"); @@ -3536,8 +3539,8 @@ namespace OpenIddict.Server.FunctionalTests mock.Setup(manager => manager.ValidateRedirectUriAsync(application, "http://www.fabrikam.com/path", It.IsAny())) .ReturnsAsync(true); - mock.Setup(manager => manager.GetClientTypeAsync(application, It.IsAny())) - .ReturnsAsync(ClientTypes.Public); + mock.Setup(manager => manager.HasClientTypeAsync(application, ClientTypes.Public, It.IsAny())) + .ReturnsAsync(true); mock.Setup(manager => manager.GetIdAsync(application, It.IsAny())) .ReturnsAsync("3E228451-1555-46F7-A471-951EFBA23A56");