Browse Source

Introduce new IOpenIddictApplicationStore/OpenIddictApplicationManager APIs

pull/310/head
Kévin Chalet 9 years ago
parent
commit
7c65c83a04
  1. 4
      samples/Mvc.Client/Startup.cs
  2. 82
      samples/Mvc.Server/Startup.cs
  3. 205
      src/OpenIddict.Core/Managers/OpenIddictApplicationManager.cs
  4. 53
      src/OpenIddict.Core/Stores/IOpenIddictApplicationStore.cs
  5. 108
      src/OpenIddict.EntityFrameworkCore/Stores/OpenIddictApplicationStore.cs
  6. 2
      src/OpenIddict.Models/OpenIddictApplication.cs
  7. 13
      src/OpenIddict/OpenIddictProvider.Authentication.cs
  8. 2
      src/OpenIddict/OpenIddictProvider.Exchange.cs
  9. 2
      src/OpenIddict/OpenIddictProvider.Introspection.cs
  10. 2
      src/OpenIddict/OpenIddictProvider.Revocation.cs
  11. 54
      test/OpenIddict.Tests/OpenIddictProviderTests.Authentication.cs
  12. 6
      test/OpenIddict.Tests/OpenIddictProviderTests.Exchange.cs
  13. 14
      test/OpenIddict.Tests/OpenIddictProviderTests.Introspection.cs
  14. 4
      test/OpenIddict.Tests/OpenIddictProviderTests.Revocation.cs
  15. 3
      test/OpenIddict.Tests/OpenIddictProviderTests.Serialization.cs

4
samples/Mvc.Client/Startup.cs

