diff --git a/samples/Mvc.Server/Startup.cs b/samples/Mvc.Server/Startup.cs index d6bad2d6..97063028 100644 --- a/samples/Mvc.Server/Startup.cs +++ b/samples/Mvc.Server/Startup.cs @@ -151,7 +151,15 @@ namespace Mvc.Server ClientSecret = "901564A5-E7FE-42CB-B10D-61EF6A8F3654", DisplayName = "MVC client application", PostLogoutRedirectUris = { new Uri("http://localhost:53507/signout-callback-oidc") }, - RedirectUris = { new Uri("http://localhost:53507/signin-oidc") } + RedirectUris = { new Uri("http://localhost:53507/signin-oidc") }, + Permissions = + { + OpenIddictConstants.Permissions.Endpoints.Authorization, + OpenIddictConstants.Permissions.Endpoints.Logout, + OpenIddictConstants.Permissions.Endpoints.Token, + OpenIddictConstants.Permissions.GrantTypes.AuthorizationCode, + OpenIddictConstants.Permissions.GrantTypes.RefreshToken + } }; await manager.CreateAsync(descriptor, cancellationToken); @@ -172,7 +180,13 @@ namespace Mvc.Server { ClientId = "postman", DisplayName = "Postman", - RedirectUris = { new Uri("https://www.getpostman.com/oauth2/callback") } + RedirectUris = { new Uri("https://www.getpostman.com/oauth2/callback") }, + Permissions = + { + OpenIddictConstants.Permissions.Endpoints.Authorization, + OpenIddictConstants.Permissions.Endpoints.Token, + OpenIddictConstants.Permissions.GrantTypes.AuthorizationCode + } }; await manager.CreateAsync(descriptor, cancellationToken); diff --git a/src/OpenIddict.Core/Descriptors/OpenIddictApplicationDescriptor.cs b/src/OpenIddict.Core/Descriptors/OpenIddictApplicationDescriptor.cs index 11cbcdae..8f1524cc 100644 --- a/src/OpenIddict.Core/Descriptors/OpenIddictApplicationDescriptor.cs +++ b/src/OpenIddict.Core/Descriptors/OpenIddictApplicationDescriptor.cs @@ -27,6 +27,11 @@ namespace OpenIddict.Core /// public string DisplayName { get; set; } + /// + /// Gets the permissions associated with the application. + /// + public ISet Permissions { get; } = new HashSet(StringComparer.OrdinalIgnoreCase); + /// /// Gets the logout callback URLs /// associated with the application. diff --git a/src/OpenIddict.Core/Managers/OpenIddictApplicationManager.cs b/src/OpenIddict.Core/Managers/OpenIddictApplicationManager.cs index 0585d32a..1cbcd6a8 100644 --- a/src/OpenIddict.Core/Managers/OpenIddictApplicationManager.cs +++ b/src/OpenIddict.Core/Managers/OpenIddictApplicationManager.cs @@ -417,6 +417,25 @@ namespace OpenIddict.Core return Store.GetIdAsync(application, cancellationToken); } + /// + /// Retrieves the permissions associated with an application. + /// + /// The application. + /// The that can be used to abort the operation. + /// + /// A that can be used to monitor the asynchronous operation, + /// whose result returns all the permissions associated with the application. + /// + public virtual Task> GetPermissionsAsync([NotNull] TApplication application, CancellationToken cancellationToken) + { + if (application == null) + { + throw new ArgumentNullException(nameof(application)); + } + + return Store.GetPermissionsAsync(application, cancellationToken); + } + /// /// Retrieves the logout callback addresses associated with an application. /// @@ -455,6 +474,23 @@ namespace OpenIddict.Core return Store.GetRedirectUrisAsync(application, cancellationToken); } + /// + /// 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 has been granted the specified permission, false otherwise. + public virtual async Task HasPermissionAsync([NotNull] TApplication application, [NotNull] string permission, CancellationToken cancellationToken) + { + if (application == null) + { + throw new ArgumentNullException(nameof(application)); + } + + return (await Store.GetPermissionsAsync(application, cancellationToken)).Contains(permission, StringComparer.OrdinalIgnoreCase); + } + /// /// Determines whether an application is a confidential client. /// @@ -668,6 +704,8 @@ namespace OpenIddict.Core Type = await Store.GetClientTypeAsync(application, cancellationToken) }; + descriptor.Permissions.UnionWith(await Store.GetPermissionsAsync(application, cancellationToken)); + foreach (var address in await Store.GetPostLogoutRedirectUrisAsync(application, cancellationToken)) { // Ensure the address is not null or empty. @@ -783,6 +821,12 @@ namespace OpenIddict.Core // To ensure a case-sensitive comparison is used, string.Equals(Ordinal) is manually called here. foreach (var application in await Store.FindByPostLogoutRedirectUriAsync(address, cancellationToken)) { + // If the application is not allowed to use the logout endpoint, ignore it and keep iterating. + if (!await HasPermissionAsync(application, OpenIddictConstants.Permissions.Endpoints.Logout, cancellationToken)) + { + continue; + } + foreach (var uri in await Store.GetPostLogoutRedirectUrisAsync(application, cancellationToken)) { // Note: the post_logout_redirect_uri must be compared using case-sensitive "Simple String Comparison". @@ -864,6 +908,7 @@ namespace OpenIddict.Core await Store.SetClientSecretAsync(application, descriptor.ClientSecret, cancellationToken); await Store.SetClientTypeAsync(application, descriptor.Type, cancellationToken); await Store.SetDisplayNameAsync(application, descriptor.DisplayName, cancellationToken); + await Store.SetPermissionsAsync(application, ImmutableArray.CreateRange(descriptor.Permissions), cancellationToken); await Store.SetPostLogoutRedirectUrisAsync(application, ImmutableArray.CreateRange( descriptor.PostLogoutRedirectUris.Select(address => address.OriginalString)), cancellationToken); await Store.SetRedirectUrisAsync(application, ImmutableArray.CreateRange( diff --git a/src/OpenIddict.Core/OpenIddictConstants.cs b/src/OpenIddict.Core/OpenIddictConstants.cs index 56d38393..201f1e37 100644 --- a/src/OpenIddict.Core/OpenIddictConstants.cs +++ b/src/OpenIddict.Core/OpenIddictConstants.cs @@ -37,6 +37,33 @@ namespace OpenIddict.Core public const string ExternalProvidersSupported = "external_providers_supported"; } + public static class Permissions + { + public static class Endpoints + { + public const string Authorization = "ept:authorization"; + public const string Introspection = "ept:introspection"; + public const string Logout = "ept:logout"; + public const string Revocation = "ept:revocation"; + public const string Token = "ept:token"; + } + + public static class GrantTypes + { + public const string AuthorizationCode = "gt:authorization_code"; + public const string ClientCredentials = "gt:client_credentials"; + public const string Implicit = "gt:implicit"; + public const string Password = "gt:password"; + public const string RefreshToken = "gt:refresh_token"; + } + + public static class Prefixes + { + public const string Endpoint = "ept:"; + public const string GrantType = "gt:"; + } + } + public static class Properties { public const string Application = ".application"; diff --git a/src/OpenIddict.Core/Stores/IOpenIddictApplicationStore.cs b/src/OpenIddict.Core/Stores/IOpenIddictApplicationStore.cs index f9dd5470..8c87415c 100644 --- a/src/OpenIddict.Core/Stores/IOpenIddictApplicationStore.cs +++ b/src/OpenIddict.Core/Stores/IOpenIddictApplicationStore.cs @@ -179,6 +179,17 @@ namespace OpenIddict.Core /// Task GetIdAsync([NotNull] TApplication application, CancellationToken cancellationToken); + /// + /// Retrieves the permissions associated with an application. + /// + /// The application. + /// The that can be used to abort the operation. + /// + /// A that can be used to monitor the asynchronous operation, + /// whose result returns all the permissions associated with the application. + /// + Task> GetPermissionsAsync([NotNull] TApplication application, CancellationToken cancellationToken); + /// /// Retrieves the logout callback addresses associated with an application. /// @@ -296,6 +307,17 @@ namespace OpenIddict.Core /// Task SetDisplayNameAsync([NotNull] TApplication application, [CanBeNull] string name, CancellationToken cancellationToken); + /// + /// Sets the permissions associated with an application. + /// + /// The application. + /// The permissions associated with the application + /// The that can be used to abort the operation. + /// + /// A that can be used to monitor the asynchronous operation. + /// + Task SetPermissionsAsync([NotNull] TApplication application, ImmutableArray permissions, CancellationToken cancellationToken); + /// /// Sets the logout callback addresses associated with an application. /// diff --git a/src/OpenIddict.Core/Stores/OpenIddictApplicationStore.cs b/src/OpenIddict.Core/Stores/OpenIddictApplicationStore.cs index 620e0d1a..1e9002a9 100644 --- a/src/OpenIddict.Core/Stores/OpenIddictApplicationStore.cs +++ b/src/OpenIddict.Core/Stores/OpenIddictApplicationStore.cs @@ -355,6 +355,30 @@ namespace OpenIddict.Core return Task.FromResult(ConvertIdentifierToString(application.Id)); } + /// + /// Retrieves the permissions associated with an application. + /// + /// The application. + /// The that can be used to abort the operation. + /// + /// A that can be used to monitor the asynchronous operation, + /// whose result returns all the permissions associated with the application. + /// + public virtual Task> GetPermissionsAsync([NotNull] TApplication application, CancellationToken cancellationToken) + { + if (application == null) + { + throw new ArgumentNullException(nameof(application)); + } + + if (string.IsNullOrEmpty(application.Permissions)) + { + return Task.FromResult(ImmutableArray.Create()); + } + + return Task.FromResult(JArray.Parse(application.Permissions).Select(element => (string) element).ToImmutableArray()); + } + /// /// Retrieves the logout callback addresses associated with an application. /// @@ -583,6 +607,34 @@ namespace OpenIddict.Core return Task.CompletedTask; } + /// + /// Sets the permissions associated with an application. + /// + /// The application. + /// The permissions associated with the application + /// The that can be used to abort the operation. + /// + /// A that can be used to monitor the asynchronous operation. + /// + public virtual Task SetPermissionsAsync([NotNull] TApplication application, ImmutableArray permissions, CancellationToken cancellationToken) + { + if (application == null) + { + throw new ArgumentNullException(nameof(application)); + } + + if (permissions.IsDefaultOrEmpty) + { + application.Permissions = null; + + return Task.CompletedTask; + } + + application.Permissions = new JArray(permissions.ToArray()).ToString(Formatting.None); + + return Task.CompletedTask; + } + /// /// Sets the logout callback addresses associated with an application. /// diff --git a/src/OpenIddict.Models/OpenIddictApplication.cs b/src/OpenIddict.Models/OpenIddictApplication.cs index f198b7e8..f5d6b0d6 100644 --- a/src/OpenIddict.Models/OpenIddictApplication.cs +++ b/src/OpenIddict.Models/OpenIddictApplication.cs @@ -68,6 +68,12 @@ namespace OpenIddict.Models /// public virtual TKey Id { get; set; } + /// + /// Gets or sets the permissions associated with the + /// current application, serialized as a JSON array. + /// + public virtual string Permissions { get; set; } + /// /// Gets or sets the logout callback URLs /// associated with the current application, diff --git a/src/OpenIddict/OpenIddictProvider.Authentication.cs b/src/OpenIddict/OpenIddictProvider.Authentication.cs index 10910073..47c52c3d 100644 --- a/src/OpenIddict/OpenIddictProvider.Authentication.cs +++ b/src/OpenIddict/OpenIddictProvider.Authentication.cs @@ -271,30 +271,105 @@ namespace OpenIddict // from the other provider methods without having to call the store twice. context.Request.SetProperty($"{OpenIddictConstants.Properties.Application}:{context.ClientId}", application); - // Ensure that the specified redirect_uri is valid and is associated with the client application. - if (!await Applications.ValidateRedirectUriAsync(application, context.RedirectUri, context.HttpContext.RequestAborted)) + // To prevent downgrade attacks, ensure that authorization requests returning a token directly from + // the authorization endpoint are rejected if the client_id corresponds to a confidential application. + // Note: when using the authorization code grant, ValidateTokenRequest is responsible of rejecting + // the token request if the client_id corresponds to an unauthenticated confidential client. + if (await Applications.IsConfidentialAsync(application, context.HttpContext.RequestAborted) && + (context.Request.HasResponseType(OpenIdConnectConstants.ResponseTypes.IdToken) || + context.Request.HasResponseType(OpenIdConnectConstants.ResponseTypes.Token))) { - Logger.LogError("The authorization request was rejected because the redirect_uri " + - "was invalid: '{RedirectUri}'.", context.RedirectUri); + context.Reject( + error: OpenIdConnectConstants.Errors.UnsupportedResponseType, + description: "The specified 'response_type' parameter is not valid for this client application."); + + return; + } + + // Reject the request if the application is not allowed to use the authorization endpoint. + if (!await Applications.HasPermissionAsync(application, + OpenIddictConstants.Permissions.Endpoints.Authorization, context.HttpContext.RequestAborted)) + { + Logger.LogError("The authorization request was rejected because the application '{ClientId}' " + + "was not allowed to use the authorization endpoint.", context.ClientId); + + context.Reject( + error: OpenIdConnectConstants.Errors.UnauthorizedClient, + description: "This client application is not allowed to use the authorization endpoint."); + + return; + } + + // Reject the request if the application is not allowed to use the authorization code flow. + if (context.Request.IsAuthorizationCodeFlow() && !await Applications.HasPermissionAsync(application, + OpenIddictConstants.Permissions.GrantTypes.AuthorizationCode, context.HttpContext.RequestAborted)) + { + Logger.LogError("The authorization request was rejected because the application '{ClientId}' " + + "was not allowed to use the authorization code flow.", context.ClientId); + + context.Reject( + error: OpenIdConnectConstants.Errors.UnauthorizedClient, + description: "The client application is not allowed to use the authorization code flow."); + + return; + } + + // Reject the request if the application is not allowed to use the implicit flow. + if (context.Request.IsImplicitFlow() && !await Applications.HasPermissionAsync(application, + OpenIddictConstants.Permissions.GrantTypes.Implicit, context.HttpContext.RequestAborted)) + { + Logger.LogError("The authorization request was rejected because the application '{ClientId}' " + + "was not allowed to use the implicit flow.", context.ClientId); + + context.Reject( + error: OpenIdConnectConstants.Errors.UnauthorizedClient, + description: "The client application is not allowed to use the implicit flow."); + + return; + } + + // Reject the request if the application is not allowed to use the authorization code/implicit flows. + if (context.Request.IsHybridFlow() && + (!await Applications.HasPermissionAsync(application, + OpenIddictConstants.Permissions.GrantTypes.AuthorizationCode, context.HttpContext.RequestAborted) || + !await Applications.HasPermissionAsync(application, + OpenIddictConstants.Permissions.GrantTypes.Implicit, context.HttpContext.RequestAborted))) + { + Logger.LogError("The authorization request was rejected because the application '{ClientId}' " + + "was not allowed to use the hybrid flow.", context.ClientId); + + context.Reject( + error: OpenIdConnectConstants.Errors.UnauthorizedClient, + description: "The client application is not allowed to use the hybrid flow."); + + return; + } + + // Reject the request if the offline_access scope was request and if the + // application is not allowed to use the authorization code/implicit flows. + if (context.Request.HasScope(OpenIdConnectConstants.Scopes.OfflineAccess) && + !await Applications.HasPermissionAsync(application, + OpenIddictConstants.Permissions.GrantTypes.RefreshToken, context.HttpContext.RequestAborted)) + { + Logger.LogError("The authorization request was rejected because the application '{ClientId}' " + + "was not allowed to request the 'offline_access' scope.", context.ClientId); context.Reject( error: OpenIdConnectConstants.Errors.InvalidRequest, - description: "The specified 'redirect_uri' parameter is not valid for this client application."); + description: "The client application is not allowed to use the 'offline_access' scope."); return; } - // To prevent downgrade attacks, ensure that authorization requests returning a token directly from - // the authorization endpoint are rejected if the client_id corresponds to a confidential application. - // Note: when using the authorization code grant, ValidateTokenRequest is responsible of rejecting - // the token request if the client_id corresponds to an unauthenticated confidential client. - if (await Applications.IsConfidentialAsync(application, context.HttpContext.RequestAborted) && - (context.Request.HasResponseType(OpenIdConnectConstants.ResponseTypes.IdToken) || - context.Request.HasResponseType(OpenIdConnectConstants.ResponseTypes.Token))) + // Ensure that the specified redirect_uri is valid and is associated with the client application. + if (!await Applications.ValidateRedirectUriAsync(application, context.RedirectUri, context.HttpContext.RequestAborted)) { + Logger.LogError("The authorization request was rejected because the redirect_uri " + + "was invalid: '{RedirectUri}'.", context.RedirectUri); + context.Reject( error: OpenIdConnectConstants.Errors.InvalidRequest, - description: "The specified 'response_type' parameter is not valid for this client application."); + description: "The specified 'redirect_uri' parameter is not valid for this client application."); return; } diff --git a/src/OpenIddict/OpenIddictProvider.Exchange.cs b/src/OpenIddict/OpenIddictProvider.Exchange.cs index 300d2b62..fc01c73f 100644 --- a/src/OpenIddict/OpenIddictProvider.Exchange.cs +++ b/src/OpenIddict/OpenIddictProvider.Exchange.cs @@ -25,8 +25,8 @@ namespace OpenIddict // Reject token requests that don't specify a supported grant type. if (!options.GrantTypes.Contains(context.Request.GrantType)) { - Logger.LogError("The token request was rejected because the '{Grant}' " + - "grant is not supported.", context.Request.GrantType); + Logger.LogError("The token request was rejected because the '{GrantType}' " + + "grant type is not supported.", context.Request.GrantType); context.Reject( error: OpenIdConnectConstants.Errors.UnsupportedGrantType, @@ -135,6 +135,34 @@ namespace OpenIddict // from the other provider methods without having to call the store twice. context.Request.SetProperty($"{OpenIddictConstants.Properties.Application}:{context.ClientId}", application); + // Reject the request if the application is not allowed to use the token endpoint. + if (!await Applications.HasPermissionAsync(application, + OpenIddictConstants.Permissions.Endpoints.Token, context.HttpContext.RequestAborted)) + { + Logger.LogError("The token request was rejected because the application '{ClientId}' " + + "was not allowed to use the token endpoint.", context.ClientId); + + context.Reject( + error: OpenIdConnectConstants.Errors.UnauthorizedClient, + description: "This client application is not allowed to use the token endpoint."); + + return; + } + + // Reject the request if the application is not allowed to use the specified grant type. + if (!await Applications.HasPermissionAsync(application, + OpenIddictConstants.Permissions.Prefixes.GrantType + context.Request.GrantType, context.HttpContext.RequestAborted)) + { + Logger.LogError("The token request was rejected because the application '{ClientId}' was not allowed to " + + "use the specified grant type: {GrantType}.", context.ClientId, context.Request.GrantType); + + context.Reject( + error: OpenIdConnectConstants.Errors.UnauthorizedClient, + description: "This client application is not allowed to use the specified grant type."); + + return; + } + if (await Applications.IsPublicAsync(application, context.HttpContext.RequestAborted)) { // Note: public applications are not allowed to use the client credentials grant. diff --git a/src/OpenIddict/OpenIddictProvider.Introspection.cs b/src/OpenIddict/OpenIddictProvider.Introspection.cs index 292d677d..e066d458 100644 --- a/src/OpenIddict/OpenIddictProvider.Introspection.cs +++ b/src/OpenIddict/OpenIddictProvider.Introspection.cs @@ -68,6 +68,20 @@ namespace OpenIddict // from the other provider methods without having to call the store twice. context.Request.SetProperty($"{OpenIddictConstants.Properties.Application}:{context.ClientId}", application); + // Reject the request if the application is not allowed to use the introspection endpoint. + if (!await Applications.HasPermissionAsync(application, + OpenIddictConstants.Permissions.Endpoints.Introspection, context.HttpContext.RequestAborted)) + { + Logger.LogError("The introspection request was rejected because the application '{ClientId}' " + + "was not allowed to use the introspection endpoint.", context.ClientId); + + context.Reject( + error: OpenIdConnectConstants.Errors.UnauthorizedClient, + description: "This client application is not allowed to use the introspection endpoint."); + + return; + } + // Reject introspection requests sent by public applications. if (await Applications.IsPublicAsync(application, context.HttpContext.RequestAborted)) { diff --git a/src/OpenIddict/OpenIddictProvider.Revocation.cs b/src/OpenIddict/OpenIddictProvider.Revocation.cs index 945153ca..9c0af97e 100644 --- a/src/OpenIddict/OpenIddictProvider.Revocation.cs +++ b/src/OpenIddict/OpenIddictProvider.Revocation.cs @@ -94,6 +94,20 @@ namespace OpenIddict // from the other provider methods without having to call the store twice. context.Request.SetProperty($"{OpenIddictConstants.Properties.Application}:{context.ClientId}", application); + // Reject the request if the application is not allowed to use the revocation endpoint. + if (!await Applications.HasPermissionAsync(application, + OpenIddictConstants.Permissions.Endpoints.Revocation, context.HttpContext.RequestAborted)) + { + Logger.LogError("The revocation request was rejected because the application '{ClientId}' " + + "was not allowed to use the revocation endpoint.", context.ClientId); + + context.Reject( + error: OpenIdConnectConstants.Errors.UnauthorizedClient, + description: "This client application is not allowed to use the revocation endpoint."); + + return; + } + // Reject revocation requests containing a client_secret if the application is a public client. if (await Applications.IsPublicAsync(application, context.HttpContext.RequestAborted)) { diff --git a/test/OpenIddict.Tests/OpenIddictProviderTests.Authentication.cs b/test/OpenIddict.Tests/OpenIddictProviderTests.Authentication.cs index b1a43e75..fba20152 100644 --- a/test/OpenIddict.Tests/OpenIddictProviderTests.Authentication.cs +++ b/test/OpenIddict.Tests/OpenIddictProviderTests.Authentication.cs @@ -344,8 +344,53 @@ namespace OpenIddict.Tests Mock.Get(manager).Verify(mock => mock.FindByClientIdAsync("Fabrikam", It.IsAny()), Times.Once()); } + [Theory] + [InlineData("code id_token token")] + [InlineData("code token")] + [InlineData("id_token")] + [InlineData("id_token token")] + [InlineData("token")] + public async Task ValidateAuthorizationRequest_ImplicitOrHybridRequestIsRejectedWhenClientIsConfidential(string type) + { + // Arrange + var application = new OpenIddictApplication(); + + var manager = CreateApplicationManager(instance => + { + instance.Setup(mock => mock.FindByClientIdAsync("Fabrikam", It.IsAny())) + .ReturnsAsync(application); + + instance.Setup(mock => mock.GetClientTypeAsync(application, It.IsAny())) + .ReturnsAsync(OpenIddictConstants.ClientTypes.Confidential); + }); + + var server = CreateAuthorizationServer(builder => + { + builder.Services.AddSingleton(manager); + }); + + var client = new OpenIdConnectClient(server.CreateClient()); + + // Act + var response = await client.PostAsync(AuthorizationEndpoint, new OpenIdConnectRequest + { + ClientId = "Fabrikam", + Nonce = "n-0S6_WzA2Mj", + RedirectUri = "http://www.fabrikam.com/path", + ResponseType = type, + Scope = OpenIdConnectConstants.Scopes.OpenId + }); + + // Assert + Assert.Equal(OpenIdConnectConstants.Errors.UnsupportedResponseType, response.Error); + Assert.Equal("The specified 'response_type' parameter is not valid for this client application.", response.ErrorDescription); + + Mock.Get(manager).Verify(mock => mock.FindByClientIdAsync("Fabrikam", It.IsAny()), Times.Once()); + Mock.Get(manager).Verify(mock => mock.GetClientTypeAsync(application, It.IsAny()), Times.Once()); + } + [Fact] - public async Task ValidateAuthorizationRequest_RequestIsRejectedWhenRedirectUriIsInvalid() + public async Task ValidateAuthorizationRequest_RequestIsRejectedWhenEndpointPermissionIsNotGranted() { // Arrange var application = new OpenIddictApplication(); @@ -355,7 +400,8 @@ namespace OpenIddict.Tests instance.Setup(mock => mock.FindByClientIdAsync("Fabrikam", It.IsAny())) .ReturnsAsync(application); - instance.Setup(mock => mock.ValidateRedirectUriAsync(application, "http://www.fabrikam.com/path", It.IsAny())) + instance.Setup(mock => mock.HasPermissionAsync(application, + OpenIddictConstants.Permissions.Endpoints.Authorization, It.IsAny())) .ReturnsAsync(false); }); @@ -375,20 +421,45 @@ namespace OpenIddict.Tests }); // Assert - Assert.Equal(OpenIdConnectConstants.Errors.InvalidRequest, response.Error); - Assert.Equal("The specified 'redirect_uri' parameter is not valid for this client application.", response.ErrorDescription); + Assert.Equal(OpenIdConnectConstants.Errors.UnauthorizedClient, response.Error); + Assert.Equal("This client application is not allowed to use the authorization endpoint.", response.ErrorDescription); Mock.Get(manager).Verify(mock => mock.FindByClientIdAsync("Fabrikam", It.IsAny()), Times.Once()); - Mock.Get(manager).Verify(mock => mock.ValidateRedirectUriAsync(application, "http://www.fabrikam.com/path", It.IsAny()), Times.Once()); + Mock.Get(manager).Verify(mock => mock.HasPermissionAsync(application, + OpenIddictConstants.Permissions.Endpoints.Authorization, It.IsAny()), Times.Once()); } [Theory] - [InlineData("code id_token token")] - [InlineData("code token")] - [InlineData("id_token")] - [InlineData("id_token token")] - [InlineData("token")] - public async Task ValidateAuthorizationRequest_ImplicitOrHybridRequestIsRejectedWhenClientIsConfidential(string type) + [InlineData( + "code", + new[] { OpenIddictConstants.Permissions.GrantTypes.AuthorizationCode }, + "The client application is not allowed to use the authorization code flow.")] + [InlineData( + "code id_token", + new[] { OpenIddictConstants.Permissions.GrantTypes.AuthorizationCode, OpenIddictConstants.Permissions.GrantTypes.Implicit }, + "The client application is not allowed to use the hybrid flow.")] + [InlineData( + "code id_token token", + new[] { OpenIddictConstants.Permissions.GrantTypes.AuthorizationCode, OpenIddictConstants.Permissions.GrantTypes.Implicit }, + "The client application is not allowed to use the hybrid flow.")] + [InlineData( + "code token", + new[] { OpenIddictConstants.Permissions.GrantTypes.AuthorizationCode, OpenIddictConstants.Permissions.GrantTypes.Implicit }, + "The client application is not allowed to use the hybrid flow.")] + [InlineData( + "id_token", + new[] { OpenIddictConstants.Permissions.GrantTypes.Implicit }, + "The client application is not allowed to use the implicit flow.")] + [InlineData( + "id_token token", + new[] { OpenIddictConstants.Permissions.GrantTypes.Implicit }, + "The client application is not allowed to use the implicit flow.")] + [InlineData( + "token", + new[] { OpenIddictConstants.Permissions.GrantTypes.Implicit }, + "The client application is not allowed to use the implicit flow.")] + public async Task ValidateAuthorizationRequest_RequestIsRejectedWhenGrantTypePermissionIsNotGranted( + string type, string[] permissions, string description) { // Arrange var application = new OpenIddictApplication(); @@ -398,11 +469,15 @@ namespace OpenIddict.Tests instance.Setup(mock => mock.FindByClientIdAsync("Fabrikam", It.IsAny())) .ReturnsAsync(application); - instance.Setup(mock => mock.ValidateRedirectUriAsync(application, "http://www.fabrikam.com/path", It.IsAny())) + instance.Setup(mock => mock.HasPermissionAsync(application, + OpenIddictConstants.Permissions.Endpoints.Authorization, It.IsAny())) .ReturnsAsync(true); - instance.Setup(mock => mock.GetClientTypeAsync(application, It.IsAny())) - .ReturnsAsync(OpenIddictConstants.ClientTypes.Confidential); + foreach (var permission in permissions) + { + instance.Setup(mock => mock.HasPermissionAsync(application, permission, It.IsAny())) + .ReturnsAsync(false); + } }); var server = CreateAuthorizationServer(builder => @@ -422,13 +497,64 @@ namespace OpenIddict.Tests Scope = OpenIdConnectConstants.Scopes.OpenId }); + // Assert + Assert.Equal(OpenIdConnectConstants.Errors.UnauthorizedClient, response.Error); + Assert.Equal(description, response.ErrorDescription); + + Mock.Get(manager).Verify(mock => mock.FindByClientIdAsync("Fabrikam", It.IsAny()), Times.Once()); + Mock.Get(manager).Verify(mock => mock.HasPermissionAsync(application, + OpenIddictConstants.Permissions.Endpoints.Authorization, It.IsAny()), Times.Once()); + Mock.Get(manager).Verify(mock => mock.HasPermissionAsync(application, permissions[0], It.IsAny()), Times.Once()); + } + + [Fact] + public async Task ValidateAuthorizationRequest_RequestIsRejectedWhenRedirectUriIsInvalid() + { + // Arrange + var application = new OpenIddictApplication(); + + var manager = CreateApplicationManager(instance => + { + instance.Setup(mock => mock.FindByClientIdAsync("Fabrikam", It.IsAny())) + .ReturnsAsync(application); + + instance.Setup(mock => mock.HasPermissionAsync(application, + OpenIddictConstants.Permissions.Endpoints.Authorization, It.IsAny())) + .ReturnsAsync(true); + + instance.Setup(mock => mock.HasPermissionAsync(application, + OpenIddictConstants.Permissions.GrantTypes.AuthorizationCode, It.IsAny())) + .ReturnsAsync(true); + + instance.Setup(mock => mock.ValidateRedirectUriAsync(application, "http://www.fabrikam.com/path", It.IsAny())) + .ReturnsAsync(false); + }); + + var server = CreateAuthorizationServer(builder => + { + builder.Services.AddSingleton(manager); + }); + + var client = new OpenIdConnectClient(server.CreateClient()); + + // Act + var response = await client.PostAsync(AuthorizationEndpoint, new OpenIdConnectRequest + { + ClientId = "Fabrikam", + RedirectUri = "http://www.fabrikam.com/path", + ResponseType = OpenIdConnectConstants.ResponseTypes.Code + }); + // Assert Assert.Equal(OpenIdConnectConstants.Errors.InvalidRequest, response.Error); - Assert.Equal("The specified 'response_type' parameter is not valid for this client application.", response.ErrorDescription); + Assert.Equal("The specified 'redirect_uri' parameter is not valid for this client application.", response.ErrorDescription); Mock.Get(manager).Verify(mock => mock.FindByClientIdAsync("Fabrikam", It.IsAny()), Times.Once()); + Mock.Get(manager).Verify(mock => mock.HasPermissionAsync(application, + OpenIddictConstants.Permissions.Endpoints.Authorization, It.IsAny()), Times.Once()); + Mock.Get(manager).Verify(mock => mock.HasPermissionAsync(application, + OpenIddictConstants.Permissions.GrantTypes.AuthorizationCode, It.IsAny()), Times.Once()); Mock.Get(manager).Verify(mock => mock.ValidateRedirectUriAsync(application, "http://www.fabrikam.com/path", It.IsAny()), Times.Once()); - Mock.Get(manager).Verify(mock => mock.GetClientTypeAsync(application, It.IsAny()), Times.Once()); } [Fact] @@ -447,6 +573,14 @@ namespace OpenIddict.Tests instance.Setup(mock => mock.FindByClientIdAsync("Fabrikam", It.IsAny())) .ReturnsAsync(application); + instance.Setup(mock => mock.HasPermissionAsync(application, + OpenIddictConstants.Permissions.Endpoints.Authorization, It.IsAny())) + .ReturnsAsync(true); + + instance.Setup(mock => mock.HasPermissionAsync(application, + OpenIddictConstants.Permissions.GrantTypes.Implicit, It.IsAny())) + .ReturnsAsync(true); + instance.Setup(mock => mock.ValidateRedirectUriAsync(application, "http://www.fabrikam.com/path", It.IsAny())) .ReturnsAsync(true); @@ -506,6 +640,18 @@ namespace OpenIddict.Tests instance.Setup(mock => mock.FindByClientIdAsync("Fabrikam", It.IsAny())) .ReturnsAsync(application); + instance.Setup(mock => mock.HasPermissionAsync(application, + OpenIddictConstants.Permissions.Endpoints.Authorization, It.IsAny())) + .ReturnsAsync(true); + + instance.Setup(mock => mock.HasPermissionAsync(application, + OpenIddictConstants.Permissions.GrantTypes.AuthorizationCode, It.IsAny())) + .ReturnsAsync(true); + + instance.Setup(mock => mock.HasPermissionAsync(application, + OpenIddictConstants.Permissions.GrantTypes.Implicit, It.IsAny())) + .ReturnsAsync(true); + instance.Setup(mock => mock.ValidateRedirectUriAsync(application, "http://www.fabrikam.com/path", It.IsAny())) .ReturnsAsync(true); @@ -582,6 +728,14 @@ namespace OpenIddict.Tests instance.Setup(mock => mock.FindByClientIdAsync("Fabrikam", It.IsAny())) .ReturnsAsync(application); + instance.Setup(mock => mock.HasPermissionAsync(application, + OpenIddictConstants.Permissions.Endpoints.Authorization, It.IsAny())) + .ReturnsAsync(true); + + instance.Setup(mock => mock.HasPermissionAsync(application, + OpenIddictConstants.Permissions.GrantTypes.Implicit, It.IsAny())) + .ReturnsAsync(true); + instance.Setup(mock => mock.ValidateRedirectUriAsync(application, "http://www.fabrikam.com/path", It.IsAny())) .ReturnsAsync(true); @@ -616,20 +770,6 @@ namespace OpenIddict.Tests // Arrange var server = CreateAuthorizationServer(builder => { - builder.Services.AddSingleton(CreateApplicationManager(instance => - { - var application = new OpenIddictApplication(); - - instance.Setup(mock => mock.FindByClientIdAsync("Fabrikam", It.IsAny())) - .ReturnsAsync(application); - - instance.Setup(mock => mock.ValidateRedirectUriAsync(application, "http://www.fabrikam.com/path", It.IsAny())) - .ReturnsAsync(true); - - instance.Setup(mock => mock.GetClientTypeAsync(application, It.IsAny())) - .ReturnsAsync(OpenIddictConstants.ClientTypes.Public); - })); - builder.EnableAuthorizationEndpoint("/authorize-status-code-middleware"); }); diff --git a/test/OpenIddict.Tests/OpenIddictProviderTests.Exchange.cs b/test/OpenIddict.Tests/OpenIddictProviderTests.Exchange.cs index 96d1baea..b2c0753c 100644 --- a/test/OpenIddict.Tests/OpenIddictProviderTests.Exchange.cs +++ b/test/OpenIddict.Tests/OpenIddictProviderTests.Exchange.cs @@ -200,6 +200,94 @@ namespace OpenIddict.Tests Mock.Get(manager).Verify(mock => mock.FindByClientIdAsync("Fabrikam", It.IsAny()), Times.Once()); } + [Fact] + public async Task ValidateTokenRequest_RequestIsRejectedWhenEndpointPermissionIsNotGranted() + { + // Arrange + var application = new OpenIddictApplication(); + + var manager = CreateApplicationManager(instance => + { + instance.Setup(mock => mock.FindByClientIdAsync("Fabrikam", It.IsAny())) + .ReturnsAsync(application); + + instance.Setup(mock => mock.HasPermissionAsync(application, + OpenIddictConstants.Permissions.Endpoints.Token, It.IsAny())) + .ReturnsAsync(false); + }); + + var server = CreateAuthorizationServer(builder => + { + builder.Services.AddSingleton(manager); + }); + + var client = new OpenIdConnectClient(server.CreateClient()); + + // Act + var response = await client.PostAsync(TokenEndpoint, new OpenIdConnectRequest + { + ClientId = "Fabrikam", + GrantType = OpenIdConnectConstants.GrantTypes.Password, + Username = "johndoe", + Password = "A3ddj3w" + }); + + // Assert + Assert.Equal(OpenIdConnectConstants.Errors.UnauthorizedClient, response.Error); + Assert.Equal("This client application is not allowed to use the token endpoint.", response.ErrorDescription); + + Mock.Get(manager).Verify(mock => mock.FindByClientIdAsync("Fabrikam", It.IsAny()), Times.Once()); + Mock.Get(manager).Verify(mock => mock.HasPermissionAsync(application, + OpenIddictConstants.Permissions.Endpoints.Token, It.IsAny()), Times.Once()); + } + + [Fact] + public async Task ValidateTokenRequest_RequestIsRejectedWhenGrantTypePermissionIsNotGranted() + { + // Arrange + var application = new OpenIddictApplication(); + + var manager = CreateApplicationManager(instance => + { + instance.Setup(mock => mock.FindByClientIdAsync("Fabrikam", It.IsAny())) + .ReturnsAsync(application); + + instance.Setup(mock => mock.HasPermissionAsync(application, + OpenIddictConstants.Permissions.Endpoints.Token, It.IsAny())) + .ReturnsAsync(true); + + instance.Setup(mock => mock.HasPermissionAsync(application, + OpenIddictConstants.Permissions.GrantTypes.Password, It.IsAny())) + .ReturnsAsync(false); + }); + + var server = CreateAuthorizationServer(builder => + { + builder.Services.AddSingleton(manager); + }); + + var client = new OpenIdConnectClient(server.CreateClient()); + + // Act + var response = await client.PostAsync(TokenEndpoint, new OpenIdConnectRequest + { + ClientId = "Fabrikam", + GrantType = OpenIdConnectConstants.GrantTypes.Password, + Username = "johndoe", + Password = "A3ddj3w" + }); + + // Assert + Assert.Equal(OpenIdConnectConstants.Errors.UnauthorizedClient, response.Error); + Assert.Equal("This client application is not allowed to use the specified grant type.", response.ErrorDescription); + + Mock.Get(manager).Verify(mock => mock.FindByClientIdAsync("Fabrikam", It.IsAny()), Times.Once()); + Mock.Get(manager).Verify(mock => mock.HasPermissionAsync(application, + OpenIddictConstants.Permissions.Endpoints.Token, It.IsAny()), Times.Once()); + Mock.Get(manager).Verify(mock => mock.HasPermissionAsync(application, + OpenIddictConstants.Permissions.GrantTypes.Password, It.IsAny()), Times.Once()); + } + [Fact] public async Task ValidateTokenRequest_ClientCredentialsRequestFromPublicClientIsRejected() { @@ -211,6 +299,14 @@ namespace OpenIddict.Tests instance.Setup(mock => mock.FindByClientIdAsync("Fabrikam", It.IsAny())) .ReturnsAsync(application); + instance.Setup(mock => mock.HasPermissionAsync(application, + OpenIddictConstants.Permissions.Endpoints.Token, It.IsAny())) + .ReturnsAsync(true); + + instance.Setup(mock => mock.HasPermissionAsync(application, + OpenIddictConstants.Permissions.GrantTypes.ClientCredentials, It.IsAny())) + .ReturnsAsync(true); + instance.Setup(mock => mock.GetClientTypeAsync(application, It.IsAny())) .ReturnsAsync(OpenIddictConstants.ClientTypes.Public); }); @@ -235,6 +331,10 @@ namespace OpenIddict.Tests Assert.Equal("The specified 'grant_type' parameter is not valid for this client application.", response.ErrorDescription); Mock.Get(manager).Verify(mock => mock.FindByClientIdAsync("Fabrikam", It.IsAny()), Times.Once()); + Mock.Get(manager).Verify(mock => mock.HasPermissionAsync(application, + OpenIddictConstants.Permissions.Endpoints.Token, It.IsAny()), Times.Once()); + Mock.Get(manager).Verify(mock => mock.HasPermissionAsync(application, + OpenIddictConstants.Permissions.GrantTypes.ClientCredentials, It.IsAny()), Times.Once()); Mock.Get(manager).Verify(mock => mock.GetClientTypeAsync(application, It.IsAny()), Times.Once()); } @@ -249,6 +349,14 @@ namespace OpenIddict.Tests instance.Setup(mock => mock.FindByClientIdAsync("Fabrikam", It.IsAny())) .ReturnsAsync(application); + instance.Setup(mock => mock.HasPermissionAsync(application, + OpenIddictConstants.Permissions.Endpoints.Token, It.IsAny())) + .ReturnsAsync(true); + + instance.Setup(mock => mock.HasPermissionAsync(application, + OpenIddictConstants.Permissions.GrantTypes.Password, It.IsAny())) + .ReturnsAsync(true); + instance.Setup(mock => mock.GetClientTypeAsync(application, It.IsAny())) .ReturnsAsync(OpenIddictConstants.ClientTypes.Public); }); @@ -275,6 +383,10 @@ namespace OpenIddict.Tests Assert.Equal("The 'client_secret' parameter is not valid for this client application.", response.ErrorDescription); Mock.Get(manager).Verify(mock => mock.FindByClientIdAsync("Fabrikam", It.IsAny()), Times.Once()); + Mock.Get(manager).Verify(mock => mock.HasPermissionAsync(application, + OpenIddictConstants.Permissions.Endpoints.Token, It.IsAny()), Times.Once()); + Mock.Get(manager).Verify(mock => mock.HasPermissionAsync(application, + OpenIddictConstants.Permissions.GrantTypes.Password, It.IsAny()), Times.Once()); Mock.Get(manager).Verify(mock => mock.GetClientTypeAsync(application, It.IsAny()), Times.Once()); } @@ -289,6 +401,14 @@ namespace OpenIddict.Tests instance.Setup(mock => mock.FindByClientIdAsync("Fabrikam", It.IsAny())) .ReturnsAsync(application); + instance.Setup(mock => mock.HasPermissionAsync(application, + OpenIddictConstants.Permissions.Endpoints.Token, It.IsAny())) + .ReturnsAsync(true); + + instance.Setup(mock => mock.HasPermissionAsync(application, + OpenIddictConstants.Permissions.GrantTypes.Password, It.IsAny())) + .ReturnsAsync(true); + instance.Setup(mock => mock.GetClientTypeAsync(application, It.IsAny())) .ReturnsAsync(OpenIddictConstants.ClientTypes.Confidential); }); @@ -315,6 +435,10 @@ namespace OpenIddict.Tests Assert.Equal("The 'client_secret' parameter required for this client application is missing.", response.ErrorDescription); Mock.Get(manager).Verify(mock => mock.FindByClientIdAsync("Fabrikam", It.IsAny()), Times.Once()); + Mock.Get(manager).Verify(mock => mock.HasPermissionAsync(application, + OpenIddictConstants.Permissions.Endpoints.Token, It.IsAny()), Times.Once()); + Mock.Get(manager).Verify(mock => mock.HasPermissionAsync(application, + OpenIddictConstants.Permissions.GrantTypes.Password, It.IsAny()), Times.Once()); Mock.Get(manager).Verify(mock => mock.GetClientTypeAsync(application, It.IsAny()), Times.Once()); } @@ -329,6 +453,14 @@ namespace OpenIddict.Tests instance.Setup(mock => mock.FindByClientIdAsync("Fabrikam", It.IsAny())) .ReturnsAsync(application); + instance.Setup(mock => mock.HasPermissionAsync(application, + OpenIddictConstants.Permissions.Endpoints.Token, It.IsAny())) + .ReturnsAsync(true); + + instance.Setup(mock => mock.HasPermissionAsync(application, + OpenIddictConstants.Permissions.GrantTypes.Password, It.IsAny())) + .ReturnsAsync(true); + instance.Setup(mock => mock.GetClientTypeAsync(application, It.IsAny())) .ReturnsAsync(OpenIddictConstants.ClientTypes.Hybrid); }); @@ -355,6 +487,10 @@ namespace OpenIddict.Tests Assert.Equal("The 'client_secret' parameter required for this client application is missing.", response.ErrorDescription); Mock.Get(manager).Verify(mock => mock.FindByClientIdAsync("Fabrikam", It.IsAny()), Times.Once()); + Mock.Get(manager).Verify(mock => mock.HasPermissionAsync(application, + OpenIddictConstants.Permissions.Endpoints.Token, It.IsAny()), Times.Once()); + Mock.Get(manager).Verify(mock => mock.HasPermissionAsync(application, + OpenIddictConstants.Permissions.GrantTypes.Password, It.IsAny()), Times.Once()); Mock.Get(manager).Verify(mock => mock.GetClientTypeAsync(application, It.IsAny()), Times.Once()); } @@ -369,6 +505,14 @@ namespace OpenIddict.Tests instance.Setup(mock => mock.FindByClientIdAsync("Fabrikam", It.IsAny())) .ReturnsAsync(application); + instance.Setup(mock => mock.HasPermissionAsync(application, + OpenIddictConstants.Permissions.Endpoints.Token, It.IsAny())) + .ReturnsAsync(true); + + instance.Setup(mock => mock.HasPermissionAsync(application, + OpenIddictConstants.Permissions.GrantTypes.Password, It.IsAny())) + .ReturnsAsync(true); + instance.Setup(mock => mock.GetClientTypeAsync(application, It.IsAny())) .ReturnsAsync(OpenIddictConstants.ClientTypes.Confidential); @@ -398,6 +542,10 @@ namespace OpenIddict.Tests Assert.Equal("The specified client credentials are invalid.", response.ErrorDescription); Mock.Get(manager).Verify(mock => mock.FindByClientIdAsync("Fabrikam", It.IsAny()), Times.Once()); + Mock.Get(manager).Verify(mock => mock.HasPermissionAsync(application, + OpenIddictConstants.Permissions.Endpoints.Token, It.IsAny()), Times.Once()); + Mock.Get(manager).Verify(mock => mock.HasPermissionAsync(application, + OpenIddictConstants.Permissions.GrantTypes.Password, It.IsAny()), Times.Once()); Mock.Get(manager).Verify(mock => mock.GetClientTypeAsync(application, It.IsAny()), Times.Once()); Mock.Get(manager).Verify(mock => mock.ValidateClientSecretAsync(application, "7Fjfp0ZBr1KtDRbnfVdmIw", It.IsAny()), Times.Once()); } @@ -429,6 +577,14 @@ namespace OpenIddict.Tests instance.Setup(mock => mock.FindByClientIdAsync("Fabrikam", It.IsAny())) .ReturnsAsync(application); + instance.Setup(mock => mock.HasPermissionAsync(application, + OpenIddictConstants.Permissions.Endpoints.Token, It.IsAny())) + .ReturnsAsync(true); + + instance.Setup(mock => mock.HasPermissionAsync(application, + OpenIddictConstants.Permissions.GrantTypes.AuthorizationCode, It.IsAny())) + .ReturnsAsync(true); + instance.Setup(mock => mock.GetClientTypeAsync(application, It.IsAny())) .ReturnsAsync(OpenIddictConstants.ClientTypes.Public); })); @@ -481,6 +637,14 @@ namespace OpenIddict.Tests instance.Setup(mock => mock.FindByClientIdAsync("Fabrikam", It.IsAny())) .ReturnsAsync(application); + instance.Setup(mock => mock.HasPermissionAsync(application, + OpenIddictConstants.Permissions.Endpoints.Token, It.IsAny())) + .ReturnsAsync(true); + + instance.Setup(mock => mock.HasPermissionAsync(application, + OpenIddictConstants.Permissions.GrantTypes.RefreshToken, It.IsAny())) + .ReturnsAsync(true); + instance.Setup(mock => mock.GetClientTypeAsync(application, It.IsAny())) .ReturnsAsync(OpenIddictConstants.ClientTypes.Public); })); @@ -538,6 +702,14 @@ namespace OpenIddict.Tests instance.Setup(mock => mock.FindByClientIdAsync("Fabrikam", It.IsAny())) .ReturnsAsync(application); + instance.Setup(mock => mock.HasPermissionAsync(application, + OpenIddictConstants.Permissions.Endpoints.Token, It.IsAny())) + .ReturnsAsync(true); + + instance.Setup(mock => mock.HasPermissionAsync(application, + OpenIddictConstants.Permissions.GrantTypes.AuthorizationCode, It.IsAny())) + .ReturnsAsync(true); + instance.Setup(mock => mock.GetClientTypeAsync(application, It.IsAny())) .ReturnsAsync(OpenIddictConstants.ClientTypes.Public); })); @@ -597,6 +769,14 @@ namespace OpenIddict.Tests instance.Setup(mock => mock.FindByClientIdAsync("Fabrikam", It.IsAny())) .ReturnsAsync(application); + instance.Setup(mock => mock.HasPermissionAsync(application, + OpenIddictConstants.Permissions.Endpoints.Token, It.IsAny())) + .ReturnsAsync(true); + + instance.Setup(mock => mock.HasPermissionAsync(application, + OpenIddictConstants.Permissions.GrantTypes.RefreshToken, It.IsAny())) + .ReturnsAsync(true); + instance.Setup(mock => mock.GetClientTypeAsync(application, It.IsAny())) .ReturnsAsync(OpenIddictConstants.ClientTypes.Public); })); @@ -663,6 +843,14 @@ namespace OpenIddict.Tests instance.Setup(mock => mock.FindByClientIdAsync("Fabrikam", It.IsAny())) .ReturnsAsync(application); + instance.Setup(mock => mock.HasPermissionAsync(application, + OpenIddictConstants.Permissions.Endpoints.Token, It.IsAny())) + .ReturnsAsync(true); + + instance.Setup(mock => mock.HasPermissionAsync(application, + OpenIddictConstants.Permissions.GrantTypes.AuthorizationCode, It.IsAny())) + .ReturnsAsync(true); + instance.Setup(mock => mock.GetClientTypeAsync(application, It.IsAny())) .ReturnsAsync(OpenIddictConstants.ClientTypes.Public); })); @@ -731,6 +919,14 @@ namespace OpenIddict.Tests instance.Setup(mock => mock.FindByClientIdAsync("Fabrikam", It.IsAny())) .ReturnsAsync(application); + instance.Setup(mock => mock.HasPermissionAsync(application, + OpenIddictConstants.Permissions.Endpoints.Token, It.IsAny())) + .ReturnsAsync(true); + + instance.Setup(mock => mock.HasPermissionAsync(application, + OpenIddictConstants.Permissions.GrantTypes.RefreshToken, It.IsAny())) + .ReturnsAsync(true); + instance.Setup(mock => mock.GetClientTypeAsync(application, It.IsAny())) .ReturnsAsync(OpenIddictConstants.ClientTypes.Public); })); @@ -793,6 +989,14 @@ namespace OpenIddict.Tests instance.Setup(mock => mock.FindByClientIdAsync("Fabrikam", It.IsAny())) .ReturnsAsync(application); + instance.Setup(mock => mock.HasPermissionAsync(application, + OpenIddictConstants.Permissions.Endpoints.Token, It.IsAny())) + .ReturnsAsync(true); + + instance.Setup(mock => mock.HasPermissionAsync(application, + OpenIddictConstants.Permissions.GrantTypes.AuthorizationCode, It.IsAny())) + .ReturnsAsync(true); + instance.Setup(mock => mock.GetClientTypeAsync(application, It.IsAny())) .ReturnsAsync(OpenIddictConstants.ClientTypes.Public); })); @@ -876,6 +1080,14 @@ namespace OpenIddict.Tests instance.Setup(mock => mock.FindByClientIdAsync("Fabrikam", It.IsAny())) .ReturnsAsync(application); + instance.Setup(mock => mock.HasPermissionAsync(application, + OpenIddictConstants.Permissions.Endpoints.Token, It.IsAny())) + .ReturnsAsync(true); + + instance.Setup(mock => mock.HasPermissionAsync(application, + OpenIddictConstants.Permissions.GrantTypes.RefreshToken, It.IsAny())) + .ReturnsAsync(true); + instance.Setup(mock => mock.GetClientTypeAsync(application, It.IsAny())) .ReturnsAsync(OpenIddictConstants.ClientTypes.Public); })); @@ -973,6 +1185,14 @@ namespace OpenIddict.Tests instance.Setup(mock => mock.FindByClientIdAsync("Fabrikam", It.IsAny())) .ReturnsAsync(application); + instance.Setup(mock => mock.HasPermissionAsync(application, + OpenIddictConstants.Permissions.Endpoints.Token, It.IsAny())) + .ReturnsAsync(true); + + instance.Setup(mock => mock.HasPermissionAsync(application, + OpenIddictConstants.Permissions.GrantTypes.AuthorizationCode, It.IsAny())) + .ReturnsAsync(true); + instance.Setup(mock => mock.GetClientTypeAsync(application, It.IsAny())) .ReturnsAsync(OpenIddictConstants.ClientTypes.Public); })); @@ -1062,6 +1282,14 @@ namespace OpenIddict.Tests instance.Setup(mock => mock.FindByClientIdAsync("Fabrikam", It.IsAny())) .ReturnsAsync(application); + instance.Setup(mock => mock.HasPermissionAsync(application, + OpenIddictConstants.Permissions.Endpoints.Token, It.IsAny())) + .ReturnsAsync(true); + + instance.Setup(mock => mock.HasPermissionAsync(application, + OpenIddictConstants.Permissions.GrantTypes.RefreshToken, It.IsAny())) + .ReturnsAsync(true); + instance.Setup(mock => mock.GetClientTypeAsync(application, It.IsAny())) .ReturnsAsync(OpenIddictConstants.ClientTypes.Public); })); @@ -1143,6 +1371,14 @@ namespace OpenIddict.Tests instance.Setup(mock => mock.FindByClientIdAsync("Fabrikam", It.IsAny())) .ReturnsAsync(application); + instance.Setup(mock => mock.HasPermissionAsync(application, + OpenIddictConstants.Permissions.Endpoints.Token, It.IsAny())) + .ReturnsAsync(true); + + instance.Setup(mock => mock.HasPermissionAsync(application, + OpenIddictConstants.Permissions.GrantTypes.AuthorizationCode, It.IsAny())) + .ReturnsAsync(true); + instance.Setup(mock => mock.GetClientTypeAsync(application, It.IsAny())) .ReturnsAsync(OpenIddictConstants.ClientTypes.Public); })); @@ -1215,6 +1451,14 @@ namespace OpenIddict.Tests instance.Setup(mock => mock.FindByClientIdAsync("Fabrikam", It.IsAny())) .ReturnsAsync(application); + instance.Setup(mock => mock.HasPermissionAsync(application, + OpenIddictConstants.Permissions.Endpoints.Token, It.IsAny())) + .ReturnsAsync(true); + + instance.Setup(mock => mock.HasPermissionAsync(application, + OpenIddictConstants.Permissions.GrantTypes.RefreshToken, It.IsAny())) + .ReturnsAsync(true); + instance.Setup(mock => mock.GetClientTypeAsync(application, It.IsAny())) .ReturnsAsync(OpenIddictConstants.ClientTypes.Public); })); @@ -1300,6 +1544,30 @@ namespace OpenIddict.Tests instance.Setup(mock => mock.FindByClientIdAsync("Fabrikam", It.IsAny())) .ReturnsAsync(application); + instance.Setup(mock => mock.HasPermissionAsync(application, + OpenIddictConstants.Permissions.Endpoints.Token, It.IsAny())) + .ReturnsAsync(true); + + instance.Setup(mock => mock.HasPermissionAsync(application, + OpenIddictConstants.Permissions.GrantTypes.AuthorizationCode, It.IsAny())) + .ReturnsAsync(true); + + instance.Setup(mock => mock.HasPermissionAsync(application, + OpenIddictConstants.Permissions.GrantTypes.ClientCredentials, It.IsAny())) + .ReturnsAsync(true); + + instance.Setup(mock => mock.HasPermissionAsync(application, + OpenIddictConstants.Permissions.GrantTypes.Password, It.IsAny())) + .ReturnsAsync(true); + + instance.Setup(mock => mock.HasPermissionAsync(application, + OpenIddictConstants.Permissions.GrantTypes.RefreshToken, It.IsAny())) + .ReturnsAsync(true); + + instance.Setup(mock => mock.HasPermissionAsync(application, + "gt:urn:ietf:params:oauth:grant-type:custom_grant", It.IsAny())) + .ReturnsAsync(true); + instance.Setup(mock => mock.GetClientTypeAsync(application, It.IsAny())) .ReturnsAsync(OpenIddictConstants.ClientTypes.Confidential); diff --git a/test/OpenIddict.Tests/OpenIddictProviderTests.Introspection.cs b/test/OpenIddict.Tests/OpenIddictProviderTests.Introspection.cs index cb9c5f3d..a1150489 100644 --- a/test/OpenIddict.Tests/OpenIddictProviderTests.Introspection.cs +++ b/test/OpenIddict.Tests/OpenIddictProviderTests.Introspection.cs @@ -97,6 +97,46 @@ namespace OpenIddict.Tests Mock.Get(manager).Verify(mock => mock.FindByClientIdAsync("Fabrikam", It.IsAny()), Times.Once()); } + [Fact] + public async Task ValidateIntrospectionRequest_RequestIsRejectedWhenEndpointPermissionIsNotGranted() + { + // Arrange + var application = new OpenIddictApplication(); + + var manager = CreateApplicationManager(instance => + { + instance.Setup(mock => mock.FindByClientIdAsync("Fabrikam", It.IsAny())) + .ReturnsAsync(application); + + instance.Setup(mock => mock.HasPermissionAsync(application, + OpenIddictConstants.Permissions.Endpoints.Introspection, It.IsAny())) + .ReturnsAsync(false); + }); + + var server = CreateAuthorizationServer(builder => + { + builder.Services.AddSingleton(manager); + }); + + var client = new OpenIdConnectClient(server.CreateClient()); + + // Act + var response = await client.PostAsync(IntrospectionEndpoint, new OpenIdConnectRequest + { + ClientId = "Fabrikam", + ClientSecret = "7Fjfp0ZBr1KtDRbnfVdmIw", + Token = "2YotnFZFEjr1zCsicMWpAA" + }); + + // Assert + Assert.Equal(OpenIdConnectConstants.Errors.UnauthorizedClient, response.Error); + Assert.Equal("This client application is not allowed to use the introspection endpoint.", response.ErrorDescription); + + Mock.Get(manager).Verify(mock => mock.FindByClientIdAsync("Fabrikam", It.IsAny()), Times.Once()); + Mock.Get(manager).Verify(mock => mock.HasPermissionAsync(application, + OpenIddictConstants.Permissions.Endpoints.Introspection, It.IsAny()), Times.Once()); + } + [Fact] public async Task ValidateIntrospectionRequest_RequestsSentByPublicClientsAreRejected() { @@ -108,6 +148,10 @@ namespace OpenIddict.Tests instance.Setup(mock => mock.FindByClientIdAsync("Fabrikam", It.IsAny())) .ReturnsAsync(application); + instance.Setup(mock => mock.HasPermissionAsync(application, + OpenIddictConstants.Permissions.Endpoints.Introspection, It.IsAny())) + .ReturnsAsync(true); + instance.Setup(mock => mock.GetClientTypeAsync(application, It.IsAny())) .ReturnsAsync(OpenIddictConstants.ClientTypes.Public); }); @@ -132,6 +176,8 @@ namespace OpenIddict.Tests Assert.Equal("This client application is not allowed to use the introspection endpoint.", response.ErrorDescription); Mock.Get(manager).Verify(mock => mock.FindByClientIdAsync("Fabrikam", It.IsAny()), Times.Once()); + Mock.Get(manager).Verify(mock => mock.HasPermissionAsync(application, + OpenIddictConstants.Permissions.Endpoints.Introspection, It.IsAny()), Times.Once()); Mock.Get(manager).Verify(mock => mock.GetClientTypeAsync(application, It.IsAny()), Times.Once()); } @@ -146,6 +192,10 @@ namespace OpenIddict.Tests instance.Setup(mock => mock.FindByClientIdAsync("Fabrikam", It.IsAny())) .ReturnsAsync(application); + instance.Setup(mock => mock.HasPermissionAsync(application, + OpenIddictConstants.Permissions.Endpoints.Introspection, It.IsAny())) + .ReturnsAsync(true); + instance.Setup(mock => mock.GetClientTypeAsync(application, It.IsAny())) .ReturnsAsync(OpenIddictConstants.ClientTypes.Confidential); @@ -173,6 +223,8 @@ namespace OpenIddict.Tests Assert.Equal("The specified client credentials are invalid.", response.ErrorDescription); Mock.Get(manager).Verify(mock => mock.FindByClientIdAsync("Fabrikam", It.IsAny()), Times.Once()); + Mock.Get(manager).Verify(mock => mock.HasPermissionAsync(application, + OpenIddictConstants.Permissions.Endpoints.Introspection, It.IsAny()), Times.Once()); Mock.Get(manager).Verify(mock => mock.GetClientTypeAsync(application, It.IsAny()), Times.Once()); Mock.Get(manager).Verify(mock => mock.ValidateClientSecretAsync(application, "7Fjfp0ZBr1KtDRbnfVdmIw", It.IsAny()), Times.Once()); } @@ -207,6 +259,10 @@ namespace OpenIddict.Tests instance.Setup(mock => mock.FindByClientIdAsync("Fabrikam", It.IsAny())) .ReturnsAsync(application); + instance.Setup(mock => mock.HasPermissionAsync(application, + OpenIddictConstants.Permissions.Endpoints.Introspection, It.IsAny())) + .ReturnsAsync(true); + instance.Setup(mock => mock.GetClientTypeAsync(application, It.IsAny())) .ReturnsAsync(OpenIddictConstants.ClientTypes.Confidential); @@ -261,6 +317,10 @@ namespace OpenIddict.Tests instance.Setup(mock => mock.FindByClientIdAsync("Fabrikam", It.IsAny())) .ReturnsAsync(application); + instance.Setup(mock => mock.HasPermissionAsync(application, + OpenIddictConstants.Permissions.Endpoints.Introspection, It.IsAny())) + .ReturnsAsync(true); + instance.Setup(mock => mock.GetClientTypeAsync(application, It.IsAny())) .ReturnsAsync(OpenIddictConstants.ClientTypes.Confidential); @@ -318,6 +378,10 @@ namespace OpenIddict.Tests instance.Setup(mock => mock.FindByClientIdAsync("Fabrikam", It.IsAny())) .ReturnsAsync(application); + instance.Setup(mock => mock.HasPermissionAsync(application, + OpenIddictConstants.Permissions.Endpoints.Introspection, It.IsAny())) + .ReturnsAsync(true); + instance.Setup(mock => mock.GetClientTypeAsync(application, It.IsAny())) .ReturnsAsync(OpenIddictConstants.ClientTypes.Confidential); @@ -368,6 +432,10 @@ namespace OpenIddict.Tests instance.Setup(mock => mock.FindByClientIdAsync("Fabrikam", It.IsAny())) .ReturnsAsync(application); + instance.Setup(mock => mock.HasPermissionAsync(application, + OpenIddictConstants.Permissions.Endpoints.Introspection, It.IsAny())) + .ReturnsAsync(true); + instance.Setup(mock => mock.GetClientTypeAsync(application, It.IsAny())) .ReturnsAsync(OpenIddictConstants.ClientTypes.Confidential); @@ -447,6 +515,10 @@ namespace OpenIddict.Tests instance.Setup(mock => mock.FindByClientIdAsync("Fabrikam", It.IsAny())) .ReturnsAsync(application); + instance.Setup(mock => mock.HasPermissionAsync(application, + OpenIddictConstants.Permissions.Endpoints.Introspection, It.IsAny())) + .ReturnsAsync(true); + instance.Setup(mock => mock.GetClientTypeAsync(application, It.IsAny())) .ReturnsAsync(OpenIddictConstants.ClientTypes.Confidential); @@ -514,6 +586,10 @@ namespace OpenIddict.Tests instance.Setup(mock => mock.FindByClientIdAsync("Fabrikam", It.IsAny())) .ReturnsAsync(application); + instance.Setup(mock => mock.HasPermissionAsync(application, + OpenIddictConstants.Permissions.Endpoints.Introspection, It.IsAny())) + .ReturnsAsync(true); + instance.Setup(mock => mock.GetClientTypeAsync(application, It.IsAny())) .ReturnsAsync(OpenIddictConstants.ClientTypes.Confidential); @@ -586,6 +662,10 @@ namespace OpenIddict.Tests instance.Setup(mock => mock.FindByClientIdAsync("Fabrikam", It.IsAny())) .ReturnsAsync(application); + instance.Setup(mock => mock.HasPermissionAsync(application, + OpenIddictConstants.Permissions.Endpoints.Introspection, It.IsAny())) + .ReturnsAsync(true); + instance.Setup(mock => mock.GetClientTypeAsync(application, It.IsAny())) .ReturnsAsync(OpenIddictConstants.ClientTypes.Confidential); @@ -651,6 +731,10 @@ namespace OpenIddict.Tests instance.Setup(mock => mock.FindByClientIdAsync("Fabrikam", It.IsAny())) .ReturnsAsync(application); + instance.Setup(mock => mock.HasPermissionAsync(application, + OpenIddictConstants.Permissions.Endpoints.Introspection, It.IsAny())) + .ReturnsAsync(true); + instance.Setup(mock => mock.GetClientTypeAsync(application, It.IsAny())) .ReturnsAsync(OpenIddictConstants.ClientTypes.Confidential); @@ -726,6 +810,10 @@ namespace OpenIddict.Tests instance.Setup(mock => mock.FindByClientIdAsync("Fabrikam", It.IsAny())) .ReturnsAsync(application); + instance.Setup(mock => mock.HasPermissionAsync(application, + OpenIddictConstants.Permissions.Endpoints.Introspection, It.IsAny())) + .ReturnsAsync(true); + instance.Setup(mock => mock.GetClientTypeAsync(application, It.IsAny())) .ReturnsAsync(OpenIddictConstants.ClientTypes.Confidential); diff --git a/test/OpenIddict.Tests/OpenIddictProviderTests.Revocation.cs b/test/OpenIddict.Tests/OpenIddictProviderTests.Revocation.cs index ec0a7cfb..e55604a3 100644 --- a/test/OpenIddict.Tests/OpenIddictProviderTests.Revocation.cs +++ b/test/OpenIddict.Tests/OpenIddictProviderTests.Revocation.cs @@ -99,6 +99,47 @@ namespace OpenIddict.Tests Mock.Get(manager).Verify(mock => mock.FindByClientIdAsync("Fabrikam", It.IsAny()), Times.Once()); } + [Fact] + public async Task ValidateRevocationRequest_RequestIsRejectedWhenEndpointPermissionIsNotGranted() + { + // Arrange + var application = new OpenIddictApplication(); + + var manager = CreateApplicationManager(instance => + { + instance.Setup(mock => mock.FindByClientIdAsync("Fabrikam", It.IsAny())) + .ReturnsAsync(application); + + instance.Setup(mock => mock.HasPermissionAsync(application, + OpenIddictConstants.Permissions.Endpoints.Revocation, It.IsAny())) + .ReturnsAsync(false); + }); + + var server = CreateAuthorizationServer(builder => + { + builder.Services.AddSingleton(manager); + }); + + var client = new OpenIdConnectClient(server.CreateClient()); + + // Act + var response = await client.PostAsync(RevocationEndpoint, new OpenIdConnectRequest + { + ClientId = "Fabrikam", + ClientSecret = "7Fjfp0ZBr1KtDRbnfVdmIw", + Token = "SlAV32hkKG", + TokenTypeHint = OpenIdConnectConstants.TokenTypeHints.RefreshToken + }); + + // Assert + Assert.Equal(OpenIdConnectConstants.Errors.UnauthorizedClient, response.Error); + Assert.Equal("This client application is not allowed to use the revocation endpoint.", response.ErrorDescription); + + Mock.Get(manager).Verify(mock => mock.FindByClientIdAsync("Fabrikam", It.IsAny()), Times.Once()); + Mock.Get(manager).Verify(mock => mock.HasPermissionAsync(application, + OpenIddictConstants.Permissions.Endpoints.Revocation, It.IsAny()), Times.Once()); + } + [Fact] public async Task ValidateRevocationRequest_ClientSecretCannotBeUsedByPublicClients() { @@ -110,6 +151,10 @@ namespace OpenIddict.Tests instance.Setup(mock => mock.FindByClientIdAsync("Fabrikam", It.IsAny())) .ReturnsAsync(application); + instance.Setup(mock => mock.HasPermissionAsync(application, + OpenIddictConstants.Permissions.Endpoints.Revocation, It.IsAny())) + .ReturnsAsync(true); + instance.Setup(mock => mock.GetClientTypeAsync(application, It.IsAny())) .ReturnsAsync(OpenIddictConstants.ClientTypes.Public); }); @@ -135,6 +180,8 @@ namespace OpenIddict.Tests Assert.Equal("The 'client_secret' parameter is not valid for this client application.", response.ErrorDescription); Mock.Get(manager).Verify(mock => mock.FindByClientIdAsync("Fabrikam", It.IsAny()), Times.Once()); + Mock.Get(manager).Verify(mock => mock.HasPermissionAsync(application, + OpenIddictConstants.Permissions.Endpoints.Revocation, It.IsAny()), Times.Once()); Mock.Get(manager).Verify(mock => mock.GetClientTypeAsync(application, It.IsAny()), Times.Once()); } @@ -149,6 +196,10 @@ namespace OpenIddict.Tests instance.Setup(mock => mock.FindByClientIdAsync("Fabrikam", It.IsAny())) .ReturnsAsync(application); + instance.Setup(mock => mock.HasPermissionAsync(application, + OpenIddictConstants.Permissions.Endpoints.Revocation, It.IsAny())) + .ReturnsAsync(true); + instance.Setup(mock => mock.GetClientTypeAsync(application, It.IsAny())) .ReturnsAsync(OpenIddictConstants.ClientTypes.Confidential); }); @@ -174,6 +225,8 @@ namespace OpenIddict.Tests Assert.Equal("The 'client_secret' parameter required for this client application is missing.", response.ErrorDescription); Mock.Get(manager).Verify(mock => mock.FindByClientIdAsync("Fabrikam", It.IsAny()), Times.Once()); + Mock.Get(manager).Verify(mock => mock.HasPermissionAsync(application, + OpenIddictConstants.Permissions.Endpoints.Revocation, It.IsAny()), Times.Once()); Mock.Get(manager).Verify(mock => mock.GetClientTypeAsync(application, It.IsAny()), Times.Once()); } @@ -188,6 +241,10 @@ namespace OpenIddict.Tests instance.Setup(mock => mock.FindByClientIdAsync("Fabrikam", It.IsAny())) .ReturnsAsync(application); + instance.Setup(mock => mock.HasPermissionAsync(application, + OpenIddictConstants.Permissions.Endpoints.Revocation, It.IsAny())) + .ReturnsAsync(true); + instance.Setup(mock => mock.GetClientTypeAsync(application, It.IsAny())) .ReturnsAsync(OpenIddictConstants.ClientTypes.Hybrid); }); @@ -213,6 +270,8 @@ namespace OpenIddict.Tests Assert.Equal("The 'client_secret' parameter required for this client application is missing.", response.ErrorDescription); Mock.Get(manager).Verify(mock => mock.FindByClientIdAsync("Fabrikam", It.IsAny()), Times.Once()); + Mock.Get(manager).Verify(mock => mock.HasPermissionAsync(application, + OpenIddictConstants.Permissions.Endpoints.Revocation, It.IsAny()), Times.Once()); Mock.Get(manager).Verify(mock => mock.GetClientTypeAsync(application, It.IsAny()), Times.Once()); } @@ -227,6 +286,10 @@ namespace OpenIddict.Tests instance.Setup(mock => mock.FindByClientIdAsync("Fabrikam", It.IsAny())) .ReturnsAsync(application); + instance.Setup(mock => mock.HasPermissionAsync(application, + OpenIddictConstants.Permissions.Endpoints.Revocation, It.IsAny())) + .ReturnsAsync(true); + instance.Setup(mock => mock.GetClientTypeAsync(application, It.IsAny())) .ReturnsAsync(OpenIddictConstants.ClientTypes.Confidential); diff --git a/test/OpenIddict.Tests/OpenIddictProviderTests.Serialization.cs b/test/OpenIddict.Tests/OpenIddictProviderTests.Serialization.cs index 0740aa22..09ce5023 100644 --- a/test/OpenIddict.Tests/OpenIddictProviderTests.Serialization.cs +++ b/test/OpenIddict.Tests/OpenIddictProviderTests.Serialization.cs @@ -40,6 +40,10 @@ namespace OpenIddict.Tests instance.Setup(mock => mock.FindByClientIdAsync("Fabrikam", It.IsAny())) .ReturnsAsync(application); + instance.Setup(mock => mock.HasPermissionAsync(application, + OpenIddictConstants.Permissions.Endpoints.Introspection, It.IsAny())) + .ReturnsAsync(true); + instance.Setup(mock => mock.GetClientTypeAsync(application, It.IsAny())) .ReturnsAsync(OpenIddictConstants.ClientTypes.Confidential); @@ -113,6 +117,10 @@ namespace OpenIddict.Tests instance.Setup(mock => mock.FindByClientIdAsync("Fabrikam", It.IsAny())) .ReturnsAsync(application); + instance.Setup(mock => mock.HasPermissionAsync(application, + OpenIddictConstants.Permissions.Endpoints.Introspection, It.IsAny())) + .ReturnsAsync(true); + instance.Setup(mock => mock.GetClientTypeAsync(application, It.IsAny())) .ReturnsAsync(OpenIddictConstants.ClientTypes.Confidential); @@ -167,6 +175,10 @@ namespace OpenIddict.Tests instance.Setup(mock => mock.FindByClientIdAsync("Fabrikam", It.IsAny())) .ReturnsAsync(application); + instance.Setup(mock => mock.HasPermissionAsync(application, + OpenIddictConstants.Permissions.Endpoints.Introspection, It.IsAny())) + .ReturnsAsync(true); + instance.Setup(mock => mock.GetClientTypeAsync(application, It.IsAny())) .ReturnsAsync(OpenIddictConstants.ClientTypes.Confidential); @@ -225,6 +237,10 @@ namespace OpenIddict.Tests instance.Setup(mock => mock.FindByClientIdAsync("Fabrikam", It.IsAny())) .ReturnsAsync(application); + instance.Setup(mock => mock.HasPermissionAsync(application, + OpenIddictConstants.Permissions.Endpoints.Introspection, It.IsAny())) + .ReturnsAsync(true); + instance.Setup(mock => mock.GetClientTypeAsync(application, It.IsAny())) .ReturnsAsync(OpenIddictConstants.ClientTypes.Confidential); @@ -288,6 +304,10 @@ namespace OpenIddict.Tests instance.Setup(mock => mock.FindByClientIdAsync("Fabrikam", It.IsAny())) .ReturnsAsync(application); + instance.Setup(mock => mock.HasPermissionAsync(application, + OpenIddictConstants.Permissions.Endpoints.Introspection, It.IsAny())) + .ReturnsAsync(true); + instance.Setup(mock => mock.GetClientTypeAsync(application, It.IsAny())) .ReturnsAsync(OpenIddictConstants.ClientTypes.Confidential); @@ -376,6 +396,10 @@ namespace OpenIddict.Tests instance.Setup(mock => mock.FindByClientIdAsync("Fabrikam", It.IsAny())) .ReturnsAsync(application); + instance.Setup(mock => mock.HasPermissionAsync(application, + OpenIddictConstants.Permissions.Endpoints.Introspection, It.IsAny())) + .ReturnsAsync(true); + instance.Setup(mock => mock.GetClientTypeAsync(application, It.IsAny())) .ReturnsAsync(OpenIddictConstants.ClientTypes.Confidential); @@ -434,6 +458,10 @@ namespace OpenIddict.Tests instance.Setup(mock => mock.FindByClientIdAsync("Fabrikam", It.IsAny())) .ReturnsAsync(application); + instance.Setup(mock => mock.HasPermissionAsync(application, + OpenIddictConstants.Permissions.Endpoints.Introspection, It.IsAny())) + .ReturnsAsync(true); + instance.Setup(mock => mock.GetClientTypeAsync(application, It.IsAny())) .ReturnsAsync(OpenIddictConstants.ClientTypes.Confidential); @@ -509,6 +537,10 @@ namespace OpenIddict.Tests instance.Setup(mock => mock.FindByClientIdAsync("Fabrikam", It.IsAny())) .ReturnsAsync(application); + instance.Setup(mock => mock.HasPermissionAsync(application, + OpenIddictConstants.Permissions.Endpoints.Introspection, It.IsAny())) + .ReturnsAsync(true); + instance.Setup(mock => mock.GetClientTypeAsync(application, It.IsAny())) .ReturnsAsync(OpenIddictConstants.ClientTypes.Confidential); @@ -563,6 +595,10 @@ namespace OpenIddict.Tests instance.Setup(mock => mock.FindByClientIdAsync("Fabrikam", It.IsAny())) .ReturnsAsync(application); + instance.Setup(mock => mock.HasPermissionAsync(application, + OpenIddictConstants.Permissions.Endpoints.Introspection, It.IsAny())) + .ReturnsAsync(true); + instance.Setup(mock => mock.GetClientTypeAsync(application, It.IsAny())) .ReturnsAsync(OpenIddictConstants.ClientTypes.Confidential); @@ -621,6 +657,10 @@ namespace OpenIddict.Tests instance.Setup(mock => mock.FindByClientIdAsync("Fabrikam", It.IsAny())) .ReturnsAsync(application); + instance.Setup(mock => mock.HasPermissionAsync(application, + OpenIddictConstants.Permissions.Endpoints.Introspection, It.IsAny())) + .ReturnsAsync(true); + instance.Setup(mock => mock.GetClientTypeAsync(application, It.IsAny())) .ReturnsAsync(OpenIddictConstants.ClientTypes.Confidential); @@ -684,6 +724,10 @@ namespace OpenIddict.Tests instance.Setup(mock => mock.FindByClientIdAsync("Fabrikam", It.IsAny())) .ReturnsAsync(application); + instance.Setup(mock => mock.HasPermissionAsync(application, + OpenIddictConstants.Permissions.Endpoints.Introspection, It.IsAny())) + .ReturnsAsync(true); + instance.Setup(mock => mock.GetClientTypeAsync(application, It.IsAny())) .ReturnsAsync(OpenIddictConstants.ClientTypes.Confidential); @@ -770,6 +814,10 @@ namespace OpenIddict.Tests instance.Setup(mock => mock.FindByClientIdAsync("Fabrikam", It.IsAny())) .ReturnsAsync(application); + instance.Setup(mock => mock.HasPermissionAsync(application, + OpenIddictConstants.Permissions.Endpoints.Introspection, It.IsAny())) + .ReturnsAsync(true); + instance.Setup(mock => mock.GetClientTypeAsync(application, It.IsAny())) .ReturnsAsync(OpenIddictConstants.ClientTypes.Confidential); @@ -838,6 +886,10 @@ namespace OpenIddict.Tests instance.Setup(mock => mock.FindByClientIdAsync("Fabrikam", It.IsAny())) .ReturnsAsync(application); + instance.Setup(mock => mock.HasPermissionAsync(application, + OpenIddictConstants.Permissions.Endpoints.Introspection, It.IsAny())) + .ReturnsAsync(true); + instance.Setup(mock => mock.GetClientTypeAsync(application, It.IsAny())) .ReturnsAsync(OpenIddictConstants.ClientTypes.Confidential); @@ -882,6 +934,10 @@ namespace OpenIddict.Tests instance.Setup(mock => mock.FindByClientIdAsync("Fabrikam", It.IsAny())) .ReturnsAsync(application); + instance.Setup(mock => mock.HasPermissionAsync(application, + OpenIddictConstants.Permissions.Endpoints.Introspection, It.IsAny())) + .ReturnsAsync(true); + instance.Setup(mock => mock.GetClientTypeAsync(application, It.IsAny())) .ReturnsAsync(OpenIddictConstants.ClientTypes.Confidential); @@ -958,6 +1014,10 @@ namespace OpenIddict.Tests instance.Setup(mock => mock.FindByClientIdAsync("Fabrikam", It.IsAny())) .ReturnsAsync(application); + instance.Setup(mock => mock.HasPermissionAsync(application, + OpenIddictConstants.Permissions.Endpoints.Introspection, It.IsAny())) + .ReturnsAsync(true); + instance.Setup(mock => mock.GetClientTypeAsync(application, It.IsAny())) .ReturnsAsync(OpenIddictConstants.ClientTypes.Confidential); @@ -1013,6 +1073,10 @@ namespace OpenIddict.Tests instance.Setup(mock => mock.FindByClientIdAsync("Fabrikam", It.IsAny())) .ReturnsAsync(application); + instance.Setup(mock => mock.HasPermissionAsync(application, + OpenIddictConstants.Permissions.Endpoints.Introspection, It.IsAny())) + .ReturnsAsync(true); + instance.Setup(mock => mock.GetClientTypeAsync(application, It.IsAny())) .ReturnsAsync(OpenIddictConstants.ClientTypes.Confidential); @@ -1088,6 +1152,10 @@ namespace OpenIddict.Tests instance.Setup(mock => mock.FindByClientIdAsync("Fabrikam", It.IsAny())) .ReturnsAsync(application); + instance.Setup(mock => mock.HasPermissionAsync(application, + OpenIddictConstants.Permissions.Endpoints.Introspection, It.IsAny())) + .ReturnsAsync(true); + instance.Setup(mock => mock.GetClientTypeAsync(application, It.IsAny())) .ReturnsAsync(OpenIddictConstants.ClientTypes.Confidential); @@ -1142,6 +1210,10 @@ namespace OpenIddict.Tests instance.Setup(mock => mock.FindByClientIdAsync("Fabrikam", It.IsAny())) .ReturnsAsync(application); + instance.Setup(mock => mock.HasPermissionAsync(application, + OpenIddictConstants.Permissions.Endpoints.Introspection, It.IsAny())) + .ReturnsAsync(true); + instance.Setup(mock => mock.GetClientTypeAsync(application, It.IsAny())) .ReturnsAsync(OpenIddictConstants.ClientTypes.Confidential); @@ -1200,6 +1272,10 @@ namespace OpenIddict.Tests instance.Setup(mock => mock.FindByClientIdAsync("Fabrikam", It.IsAny())) .ReturnsAsync(application); + instance.Setup(mock => mock.HasPermissionAsync(application, + OpenIddictConstants.Permissions.Endpoints.Introspection, It.IsAny())) + .ReturnsAsync(true); + instance.Setup(mock => mock.GetClientTypeAsync(application, It.IsAny())) .ReturnsAsync(OpenIddictConstants.ClientTypes.Confidential); @@ -1263,6 +1339,10 @@ namespace OpenIddict.Tests instance.Setup(mock => mock.FindByClientIdAsync("Fabrikam", It.IsAny())) .ReturnsAsync(application); + instance.Setup(mock => mock.HasPermissionAsync(application, + OpenIddictConstants.Permissions.Endpoints.Introspection, It.IsAny())) + .ReturnsAsync(true); + instance.Setup(mock => mock.GetClientTypeAsync(application, It.IsAny())) .ReturnsAsync(OpenIddictConstants.ClientTypes.Confidential); @@ -1349,6 +1429,10 @@ namespace OpenIddict.Tests instance.Setup(mock => mock.FindByClientIdAsync("Fabrikam", It.IsAny())) .ReturnsAsync(application); + instance.Setup(mock => mock.HasPermissionAsync(application, + OpenIddictConstants.Permissions.Endpoints.Introspection, It.IsAny())) + .ReturnsAsync(true); + instance.Setup(mock => mock.GetClientTypeAsync(application, It.IsAny())) .ReturnsAsync(OpenIddictConstants.ClientTypes.Confidential); @@ -1417,6 +1501,10 @@ namespace OpenIddict.Tests instance.Setup(mock => mock.FindByClientIdAsync("Fabrikam", It.IsAny())) .ReturnsAsync(application); + instance.Setup(mock => mock.HasPermissionAsync(application, + OpenIddictConstants.Permissions.Endpoints.Introspection, It.IsAny())) + .ReturnsAsync(true); + instance.Setup(mock => mock.GetClientTypeAsync(application, It.IsAny())) .ReturnsAsync(OpenIddictConstants.ClientTypes.Confidential); @@ -1461,6 +1549,10 @@ namespace OpenIddict.Tests instance.Setup(mock => mock.FindByClientIdAsync("Fabrikam", It.IsAny())) .ReturnsAsync(application); + instance.Setup(mock => mock.HasPermissionAsync(application, + OpenIddictConstants.Permissions.Endpoints.Introspection, It.IsAny())) + .ReturnsAsync(true); + instance.Setup(mock => mock.GetClientTypeAsync(application, It.IsAny())) .ReturnsAsync(OpenIddictConstants.ClientTypes.Confidential); @@ -1537,6 +1629,10 @@ namespace OpenIddict.Tests instance.Setup(mock => mock.FindByClientIdAsync("Fabrikam", It.IsAny())) .ReturnsAsync(application); + instance.Setup(mock => mock.HasPermissionAsync(application, + OpenIddictConstants.Permissions.Endpoints.Introspection, It.IsAny())) + .ReturnsAsync(true); + instance.Setup(mock => mock.GetClientTypeAsync(application, It.IsAny())) .ReturnsAsync(OpenIddictConstants.ClientTypes.Confidential); @@ -1695,6 +1791,14 @@ namespace OpenIddict.Tests instance.Setup(mock => mock.FindByClientIdAsync("Fabrikam", It.IsAny())) .ReturnsAsync(application); + instance.Setup(mock => mock.HasPermissionAsync(application, + OpenIddictConstants.Permissions.Endpoints.Token, It.IsAny())) + .ReturnsAsync(true); + + instance.Setup(mock => mock.HasPermissionAsync(application, + OpenIddictConstants.Permissions.GrantTypes.Password, It.IsAny())) + .ReturnsAsync(true); + instance.Setup(mock => mock.GetClientTypeAsync(application, It.IsAny())) .ReturnsAsync(OpenIddictConstants.ClientTypes.Public); @@ -1796,6 +1900,14 @@ namespace OpenIddict.Tests instance.Setup(mock => mock.FindByClientIdAsync("Fabrikam", It.IsAny())) .ReturnsAsync(application); + instance.Setup(mock => mock.HasPermissionAsync(application, + OpenIddictConstants.Permissions.Endpoints.Authorization, It.IsAny())) + .ReturnsAsync(true); + + instance.Setup(mock => mock.HasPermissionAsync(application, + OpenIddictConstants.Permissions.GrantTypes.AuthorizationCode, It.IsAny())) + .ReturnsAsync(true); + instance.Setup(mock => mock.ValidateRedirectUriAsync(application, "http://www.fabrikam.com/path", It.IsAny())) .ReturnsAsync(true); @@ -1857,6 +1969,14 @@ namespace OpenIddict.Tests instance.Setup(mock => mock.FindByClientIdAsync("Fabrikam", It.IsAny())) .ReturnsAsync(application); + instance.Setup(mock => mock.HasPermissionAsync(application, + OpenIddictConstants.Permissions.Endpoints.Authorization, It.IsAny())) + .ReturnsAsync(true); + + instance.Setup(mock => mock.HasPermissionAsync(application, + OpenIddictConstants.Permissions.GrantTypes.AuthorizationCode, It.IsAny())) + .ReturnsAsync(true); + instance.Setup(mock => mock.ValidateRedirectUriAsync(application, "http://www.fabrikam.com/path", It.IsAny())) .ReturnsAsync(true); @@ -1931,6 +2051,14 @@ namespace OpenIddict.Tests instance.Setup(mock => mock.FindByClientIdAsync("Fabrikam", It.IsAny())) .ReturnsAsync(application); + instance.Setup(mock => mock.HasPermissionAsync(application, + OpenIddictConstants.Permissions.Endpoints.Authorization, It.IsAny())) + .ReturnsAsync(true); + + instance.Setup(mock => mock.HasPermissionAsync(application, + OpenIddictConstants.Permissions.GrantTypes.AuthorizationCode, It.IsAny())) + .ReturnsAsync(true); + instance.Setup(mock => mock.ValidateRedirectUriAsync(application, "http://www.fabrikam.com/path", It.IsAny())) .ReturnsAsync(true); @@ -2003,6 +2131,14 @@ namespace OpenIddict.Tests instance.Setup(mock => mock.FindByClientIdAsync("Fabrikam", It.IsAny())) .ReturnsAsync(application); + instance.Setup(mock => mock.HasPermissionAsync(application, + OpenIddictConstants.Permissions.Endpoints.Authorization, It.IsAny())) + .ReturnsAsync(true); + + instance.Setup(mock => mock.HasPermissionAsync(application, + OpenIddictConstants.Permissions.GrantTypes.AuthorizationCode, It.IsAny())) + .ReturnsAsync(true); + instance.Setup(mock => mock.ValidateRedirectUriAsync(application, "http://www.fabrikam.com/path", It.IsAny())) .ReturnsAsync(true); @@ -2061,6 +2197,14 @@ namespace OpenIddict.Tests instance.Setup(mock => mock.FindByClientIdAsync("Fabrikam", It.IsAny())) .ReturnsAsync(application); + instance.Setup(mock => mock.HasPermissionAsync(application, + OpenIddictConstants.Permissions.Endpoints.Authorization, It.IsAny())) + .ReturnsAsync(true); + + instance.Setup(mock => mock.HasPermissionAsync(application, + OpenIddictConstants.Permissions.GrantTypes.AuthorizationCode, It.IsAny())) + .ReturnsAsync(true); + instance.Setup(mock => mock.ValidateRedirectUriAsync(application, "http://www.fabrikam.com/path", It.IsAny())) .ReturnsAsync(true); @@ -2350,6 +2494,14 @@ namespace OpenIddict.Tests instance.Setup(mock => mock.FindByClientIdAsync("Fabrikam", It.IsAny())) .ReturnsAsync(application); + instance.Setup(mock => mock.HasPermissionAsync(application, + OpenIddictConstants.Permissions.Endpoints.Token, It.IsAny())) + .ReturnsAsync(true); + + instance.Setup(mock => mock.HasPermissionAsync(application, + OpenIddictConstants.Permissions.GrantTypes.Password, It.IsAny())) + .ReturnsAsync(true); + instance.Setup(mock => mock.GetClientTypeAsync(application, It.IsAny())) .ReturnsAsync(OpenIddictConstants.ClientTypes.Public); diff --git a/test/OpenIddict.Tests/OpenIddictProviderTests.cs b/test/OpenIddict.Tests/OpenIddictProviderTests.cs index e8a104d2..13860dd2 100644 --- a/test/OpenIddict.Tests/OpenIddictProviderTests.cs +++ b/test/OpenIddict.Tests/OpenIddictProviderTests.cs @@ -58,6 +58,14 @@ namespace OpenIddict.Tests instance.Setup(mock => mock.FindByClientIdAsync("Fabrikam", It.IsAny())) .ReturnsAsync(application); + instance.Setup(mock => mock.HasPermissionAsync(application, + OpenIddictConstants.Permissions.Endpoints.Authorization, It.IsAny())) + .ReturnsAsync(true); + + instance.Setup(mock => mock.HasPermissionAsync(application, + OpenIddictConstants.Permissions.GrantTypes.AuthorizationCode, It.IsAny())) + .ReturnsAsync(true); + instance.Setup(mock => mock.ValidateRedirectUriAsync(application, "http://www.fabrikam.com/path", It.IsAny())) .ReturnsAsync(true); @@ -234,6 +242,14 @@ namespace OpenIddict.Tests instance.Setup(mock => mock.FindByClientIdAsync("Fabrikam", It.IsAny())) .ReturnsAsync(application); + instance.Setup(mock => mock.HasPermissionAsync(application, + OpenIddictConstants.Permissions.Endpoints.Token, It.IsAny())) + .ReturnsAsync(true); + + instance.Setup(mock => mock.HasPermissionAsync(application, + OpenIddictConstants.Permissions.GrantTypes.AuthorizationCode, It.IsAny())) + .ReturnsAsync(true); + instance.Setup(mock => mock.GetClientTypeAsync(application, It.IsAny())) .ReturnsAsync(OpenIddictConstants.ClientTypes.Public); })); @@ -425,6 +441,14 @@ namespace OpenIddict.Tests instance.Setup(mock => mock.FindByClientIdAsync("Fabrikam", It.IsAny())) .ReturnsAsync(application); + instance.Setup(mock => mock.HasPermissionAsync(application, + OpenIddictConstants.Permissions.Endpoints.Token, It.IsAny())) + .ReturnsAsync(true); + + instance.Setup(mock => mock.HasPermissionAsync(application, + OpenIddictConstants.Permissions.GrantTypes.AuthorizationCode, It.IsAny())) + .ReturnsAsync(true); + instance.Setup(mock => mock.GetClientTypeAsync(application, It.IsAny())) .ReturnsAsync(OpenIddictConstants.ClientTypes.Public); })); @@ -497,6 +521,14 @@ namespace OpenIddict.Tests instance.Setup(mock => mock.FindByClientIdAsync("Fabrikam", It.IsAny())) .ReturnsAsync(application); + instance.Setup(mock => mock.HasPermissionAsync(application, + OpenIddictConstants.Permissions.Endpoints.Token, It.IsAny())) + .ReturnsAsync(true); + + instance.Setup(mock => mock.HasPermissionAsync(application, + OpenIddictConstants.Permissions.GrantTypes.AuthorizationCode, It.IsAny())) + .ReturnsAsync(true); + instance.Setup(mock => mock.GetClientTypeAsync(application, It.IsAny())) .ReturnsAsync(OpenIddictConstants.ClientTypes.Public); })); @@ -1093,6 +1125,14 @@ namespace OpenIddict.Tests instance.Setup(mock => mock.FindByClientIdAsync("Fabrikam", It.IsAny())) .ReturnsAsync(application); + instance.Setup(mock => mock.HasPermissionAsync(application, + OpenIddictConstants.Permissions.Endpoints.Authorization, It.IsAny())) + .ReturnsAsync(true); + + instance.Setup(mock => mock.HasPermissionAsync(application, + OpenIddictConstants.Permissions.GrantTypes.AuthorizationCode, It.IsAny())) + .ReturnsAsync(true); + instance.Setup(mock => mock.ValidateRedirectUriAsync(application, "http://www.fabrikam.com/path", It.IsAny())) .ReturnsAsync(true); @@ -1149,6 +1189,14 @@ namespace OpenIddict.Tests instance.Setup(mock => mock.FindByClientIdAsync("Fabrikam", It.IsAny())) .ReturnsAsync(application); + instance.Setup(mock => mock.HasPermissionAsync(application, + OpenIddictConstants.Permissions.Endpoints.Authorization, It.IsAny())) + .ReturnsAsync(true); + + instance.Setup(mock => mock.HasPermissionAsync(application, + OpenIddictConstants.Permissions.GrantTypes.AuthorizationCode, It.IsAny())) + .ReturnsAsync(true); + instance.Setup(mock => mock.ValidateRedirectUriAsync(application, "http://www.fabrikam.com/path", It.IsAny())) .ReturnsAsync(true);