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