@ -31,8 +31,8 @@ namespace Mvc.Client {
app.UseOpenIdConnectAuthentication(new OpenIdConnectOptions {
// Note: these settings must match the application details
// inserted in the database at the server level.
ClientId = "myClient",
ClientSecret = "secret_secret_secret",
ClientId = "mvc",
ClientSecret = "901564A5-E7FE-42CB-B10D-61EF6A8F3654",
PostLogoutRedirectUri = "http://localhost:53507/",
RequireHttpsMetadata = false,

82
samples/Mvc.Server/Startup.cs

@ -1,5 +1,6 @@
using System.Linq;
using CryptoHelper;
using System;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Identity.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore;
@ -150,50 +151,47 @@ namespace Mvc.Server {
app.UseMvcWithDefaultRoute();
using (var context = new ApplicationDbContext(
app.ApplicationServices.GetRequiredService<DbContextOptions<ApplicationDbContext>>())) {
context.Database.EnsureCreated();
var applications = context.Set<OpenIddictApplication>();
// Add Mvc.Client to the known applications.
if (!applications.Any()) {
// Note: when using the introspection middleware, your resource server
// MUST be registered as an OAuth2 client and have valid credentials.
//
// context.Applications.Add(new OpenIddictApplication {
// Id = "resource_server",
// DisplayName = "Main resource server",
// Secret = Crypto.HashPassword("secret_secret_secret"),
// Type = OpenIddictConstants.ClientTypes.Confidential
// });
applications.Add(new OpenIddictApplication {
ClientId = "myClient",
ClientSecret = Crypto.HashPassword("secret_secret_secret"),
DisplayName = "My client application",
// Seed the database with the sample applications.
// Note: in a real world application, this step should be part of a setup script.
InitializeAsync(app.ApplicationServices, CancellationToken.None).GetAwaiter().GetResult();
}
private async Task InitializeAsync(IServiceProvider services, CancellationToken cancellationToken) {
// Create a new service scope to ensure the database context is correctly disposed when this methods returns.
using (var scope = services.GetRequiredService<IServiceScopeFactory>().CreateScope()) {
var context = scope.ServiceProvider.GetRequiredService<ApplicationDbContext>();
await context.Database.EnsureCreatedAsync();
var manager = scope.ServiceProvider.GetRequiredService<OpenIddictApplicationManager<OpenIddictApplication>>();
if (await manager.FindByClientIdAsync("mvc", cancellationToken) == null) {
var application = new OpenIddictApplication {
ClientId = "mvc",
DisplayName = "MVC client application",
LogoutRedirectUri = "http://localhost:53507/",
RedirectUri = "http://localhost:53507/signin-oidc",
Type = OpenIddictConstants.ClientTypes.Confidential
});
// To test this sample with Postman, use the following settings:
//
// * Authorization URL: http://localhost:54540/connect/authorize
// * Access token URL: http://localhost:54540/connect/token
// * Client ID: postman
// * Client secret: [blank] (not used with public clients)
// * Scope: openid email profile roles
// * Grant type: authorization code
// * Request access token locally: yes
applications.Add(new OpenIddictApplication {
RedirectUri = "http://localhost:53507/signin-oidc"
};
await manager.CreateAsync(application, "901564A5-E7FE-42CB-B10D-61EF6A8F3654", cancellationToken);
}
// To test this sample with Postman, use the following settings:
//
// * Authorization URL: http://localhost:54540/connect/authorize
// * Access token URL: http://localhost:54540/connect/token
// * Client ID: postman
// * Client secret: [blank] (not used with public clients)
// * Scope: openid email profile roles
// * Grant type: authorization code
// * Request access token locally: yes
if (await manager.FindByClientIdAsync("postman", cancellationToken) == null) {
var application = new OpenIddictApplication {
ClientId = "postman",
DisplayName = "Postman",
RedirectUri = "https://www.getpostman.com/oauth2/callback",
Type = OpenIddictConstants.ClientTypes.Public
});
RedirectUri = "https://www.getpostman.com/oauth2/callback"
};
context.SaveChanges();
await manager.CreateAsync(application, cancellationToken);
}
}
}

205
src/OpenIddict.Core/Managers/OpenIddictApplicationManager.cs

@ -44,12 +44,67 @@ namespace OpenIddict.Core {
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation,
/// whose result returns the unique identifier associated with the application.
/// </returns>
public virtual Task<string> CreateAsync(TApplication application, CancellationToken cancellationToken) {
public virtual async Task<string> CreateAsync([NotNull] TApplication application, CancellationToken cancellationToken) {
if (application == null) {
throw new ArgumentNullException(nameof(application));
}
return Store.CreateAsync(application, cancellationToken);
if (!string.IsNullOrEmpty(await Store.GetHashedSecretAsync(application, cancellationToken))) {
throw new ArgumentException("The client secret hash cannot be directly set on the application entity. " +
"To create a confidential application, use the CreateAsync() overload accepting a secret parameter.");
}
// If no client type was specified, assume it's a public application.
if (string.IsNullOrEmpty(await Store.GetClientTypeAsync(application, cancellationToken))) {
await Store.SetClientTypeAsync(application, OpenIddictConstants.ClientTypes.Public, cancellationToken);
}
await ValidateAsync(application, cancellationToken);
return await Store.CreateAsync(application, cancellationToken);
}
/// <summary>
/// Creates a new confidential application, using the specified client secret.
/// </summary>
/// <param name="application">The application to create.</param>
/// <param name="secret">The client secret associated with the application.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation,
/// whose result returns the unique identifier associated with the application.
/// </returns>
public virtual async Task<string> CreateAsync(
[NotNull] TApplication application,
[NotNull] string secret, CancellationToken cancellationToken) {
if (application == null) {
throw new ArgumentNullException(nameof(application));
}
if (!string.IsNullOrEmpty(await Store.GetHashedSecretAsync(application, cancellationToken))) {
throw new ArgumentException("The client secret hash cannot be directly set on the application entity.");
}
await Store.SetClientTypeAsync(application, OpenIddictConstants.ClientTypes.Confidential, cancellationToken);
await Store.SetHashedSecretAsync(application, Crypto.HashPassword(secret), cancellationToken);
await ValidateAsync(application, cancellationToken);
return await Store.CreateAsync(application, cancellationToken);
}
/// <summary>
/// Removes an existing application.
/// </summary>
/// <param name="application">The application to delete.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation.
/// </returns>
public virtual Task DeleteAsync([NotNull] TApplication application, CancellationToken cancellationToken) {
if (application == null) {
throw new ArgumentNullException(nameof(application));
}
return Store.DeleteAsync(application, cancellationToken);
}
/// <summary>
@ -100,7 +155,7 @@ namespace OpenIddict.Core {
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation,
/// whose result returns the client type of the application (by default, "public").
/// </returns>
public virtual async Task<string> GetClientTypeAsync(TApplication application, CancellationToken cancellationToken) {
public virtual async Task<string> GetClientTypeAsync([NotNull] TApplication application, CancellationToken cancellationToken) {
if (application == null) {
throw new ArgumentNullException(nameof(application));
}
@ -126,7 +181,7 @@ namespace OpenIddict.Core {
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation,
/// whose result returns the display name associated with the application.
/// </returns>
public virtual Task<string> GetDisplayNameAsync(TApplication application, CancellationToken cancellationToken) {
public virtual Task<string> GetDisplayNameAsync([NotNull] TApplication application, CancellationToken cancellationToken) {
if (application == null) {
throw new ArgumentNullException(nameof(application));
}
@ -143,13 +198,48 @@ namespace OpenIddict.Core {
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation,
/// whose result returns the tokens associated with the application.
/// </returns>
public virtual Task<IEnumerable<string>> GetTokensAsync(TApplication application, CancellationToken cancellationToken) {
public virtual Task<IEnumerable<string>> GetTokensAsync([NotNull] TApplication application, CancellationToken cancellationToken) {
if (application == null) {
throw new ArgumentNullException(nameof(application));
}
return Store.GetTokensAsync(application, cancellationToken);
}
/// <summary>
/// Determines whether the specified application has a redirect_uri.
/// </summary>
/// <param name="application">The application.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation,
/// whose result returns a boolean indicating whether a redirect_uri is registered.
/// </returns>
public virtual async Task<bool> HasRedirectUriAsync([NotNull] TApplication application, CancellationToken cancellationToken) {
if (application == null) {
throw new ArgumentNullException(nameof(application));
}
return !string.IsNullOrEmpty(await Store.GetRedirectUriAsync(application, cancellationToken));
}
/// <summary>
/// Determines whether the specified application has a client secret.
/// </summary>
/// <param name="application">The application.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation,
/// whose result returns a boolean indicating whether a client secret is registered.
/// </returns>
public virtual async Task<bool> HasClientSecretAsync([NotNull] TApplication application, CancellationToken cancellationToken) {
if (application == null) {
throw new ArgumentNullException(nameof(application));
}
return !string.IsNullOrEmpty(await Store.GetHashedSecretAsync(application, cancellationToken));
}
/// <summary>
/// Determines whether an application is a confidential client.
/// </summary>
@ -175,7 +265,7 @@ namespace OpenIddict.Core {
/// <param name="application">The application.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns><c>true</c> if the application is a public client, <c>false</c> otherwise.</returns>
public async Task<bool> IsPublicAsync(TApplication application, CancellationToken cancellationToken) {
public async Task<bool> IsPublicAsync([NotNull] TApplication application, CancellationToken cancellationToken) {
if (application == null) {
throw new ArgumentNullException(nameof(application));
}
@ -189,6 +279,105 @@ namespace OpenIddict.Core {
return string.Equals(type, OpenIddictConstants.ClientTypes.Public, StringComparison.OrdinalIgnoreCase);
}
/// <summary>
/// Updates the client secret associated with an application.
/// </summary>
/// <param name="application">The application.</param>
/// <param name="secret">The client secret associated with the application.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation.
/// </returns>
public virtual async Task SetClientSecretAsync([NotNull] TApplication application, [NotNull] string secret, CancellationToken cancellationToken) {
if (application == null) {
throw new ArgumentNullException(nameof(application));
}
if (string.IsNullOrEmpty(secret)) {
throw new ArgumentException("The client secret cannot be null or empty.", nameof(secret));
}
await Store.SetHashedSecretAsync(application, Crypto.HashPassword(secret), cancellationToken);
await UpdateAsync(application, cancellationToken);
}
/// <summary>
/// Updates an existing application.
/// </summary>
/// <param name="application">The application to update.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation.
/// </returns>
public virtual async Task UpdateAsync([NotNull] TApplication application, CancellationToken cancellationToken) {
if (application == null) {
throw new ArgumentNullException(nameof(application));
}
await ValidateAsync(application, cancellationToken);
await Store.UpdateAsync(application, cancellationToken);
}
/// <summary>
/// Validates the application to ensure it's in a consistent state.
/// </summary>
/// <param name="application">The application.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation.
/// </returns>
protected virtual async Task ValidateAsync([NotNull] TApplication application, CancellationToken cancellationToken) {
if (application == null) {
throw new ArgumentNullException(nameof(application));
}
if (string.IsNullOrEmpty(await Store.GetClientIdAsync(application, cancellationToken))) {
throw new ArgumentException("The client identifier cannot be null or empty.", nameof(application));
}
var type = await Store.GetClientTypeAsync(application, cancellationToken);
if (string.IsNullOrEmpty(type)) {
throw new ArgumentException("The client type cannot be null or empty.", nameof(application));
}
// Ensure the application type is supported by the manager.
if (!string.Equals(type, OpenIddictConstants.ClientTypes.Confidential, StringComparison.OrdinalIgnoreCase) &&
!string.Equals(type, OpenIddictConstants.ClientTypes.Public, StringComparison.OrdinalIgnoreCase)) {
throw new ArgumentException("Only 'confidential' or 'public' applications are " +
"supported by the default application manager.", nameof(application));
}
var hash = await Store.GetHashedSecretAsync(application, cancellationToken);
// Ensure a client secret was specified if the client is a confidential application.
if (string.IsNullOrEmpty(hash) &&
string.Equals(type, OpenIddictConstants.ClientTypes.Confidential, StringComparison.OrdinalIgnoreCase)) {
throw new ArgumentException("The client secret cannot be null or empty for a confidential application.", nameof(application));
}
// Ensure no client secret was specified if the client is a public application.
else if (!string.IsNullOrEmpty(hash) &&
string.Equals(type, OpenIddictConstants.ClientTypes.Public, StringComparison.OrdinalIgnoreCase)) {
throw new ArgumentException("A client secret cannot be associated with a public application.", nameof(application));
}
// When a redirect_uri is specified, ensure it is valid and spec-compliant.
// See https://tools.ietf.org/html/rfc6749#section-3.1 for more information.
var address = await Store.GetRedirectUriAsync(application, cancellationToken);
if (!string.IsNullOrEmpty(address)) {
Uri uri;
// Ensure the redirect_uri is a valid and absolute URL.
if (!Uri.TryCreate(address, UriKind.Absolute, out uri)) {
throw new ArgumentException("The redirect_uri must be an absolute URL.");
}
// Ensure the redirect_uri doesn't contain a fragment.
if (!string.IsNullOrEmpty(uri.Fragment)) {
throw new ArgumentException("The redirect_uri cannot contain a fragment.");
}
}
}
/// <summary>
/// Validates the redirect_uri associated with an application.
/// </summary>
@ -199,7 +388,7 @@ namespace OpenIddict.Core {
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation,
/// whose result returns a boolean indicating whether the redirect_uri was valid.
/// </returns>
public virtual async Task<bool> ValidateRedirectUriAsync(TApplication application, string address, CancellationToken cancellationToken) {
public virtual async Task<bool> ValidateRedirectUriAsync([NotNull] TApplication application, string address, CancellationToken cancellationToken) {
if (application == null) {
throw new ArgumentNullException(nameof(application));
}
@ -225,7 +414,7 @@ namespace OpenIddict.Core {
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation,
/// whose result returns a boolean indicating whether the client secret was valid.
/// </returns>
public virtual async Task<bool> ValidateSecretAsync(TApplication application, string secret, CancellationToken cancellationToken) {
public virtual async Task<bool> ValidateClientSecretAsync([NotNull] TApplication application, string secret, CancellationToken cancellationToken) {
if (application == null) {
throw new ArgumentNullException(nameof(application));
}

53
src/OpenIddict.Core/Stores/IOpenIddictApplicationStore.cs

@ -26,6 +26,16 @@ namespace OpenIddict.Core {
/// </returns>
Task<string> CreateAsync([NotNull] TApplication application, CancellationToken cancellationToken);
/// <summary>
/// Removes an existing application.
/// </summary>
/// <param name="application">The application to delete.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation.
/// </returns>
Task DeleteAsync([NotNull] TApplication application, CancellationToken cancellationToken);
/// <summary>
/// Retrieves an application using its unique identifier.
/// </summary>
@ -59,6 +69,17 @@ namespace OpenIddict.Core {
/// </returns>
Task<TApplication> FindByLogoutRedirectUri(string url, CancellationToken cancellationToken);
/// <summary>
/// Retrieves the client identifier associated with an application.
/// </summary>
/// <param name="application">The application.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation,
/// whose result returns the client identifier associated with the application.
/// </returns>
Task<string> GetClientIdAsync([NotNull] TApplication application, CancellationToken cancellationToken);
/// <summary>
/// Retrieves the client type associated with an application.
/// </summary>
@ -113,5 +134,37 @@ namespace OpenIddict.Core {
/// whose result returns the tokens associated with the application.
/// </returns>
Task<IEnumerable<string>> GetTokensAsync([NotNull] TApplication application, CancellationToken cancellationToken);
/// <summary>
/// Sets the client type associated with an application.
/// </summary>
/// <param name="application">The application.</param>
/// <param name="type">The client type associated with the application.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation.
/// </returns>
Task SetClientTypeAsync([NotNull] TApplication application, [NotNull] string type, CancellationToken cancellationToken);
/// <summary>
/// Sets the hashed secret associated with an application.
/// </summary>
/// <param name="application">The application.</param>
/// <param name="hash">The hashed client secret associated with the application.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation.
/// </returns>
Task SetHashedSecretAsync([NotNull] TApplication application, [NotNull] string hash, CancellationToken cancellationToken);
/// <summary>
/// Updates an existing application.
/// </summary>
/// <param name="application">The application to update.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation.
/// </returns>
Task UpdateAsync([NotNull] TApplication application, CancellationToken cancellationToken);
}
}

108
src/OpenIddict.EntityFrameworkCore/Stores/OpenIddictApplicationStore.cs

@ -70,6 +70,28 @@ namespace OpenIddict.EntityFrameworkCore {
return converter.ConvertToInvariantString(application.Id);
}
/// <summary>
/// Removes an existing application.
/// </summary>
/// <param name="application">The application to delete.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation.
/// </returns>
public virtual async Task DeleteAsync([NotNull] TApplication application, CancellationToken cancellationToken) {
if (application == null) {
throw new ArgumentNullException(nameof(application));
}
Context.Remove(application);
try {
await Context.SaveChangesAsync(cancellationToken);
}
catch (DbUpdateConcurrencyException) { }
}
/// <summary>
/// Retrieves an application using its unique identifier.
/// </summary>
@ -119,6 +141,23 @@ namespace OpenIddict.EntityFrameworkCore {
return Applications.SingleOrDefaultAsync(application => application.LogoutRedirectUri == url, cancellationToken);
}
/// <summary>
/// Retrieves the client identifier associated with an application.
/// </summary>
/// <param name="application">The application.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation,
/// whose result returns the client identifier associated with the application.
/// </returns>
public virtual Task<string> GetClientIdAsync([NotNull] TApplication application, CancellationToken cancellationToken) {
if (application == null) {
throw new ArgumentNullException(nameof(application));
}
return Task.FromResult(application.ClientId);
}
/// <summary>
/// Retrieves the client type associated with an application.
/// </summary>
@ -220,5 +259,74 @@ namespace OpenIddict.EntityFrameworkCore {
return tokens;
}
/// <summary>
/// Sets the client type associated with an application.
/// </summary>
/// <param name="application">The application.</param>
/// <param name="type">The client type associated with the application.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation.
/// </returns>
public virtual Task SetClientTypeAsync([NotNull] TApplication application, [NotNull] string type, CancellationToken cancellationToken) {
if (application == null) {
throw new ArgumentNullException(nameof(application));
}
if (string.IsNullOrEmpty(type)) {
throw new ArgumentException("The client type cannot be null or empty.", nameof(type));
}
application.Type = type;
return Task.FromResult(0);
}
/// <summary>
/// Sets the hashed secret associated with an application.
/// </summary>
/// <param name="application">The application.</param>
/// <param name="hash">The hashed client secret associated with the application.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation.
/// </returns>
public virtual Task SetHashedSecretAsync([NotNull] TApplication application, [NotNull] string hash, CancellationToken cancellationToken) {
if (application == null) {
throw new ArgumentNullException(nameof(application));
}
if (string.IsNullOrEmpty(hash)) {
throw new ArgumentException("The client secret hash cannot be null or empty.", nameof(hash));
}
application.ClientSecret = hash;
return Task.FromResult(0);
}
/// <summary>
/// Updates an existing application.
/// </summary>
/// <param name="application">The application to update.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation.
/// </returns>
public virtual async Task UpdateAsync([NotNull] TApplication application, CancellationToken cancellationToken) {
if (application == null) {
throw new ArgumentNullException(nameof(application));
}
Context.Attach(application);
Context.Update(application);
try {
await Context.SaveChangesAsync(cancellationToken);
}
catch (DbUpdateConcurrencyException) { }
}
}
}

2
src/OpenIddict.Models/OpenIddictApplication.cs

@ -73,6 +73,6 @@ namespace OpenIddict.Models {
/// Gets or sets the application type
/// associated with the current application.
/// </summary>
public virtual string Type { get; set; } = "public";
public virtual string Type { get; set; }
}
}

13
src/OpenIddict/OpenIddictProvider.Authentication.cs

@ -246,6 +246,19 @@ namespace OpenIddict {
return;
}
// Ensure a redirect_uri was associated with the application.
if (!await applications.HasRedirectUriAsync(application, context.HttpContext.RequestAborted)) {
logger.LogError("The authorization request was rejected because no redirect_uri " +
"was registered with the application '{ClientId}'.", context.ClientId);
context.Reject(
error: OpenIdConnectConstants.Errors.UnauthorizedClient,
description: "The client application is not allowed to use interactive flows.");
return;
}
// Ensure the redirect_uri is valid.
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);

2
src/OpenIddict/OpenIddictProvider.Exchange.cs

@ -167,7 +167,7 @@ namespace OpenIddict {
return;
}
if (!await applications.ValidateSecretAsync(application, context.ClientSecret, context.HttpContext.RequestAborted)) {
if (!await applications.ValidateClientSecretAsync(application, context.ClientSecret, context.HttpContext.RequestAborted)) {
logger.LogError("The token request was rejected because the confidential application " +
"'{ClientId}' didn't specify valid client credentials.", context.ClientId);

2
src/OpenIddict/OpenIddictProvider.Introspection.cs

@ -75,7 +75,7 @@ namespace OpenIddict {
}
// Validate the client credentials.
if (!await applications.ValidateSecretAsync(application, context.ClientSecret, context.HttpContext.RequestAborted)) {
if (!await applications.ValidateClientSecretAsync(application, context.ClientSecret, context.HttpContext.RequestAborted)) {
logger.LogError("The introspection request was rejected because the confidential application " +
"'{ClientId}' didn't specify valid client credentials.", context.ClientId);

2
src/OpenIddict/OpenIddictProvider.Revocation.cs

@ -104,7 +104,7 @@ namespace OpenIddict {
return;
}
if (!await applications.ValidateSecretAsync(application, context.ClientSecret, context.HttpContext.RequestAborted)) {
if (!await applications.ValidateClientSecretAsync(application, context.ClientSecret, context.HttpContext.RequestAborted)) {
context.Reject(
error: OpenIdConnectConstants.Errors.InvalidClient,
description: "Invalid credentials: ensure that you specified a correct client_secret.");

54
test/OpenIddict.Tests/OpenIddictProviderTests.Authentication.cs

@ -306,6 +306,40 @@ namespace OpenIddict.Tests {
Mock.Get(manager).Verify(mock => mock.FindByClientIdAsync("Fabrikam", It.IsAny<CancellationToken>()), Times.Once());
}
[Fact]
public async Task ValidateAuthorizationRequest_RequestIsRejectedWhenClientHasNoRedirectUri() {
// Arrange
var application = new OpenIddictApplication();
var manager = CreateApplicationManager(instance => {
instance.Setup(mock => mock.FindByClientIdAsync("Fabrikam", It.IsAny<CancellationToken>()))
.ReturnsAsync(application);
instance.Setup(mock => mock.HasRedirectUriAsync(application, It.IsAny<CancellationToken>()))
.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.UnauthorizedClient, response.Error);
Assert.Equal("The client application is not allowed to use interactive flows.", response.ErrorDescription);
Mock.Get(manager).Verify(mock => mock.FindByClientIdAsync("Fabrikam", It.IsAny<CancellationToken>()), Times.Once());
Mock.Get(manager).Verify(mock => mock.HasRedirectUriAsync(application, It.IsAny<CancellationToken>()), Times.Once());
}
[Fact]
public async Task ValidateAuthorizationRequest_RequestIsRejectedWhenRedirectUriIsInvalid() {
// Arrange
@ -315,6 +349,9 @@ namespace OpenIddict.Tests {
instance.Setup(mock => mock.FindByClientIdAsync("Fabrikam", It.IsAny<CancellationToken>()))
.ReturnsAsync(application);
instance.Setup(mock => mock.HasRedirectUriAsync(application, It.IsAny<CancellationToken>()))
.ReturnsAsync(true);
instance.Setup(mock => mock.ValidateRedirectUriAsync(application, "http://www.fabrikam.com/path", It.IsAny<CancellationToken>()))
.ReturnsAsync(false);
});
@ -337,6 +374,7 @@ namespace OpenIddict.Tests {
Assert.Equal("Invalid redirect_uri.", response.ErrorDescription);
Mock.Get(manager).Verify(mock => mock.FindByClientIdAsync("Fabrikam", It.IsAny<CancellationToken>()), Times.Once());
Mock.Get(manager).Verify(mock => mock.HasRedirectUriAsync(application, It.IsAny<CancellationToken>()), Times.Once());
Mock.Get(manager).Verify(mock => mock.ValidateRedirectUriAsync(application, "http://www.fabrikam.com/path", It.IsAny<CancellationToken>()), Times.Once());
}
@ -353,6 +391,9 @@ namespace OpenIddict.Tests {
instance.Setup(mock => mock.FindByClientIdAsync("Fabrikam", It.IsAny<CancellationToken>()))
.ReturnsAsync(application);
instance.Setup(mock => mock.HasRedirectUriAsync(application, It.IsAny<CancellationToken>()))
.ReturnsAsync(true);
instance.Setup(mock => mock.ValidateRedirectUriAsync(application, "http://www.fabrikam.com/path", It.IsAny<CancellationToken>()))
.ReturnsAsync(true);
@ -381,6 +422,7 @@ namespace OpenIddict.Tests {
"an access token from the authorization endpoint.", response.ErrorDescription);
Mock.Get(manager).Verify(mock => mock.FindByClientIdAsync("Fabrikam", It.IsAny<CancellationToken>()), Times.Once());
Mock.Get(manager).Verify(mock => mock.HasRedirectUriAsync(application, It.IsAny<CancellationToken>()), Times.Once());
Mock.Get(manager).Verify(mock => mock.ValidateRedirectUriAsync(application, "http://www.fabrikam.com/path", It.IsAny<CancellationToken>()), Times.Once());
Mock.Get(manager).Verify(mock => mock.GetClientTypeAsync(application, It.IsAny<CancellationToken>()), Times.Once());
}
@ -397,6 +439,9 @@ namespace OpenIddict.Tests {
instance.Setup(mock => mock.FindByClientIdAsync("Fabrikam", It.IsAny<CancellationToken>()))
.ReturnsAsync(application);
instance.Setup(mock => mock.HasRedirectUriAsync(application, It.IsAny<CancellationToken>()))
.ReturnsAsync(true);
instance.Setup(mock => mock.ValidateRedirectUriAsync(application, "http://www.fabrikam.com/path", It.IsAny<CancellationToken>()))
.ReturnsAsync(true);
@ -447,6 +492,9 @@ namespace OpenIddict.Tests {
instance.Setup(mock => mock.FindByClientIdAsync("Fabrikam", It.IsAny<CancellationToken>()))
.ReturnsAsync(application);
instance.Setup(mock => mock.HasRedirectUriAsync(application, It.IsAny<CancellationToken>()))
.ReturnsAsync(true);
instance.Setup(mock => mock.ValidateRedirectUriAsync(application, "http://www.fabrikam.com/path", It.IsAny<CancellationToken>()))
.ReturnsAsync(true);
@ -507,6 +555,9 @@ namespace OpenIddict.Tests {
instance.Setup(mock => mock.FindByClientIdAsync("Fabrikam", It.IsAny<CancellationToken>()))
.ReturnsAsync(application);
instance.Setup(mock => mock.HasRedirectUriAsync(application, It.IsAny<CancellationToken>()))
.ReturnsAsync(true);
instance.Setup(mock => mock.ValidateRedirectUriAsync(application, "http://www.fabrikam.com/path", It.IsAny<CancellationToken>()))
.ReturnsAsync(true);
@ -544,6 +595,9 @@ namespace OpenIddict.Tests {
instance.Setup(mock => mock.FindByClientIdAsync("Fabrikam", It.IsAny<CancellationToken>()))
.ReturnsAsync(application);
instance.Setup(mock => mock.HasRedirectUriAsync(application, It.IsAny<CancellationToken>()))
.ReturnsAsync(true);
instance.Setup(mock => mock.ValidateRedirectUriAsync(application, "http://www.fabrikam.com/path", It.IsAny<CancellationToken>()))
.ReturnsAsync(true);

6
test/OpenIddict.Tests/OpenIddictProviderTests.Exchange.cs

@ -272,7 +272,7 @@ namespace OpenIddict.Tests {
instance.Setup(mock => mock.GetClientTypeAsync(application, It.IsAny<CancellationToken>()))
.ReturnsAsync(OpenIddictConstants.ClientTypes.Confidential);
instance.Setup(mock => mock.ValidateSecretAsync(application, "7Fjfp0ZBr1KtDRbnfVdmIw", It.IsAny<CancellationToken>()))
instance.Setup(mock => mock.ValidateClientSecretAsync(application, "7Fjfp0ZBr1KtDRbnfVdmIw", It.IsAny<CancellationToken>()))
.ReturnsAsync(false);
});
@ -297,7 +297,7 @@ namespace OpenIddict.Tests {
Mock.Get(manager).Verify(mock => mock.FindByClientIdAsync("Fabrikam", It.IsAny<CancellationToken>()), Times.Once());
Mock.Get(manager).Verify(mock => mock.GetClientTypeAsync(application, It.IsAny<CancellationToken>()), Times.Once());
Mock.Get(manager).Verify(mock => mock.ValidateSecretAsync(application, "7Fjfp0ZBr1KtDRbnfVdmIw", It.IsAny<CancellationToken>()), Times.Once());
Mock.Get(manager).Verify(mock => mock.ValidateClientSecretAsync(application, "7Fjfp0ZBr1KtDRbnfVdmIw", It.IsAny<CancellationToken>()), Times.Once());
}
[Fact]
@ -661,7 +661,7 @@ namespace OpenIddict.Tests {
instance.Setup(mock => mock.GetClientTypeAsync(application, It.IsAny<CancellationToken>()))
.ReturnsAsync(OpenIddictConstants.ClientTypes.Confidential);
instance.Setup(mock => mock.ValidateSecretAsync(application, "7Fjfp0ZBr1KtDRbnfVdmIw", It.IsAny<CancellationToken>()))
instance.Setup(mock => mock.ValidateClientSecretAsync(application, "7Fjfp0ZBr1KtDRbnfVdmIw", It.IsAny<CancellationToken>()))
.ReturnsAsync(true);
}));

14
test/OpenIddict.Tests/OpenIddictProviderTests.Introspection.cs

@ -130,7 +130,7 @@ namespace OpenIddict.Tests {
instance.Setup(mock => mock.GetClientTypeAsync(application, It.IsAny<CancellationToken>()))
.ReturnsAsync(OpenIddictConstants.ClientTypes.Confidential);
instance.Setup(mock => mock.ValidateSecretAsync(application, "7Fjfp0ZBr1KtDRbnfVdmIw", It.IsAny<CancellationToken>()))
instance.Setup(mock => mock.ValidateClientSecretAsync(application, "7Fjfp0ZBr1KtDRbnfVdmIw", It.IsAny<CancellationToken>()))
.ReturnsAsync(false);
});
@ -153,7 +153,7 @@ namespace OpenIddict.Tests {
Mock.Get(manager).Verify(mock => mock.FindByClientIdAsync("Fabrikam", It.IsAny<CancellationToken>()), Times.Once());
Mock.Get(manager).Verify(mock => mock.GetClientTypeAsync(application, It.IsAny<CancellationToken>()), Times.Once());
Mock.Get(manager).Verify(mock => mock.ValidateSecretAsync(application, "7Fjfp0ZBr1KtDRbnfVdmIw", It.IsAny<CancellationToken>()), Times.Once());
Mock.Get(manager).Verify(mock => mock.ValidateClientSecretAsync(application, "7Fjfp0ZBr1KtDRbnfVdmIw", It.IsAny<CancellationToken>()), Times.Once());
}
[Fact]
@ -186,7 +186,7 @@ namespace OpenIddict.Tests {
instance.Setup(mock => mock.GetClientTypeAsync(application, It.IsAny<CancellationToken>()))
.ReturnsAsync(OpenIddictConstants.ClientTypes.Confidential);
instance.Setup(mock => mock.ValidateSecretAsync(application, "7Fjfp0ZBr1KtDRbnfVdmIw", It.IsAny<CancellationToken>()))
instance.Setup(mock => mock.ValidateClientSecretAsync(application, "7Fjfp0ZBr1KtDRbnfVdmIw", It.IsAny<CancellationToken>()))
.ReturnsAsync(true);
}));
@ -236,7 +236,7 @@ namespace OpenIddict.Tests {
instance.Setup(mock => mock.GetClientTypeAsync(application, It.IsAny<CancellationToken>()))
.ReturnsAsync(OpenIddictConstants.ClientTypes.Confidential);
instance.Setup(mock => mock.ValidateSecretAsync(application, "7Fjfp0ZBr1KtDRbnfVdmIw", It.IsAny<CancellationToken>()))
instance.Setup(mock => mock.ValidateClientSecretAsync(application, "7Fjfp0ZBr1KtDRbnfVdmIw", It.IsAny<CancellationToken>()))
.ReturnsAsync(true);
}));
@ -288,7 +288,7 @@ namespace OpenIddict.Tests {
instance.Setup(mock => mock.GetClientTypeAsync(application, It.IsAny<CancellationToken>()))
.ReturnsAsync(OpenIddictConstants.ClientTypes.Confidential);
instance.Setup(mock => mock.ValidateSecretAsync(application, "7Fjfp0ZBr1KtDRbnfVdmIw", It.IsAny<CancellationToken>()))
instance.Setup(mock => mock.ValidateClientSecretAsync(application, "7Fjfp0ZBr1KtDRbnfVdmIw", It.IsAny<CancellationToken>()))
.ReturnsAsync(true);
}));
@ -345,7 +345,7 @@ namespace OpenIddict.Tests {
instance.Setup(mock => mock.GetClientTypeAsync(application, It.IsAny<CancellationToken>()))
.ReturnsAsync(OpenIddictConstants.ClientTypes.Confidential);
instance.Setup(mock => mock.ValidateSecretAsync(application, "7Fjfp0ZBr1KtDRbnfVdmIw", It.IsAny<CancellationToken>()))
instance.Setup(mock => mock.ValidateClientSecretAsync(application, "7Fjfp0ZBr1KtDRbnfVdmIw", It.IsAny<CancellationToken>()))
.ReturnsAsync(true);
}));
@ -404,7 +404,7 @@ namespace OpenIddict.Tests {
instance.Setup(mock => mock.GetClientTypeAsync(application, It.IsAny<CancellationToken>()))
.ReturnsAsync(OpenIddictConstants.ClientTypes.Confidential);
instance.Setup(mock => mock.ValidateSecretAsync(application, "7Fjfp0ZBr1KtDRbnfVdmIw", It.IsAny<CancellationToken>()))
instance.Setup(mock => mock.ValidateClientSecretAsync(application, "7Fjfp0ZBr1KtDRbnfVdmIw", It.IsAny<CancellationToken>()))
.ReturnsAsync(true);
}));

4
test/OpenIddict.Tests/OpenIddictProviderTests.Revocation.cs

@ -169,7 +169,7 @@ namespace OpenIddict.Tests {
instance.Setup(mock => mock.GetClientTypeAsync(application, It.IsAny<CancellationToken>()))
.ReturnsAsync(OpenIddictConstants.ClientTypes.Confidential);
instance.Setup(mock => mock.ValidateSecretAsync(application, "7Fjfp0ZBr1KtDRbnfVdmIw", It.IsAny<CancellationToken>()))
instance.Setup(mock => mock.ValidateClientSecretAsync(application, "7Fjfp0ZBr1KtDRbnfVdmIw", It.IsAny<CancellationToken>()))
.ReturnsAsync(false);
});
@ -193,7 +193,7 @@ namespace OpenIddict.Tests {
Mock.Get(manager).Verify(mock => mock.FindByClientIdAsync("Fabrikam", It.IsAny<CancellationToken>()), Times.Once());
Mock.Get(manager).Verify(mock => mock.GetClientTypeAsync(application, It.IsAny<CancellationToken>()), Times.Once());
Mock.Get(manager).Verify(mock => mock.ValidateSecretAsync(application, "7Fjfp0ZBr1KtDRbnfVdmIw", It.IsAny<CancellationToken>()), Times.Once());
Mock.Get(manager).Verify(mock => mock.ValidateClientSecretAsync(application, "7Fjfp0ZBr1KtDRbnfVdmIw", It.IsAny<CancellationToken>()), Times.Once());
}
[Fact]

3
test/OpenIddict.Tests/OpenIddictProviderTests.Serialization.cs

@ -25,6 +25,9 @@ namespace OpenIddict.Tests {
instance.Setup(mock => mock.FindByClientIdAsync("Fabrikam", It.IsAny<CancellationToken>()))
.ReturnsAsync(application);
instance.Setup(mock => mock.HasRedirectUriAsync(application, It.IsAny<CancellationToken>()))
.ReturnsAsync(true);
instance.Setup(mock => mock.ValidateRedirectUriAsync(application, "http://www.fabrikam.com/path", It.IsAny<CancellationToken>()))
.ReturnsAsync(true);

Loading…
Cancel
Save