Browse Source

Remove OpenIddictDbContext and revamp OpenIddict/OpenIddict.Core

pull/290/head
Kévin Chalet 9 years ago
parent
commit
bb1d2e07be
  1. 7
      OpenIddict.sln
  2. 2
      samples/Mvc.Server/Controllers/AccountController.cs
  3. 5
      samples/Mvc.Server/Controllers/AuthorizationController.cs
  4. 12
      samples/Mvc.Server/Models/ApplicationDbContext.cs
  5. 34
      samples/Mvc.Server/Startup.cs
  6. 2
      samples/Mvc.Server/project.json
  7. 57
      src/OpenIddict.Core/Infrastructure/OpenIddictServices.cs
  8. 86
      src/OpenIddict.Core/Managers/OpenIddictApplicationManager.cs
  9. 18
      src/OpenIddict.Core/Managers/OpenIddictAuthorizationManager.cs
  10. 18
      src/OpenIddict.Core/Managers/OpenIddictScopeManager.cs
  11. 31
      src/OpenIddict.Core/Managers/OpenIddictTokenManager.cs
  12. 463
      src/OpenIddict.Core/OpenIddictBuilder.cs
  13. 2
      src/OpenIddict.Core/OpenIddictConstants.cs
  14. 184
      src/OpenIddict.Core/OpenIddictExtensions.cs
  15. 2
      src/OpenIddict.Core/Stores/IOpenIddictApplicationStore.cs
  16. 2
      src/OpenIddict.Core/Stores/IOpenIddictAuthorizationStore.cs
  17. 2
      src/OpenIddict.Core/Stores/IOpenIddictScopeStore.cs
  18. 2
      src/OpenIddict.Core/Stores/IOpenIddictTokenStore.cs
  19. 17
      src/OpenIddict.Core/project.json
  20. 2
      src/OpenIddict.EntityFramework/OpenIddict.EntityFramework.xproj
  21. 40
      src/OpenIddict.EntityFramework/OpenIddictCustomizer.cs
  22. 199
      src/OpenIddict.EntityFramework/OpenIddictDbContext.cs
  23. 27
      src/OpenIddict.EntityFramework/OpenIddictExtension.cs
  24. 151
      src/OpenIddict.EntityFramework/OpenIddictExtensions.cs
  25. 11
      src/OpenIddict.EntityFramework/Stores/OpenIddictApplicationStore.cs
  26. 11
      src/OpenIddict.EntityFramework/Stores/OpenIddictAuthorizationStore.cs
  27. 11
      src/OpenIddict.EntityFramework/Stores/OpenIddictScopeStore.cs
  28. 11
      src/OpenIddict.EntityFramework/Stores/OpenIddictTokenStore.cs
  29. 8
      src/OpenIddict.EntityFramework/project.json
  30. 21
      src/OpenIddict.Models/OpenIddict.Models.xproj
  31. 4
      src/OpenIddict.Models/OpenIddictApplication.cs
  32. 2
      src/OpenIddict.Models/OpenIddictAuthorization.cs
  33. 2
      src/OpenIddict.Models/OpenIddictScope.cs
  34. 2
      src/OpenIddict.Models/OpenIddictToken.cs
  35. 43
      src/OpenIddict.Models/project.json
  36. 11
      src/OpenIddict.Mvc/OpenIddictExtensions.cs
  37. 3
      src/OpenIddict.Mvc/project.json
  38. 811
      src/OpenIddict/OpenIddictExtensions.cs
  39. 2
      src/OpenIddict/OpenIddictOptions.cs
  40. 109
      src/OpenIddict/OpenIddictProvider.Authentication.cs
  41. 10
      src/OpenIddict/OpenIddictProvider.Discovery.cs
  42. 73
      src/OpenIddict/OpenIddictProvider.Exchange.cs
  43. 39
      src/OpenIddict/OpenIddictProvider.Introspection.cs
  44. 41
      src/OpenIddict/OpenIddictProvider.Revocation.cs
  45. 11
      src/OpenIddict/OpenIddictProvider.Serialization.cs
  46. 50
      src/OpenIddict/OpenIddictProvider.Session.cs
  47. 2
      src/OpenIddict/OpenIddictProvider.Userinfo.cs
  48. 4
      src/OpenIddict/OpenIddictProvider.cs
  49. 7
      src/OpenIddict/project.json
  50. 522
      test/OpenIddict.Core.Tests/OpenIddictBuilderTests.cs
  51. 228
      test/OpenIddict.Core.Tests/OpenIddictExtensionsTests.cs
  52. 14
      test/OpenIddict.Core.Tests/project.json
  53. 43
      test/OpenIddict.EntityFramework.Tests/OpenIddictExtensionsTests.cs
  54. 2
      test/OpenIddict.EntityFramework.Tests/project.json
  55. 3
      test/OpenIddict.Mvc.Tests/OpenIddictExtensionsTests.cs
  56. 2
      test/OpenIddict.Mvc.Tests/project.json
  57. 0
      test/OpenIddict.Tests/Certificate.pfx
  58. 526
      test/OpenIddict.Tests/OpenIddictBuilderTests.cs
  59. 721
      test/OpenIddict.Tests/OpenIddictExtensionsTests.cs
  60. 68
      test/OpenIddict.Tests/OpenIddictProviderTests.Authentication.cs
  61. 4
      test/OpenIddict.Tests/OpenIddictProviderTests.Discovery.cs
  62. 114
      test/OpenIddict.Tests/OpenIddictProviderTests.Exchange.cs
  63. 66
      test/OpenIddict.Tests/OpenIddictProviderTests.Introspection.cs
  64. 58
      test/OpenIddict.Tests/OpenIddictProviderTests.Revocation.cs
  65. 23
      test/OpenIddict.Tests/OpenIddictProviderTests.Serialization.cs
  66. 20
      test/OpenIddict.Tests/OpenIddictProviderTests.Session.cs
  67. 3
      test/OpenIddict.Tests/OpenIddictProviderTests.Userinfo.cs
  68. 33
      test/OpenIddict.Tests/OpenIddictProviderTests.cs
  69. 12
      test/OpenIddict.Tests/project.json

7
OpenIddict.sln

@ -29,6 +29,8 @@ Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "OpenIddict.Mvc.Tests", "tes
EndProject
Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "OpenIddict.Tests", "test\OpenIddict.Tests\OpenIddict.Tests.xproj", "{3E2FBDB3-DC82-4E97-8EBC-CC8B279110FF}"
EndProject
Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "OpenIddict.Models", "src\OpenIddict.Models\OpenIddict.Models.xproj", "{0102A6CC-41A6-4B34-B49E-65AFE95882BB}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@ -75,6 +77,10 @@ Global
{3E2FBDB3-DC82-4E97-8EBC-CC8B279110FF}.Debug|Any CPU.Build.0 = Debug|Any CPU
{3E2FBDB3-DC82-4E97-8EBC-CC8B279110FF}.Release|Any CPU.ActiveCfg = Release|Any CPU
{3E2FBDB3-DC82-4E97-8EBC-CC8B279110FF}.Release|Any CPU.Build.0 = Release|Any CPU
{0102A6CC-41A6-4B34-B49E-65AFE95882BB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{0102A6CC-41A6-4B34-B49E-65AFE95882BB}.Debug|Any CPU.Build.0 = Debug|Any CPU
{0102A6CC-41A6-4B34-B49E-65AFE95882BB}.Release|Any CPU.ActiveCfg = Release|Any CPU
{0102A6CC-41A6-4B34-B49E-65AFE95882BB}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@ -90,5 +96,6 @@ Global
{7831F17A-DF0B-42EC-841B-065A9B5BD786} = {5FC71D6A-A994-4F62-977F-88A7D25379D7}
{8B4B0CCC-711B-4F9D-9DE6-DD32BDD3BCCA} = {5FC71D6A-A994-4F62-977F-88A7D25379D7}
{3E2FBDB3-DC82-4E97-8EBC-CC8B279110FF} = {5FC71D6A-A994-4F62-977F-88A7D25379D7}
{0102A6CC-41A6-4B34-B49E-65AFE95882BB} = {D544447C-D701-46BB-9A5B-C76C612A596B}
EndGlobalSection
EndGlobal

2
samples/Mvc.Server/Controllers/AccountController.cs

@ -11,7 +11,7 @@ using Mvc.Server.Models;
using Mvc.Server.Services;
using Mvc.Server.ViewModels.Account;
using Newtonsoft.Json.Linq;
using OpenIddict;
using OpenIddict.Core;
namespace Mvc.Server.Controllers {
[Authorize]

5
samples/Mvc.Server/Controllers/AuthorizationController.cs

@ -18,7 +18,8 @@ using Mvc.Server.Helpers;
using Mvc.Server.Models;
using Mvc.Server.ViewModels.Authorization;
using Mvc.Server.ViewModels.Shared;
using OpenIddict;
using OpenIddict.Core;
using OpenIddict.Models;
namespace Mvc.Server {
public class AuthorizationController : Controller {
@ -42,7 +43,7 @@ namespace Mvc.Server {
[Authorize, HttpGet("~/connect/authorize")]
public async Task<IActionResult> Authorize(OpenIdConnectRequest request) {
// Retrieve the application details from the database.
var application = await _applicationManager.FindByClientIdAsync(request.ClientId);
var application = await _applicationManager.FindByClientIdAsync(request.ClientId, HttpContext.RequestAborted);
if (application == null) {
return View("Error", new ErrorViewModel {
Error = OpenIdConnectConstants.Errors.InvalidClient,

12
samples/Mvc.Server/Models/ApplicationDbContext.cs

@ -1,14 +1,18 @@
using System;
using Microsoft.AspNetCore.Identity.EntityFrameworkCore;
using Microsoft.AspNetCore.Identity.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore;
using OpenIddict;
using Microsoft.Extensions.DependencyInjection;
namespace Mvc.Server.Models {
public class ApplicationDbContext : OpenIddictDbContext<ApplicationUser, IdentityRole> {
public class ApplicationDbContext : IdentityDbContext<ApplicationUser> {
public ApplicationDbContext(DbContextOptions options)
: base(options) { }
protected override void OnModelCreating(ModelBuilder builder) {
// Register the entity sets needed by OpenIddict.
// Note: use the generic overload if you need
// to replace the default OpenIddict entities.
builder.UseOpenIddict();
base.OnModelCreating(builder);
// Customize the ASP.NET Identity model and override the defaults if needed.

34
samples/Mvc.Server/Startup.cs

@ -8,7 +8,8 @@ using Microsoft.Extensions.DependencyInjection;
using Mvc.Server.Models;
using Mvc.Server.Services;
using NWebsec.AspNetCore.Middleware;
using OpenIddict;
using OpenIddict.Core;
using OpenIddict.Models;
namespace Mvc.Server {
public class Startup {
@ -28,8 +29,11 @@ namespace Mvc.Server {
.AddEntityFrameworkStores<ApplicationDbContext>()
.AddDefaultTokenProviders();
// Register the OpenIddict services, including the default Entity Framework stores.
services.AddOpenIddict<ApplicationDbContext>()
// Register the OpenIddict services.
services.AddOpenIddict()
// Register the Entity Framework stores.
.AddEntityFrameworkStores<ApplicationDbContext>()
// Register the ASP.NET Core MVC binder used by OpenIddict.
// Note: if you don't call this method, you won't be able to
// bind OpenIdConnectRequest or OpenIdConnectResponse parameters.
@ -68,14 +72,14 @@ namespace Mvc.Server {
// On production, using a X.509 certificate stored in the machine store is recommended.
// You can generate a self-signed certificate using Pluralsight's self-cert utility:
// https://s3.amazonaws.com/pluralsight-free/keith-brown/samples/SelfCert.zip
//
// services.AddOpenIddict<ApplicationDbContext>()
//
// services.AddOpenIddict()
// .AddSigningCertificate("7D2A741FE34CC2C7369237A5F2078988E17A6A75");
//
//
// Alternatively, you can also store the certificate as an embedded .pfx resource
// directly in this assembly or in a file published alongside this project:
//
// services.AddOpenIddict<ApplicationDbContext>()
//
// services.AddOpenIddict()
// .AddSigningCertificate(
// assembly: typeof(Startup).GetTypeInfo().Assembly,
// resource: "Mvc.Server.Certificate.pfx",
@ -97,7 +101,7 @@ namespace Mvc.Server {
// Alternatively, you can also use the introspection middleware.
// Using it is recommended if your resource server is in a
// different application/separated from the authorization server.
//
//
// app.UseOAuthIntrospection(options => {
// options.AutomaticAuthenticate = true;
// options.AutomaticChallenge = true;
@ -143,11 +147,13 @@ namespace Mvc.Server {
app.ApplicationServices.GetRequiredService<DbContextOptions<ApplicationDbContext>>())) {
context.Database.EnsureCreated();
var applications = context.Set<OpenIddictApplication>();
// Add Mvc.Client to the known applications.
if (!context.Applications.Any()) {
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",
@ -155,7 +161,7 @@ namespace Mvc.Server {
// Type = OpenIddictConstants.ClientTypes.Confidential
// });
context.Applications.Add(new OpenIddictApplication {
applications.Add(new OpenIddictApplication {
ClientId = "myClient",
ClientSecret = Crypto.HashPassword("secret_secret_secret"),
DisplayName = "My client application",
@ -165,7 +171,7 @@ namespace Mvc.Server {
});
// 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
@ -173,7 +179,7 @@ namespace Mvc.Server {
// * Scope: openid email profile roles
// * Grant type: authorization code
// * Request access token locally: yes
context.Applications.Add(new OpenIddictApplication {
applications.Add(new OpenIddictApplication {
ClientId = "postman",
DisplayName = "Postman",
RedirectUri = "https://www.getpostman.com/oauth2/callback",

2
samples/Mvc.Server/project.json

@ -24,6 +24,7 @@
"Microsoft.AspNetCore.Authentication.Google": "1.0.0",
"Microsoft.AspNetCore.Authentication.Twitter": "1.0.0",
"Microsoft.AspNetCore.Diagnostics": "1.0.0",
"Microsoft.AspNetCore.Identity.EntityFrameworkCore": "1.0.0",
"Microsoft.AspNetCore.Mvc": "1.0.0",
"Microsoft.AspNetCore.Server.IISIntegration": "1.0.0",
"Microsoft.AspNetCore.Server.Kestrel": "1.0.0",
@ -36,6 +37,7 @@
"Microsoft.Extensions.Logging.Debug": "1.0.0",
"NWebsec.AspNetCore.Middleware": "1.0.0-gamma1-15",
"OpenIddict": { "target": "project" },
"OpenIddict.EntityFramework": { "target": "project" },
"OpenIddict.Mvc": { "target": "project" }
},

57
src/OpenIddict.Core/Infrastructure/OpenIddictServices.cs

@ -1,57 +0,0 @@
/*
* Licensed under the Apache License, Version 2.0 (http://www.apache.org/licenses/LICENSE-2.0)
* See https://github.com/openiddict/openiddict-core for more information concerning
* the license and the contributors participating to this project.
*/
using System;
using JetBrains.Annotations;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
namespace OpenIddict.Infrastructure {
/// <summary>
/// Exposes the common services used by OpenIddict.
/// </summary>
public class OpenIddictServices<TApplication, TAuthorization, TScope, TToken>
where TApplication : class where TAuthorization : class
where TScope : class where TToken : class {
public OpenIddictServices([NotNull] IServiceProvider services) {
Services = services;
}
/// <summary>
/// Gets the <see cref="OpenIddictApplicationManager{TApplication}"/>.
/// </summary>
public virtual OpenIddictApplicationManager<TApplication> Applications =>
Services.GetRequiredService<OpenIddictApplicationManager<TApplication>>();
/// <summary>
/// Gets the optional <see cref="HttpContext"/>.
/// </summary>
public virtual HttpContext Context => Services.GetService<IHttpContextAccessor>()?.HttpContext;
/// <summary>
/// Gets the <see cref="ILogger"/>.
/// </summary>
public virtual ILogger Logger =>
Services.GetRequiredService<ILogger<OpenIddictProvider<TApplication, TAuthorization, TScope, TToken>>>();
/// <summary>
/// Gets the <see cref="OpenIddictOptions"/>.
/// </summary>
public virtual OpenIddictOptions Options => Services.GetRequiredService<IOptions<OpenIddictOptions>>().Value;
/// <summary>
/// Gets the <see cref="IServiceProvider"/> used to resolve services.
/// </summary>
public virtual IServiceProvider Services { get; }
/// <summary>
/// Gets the <see cref="OpenIddictTokenManager{TToken}"/>.
/// </summary>
public virtual OpenIddictTokenManager<TToken> Tokens => Services.GetRequiredService<OpenIddictTokenManager<TToken>>();
}
}

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

@ -10,35 +10,21 @@ using System.Threading;
using System.Threading.Tasks;
using CryptoHelper;
using JetBrains.Annotations;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
namespace OpenIddict {
namespace OpenIddict.Core {
/// <summary>
/// Provides methods allowing to manage the applications stored in the store.
/// </summary>
/// <typeparam name="TApplication">The type of the Application entity.</typeparam>
public class OpenIddictApplicationManager<TApplication> where TApplication : class {
public OpenIddictApplicationManager(
[NotNull] IServiceProvider services,
[NotNull] IOpenIddictApplicationStore<TApplication> store,
[NotNull] ILogger<OpenIddictApplicationManager<TApplication>> logger) {
Context = services?.GetService<IHttpContextAccessor>()?.HttpContext;
Store = store;
Logger = logger;
}
/// <summary>
/// Gets the HTTP context associated with the current manager.
/// </summary>
protected HttpContext Context { get; }
/// <summary>
/// Gets the cancellation token used to abort async operations.
/// </summary>
protected CancellationToken CancellationToken => Context?.RequestAborted ?? CancellationToken.None;
/// <summary>
/// Gets the logger associated with the current manager.
/// </summary>
@ -53,68 +39,73 @@ namespace OpenIddict {
/// Creates a new application.
/// </summary>
/// <param name="application">The application to create.</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 Task<string> CreateAsync(TApplication application) {
public virtual Task<string> CreateAsync(TApplication application, CancellationToken cancellationToken) {
if (application == null) {
throw new ArgumentNullException(nameof(application));
}
return Store.CreateAsync(application, CancellationToken);
return Store.CreateAsync(application, cancellationToken);
}
/// <summary>
/// Retrieves an application using its unique identifier.
/// </summary>
/// <param name="identifier">The unique identifier 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 client application corresponding to the identifier.
/// </returns>
public virtual Task<TApplication> FindByIdAsync(string identifier) {
return Store.FindByIdAsync(identifier, CancellationToken);
public virtual Task<TApplication> FindByIdAsync(string identifier, CancellationToken cancellationToken) {
return Store.FindByIdAsync(identifier, cancellationToken);
}
/// <summary>
/// Retrieves an application using its client identifier.
/// </summary>
/// <param name="identifier">The client identifier 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 client application corresponding to the identifier.
/// </returns>
public virtual Task<TApplication> FindByClientIdAsync(string identifier) {
return Store.FindByClientIdAsync(identifier, CancellationToken);
public virtual Task<TApplication> FindByClientIdAsync(string identifier, CancellationToken cancellationToken) {
return Store.FindByClientIdAsync(identifier, cancellationToken);
}
/// <summary>
/// Retrieves an application using its post_logout_redirect_uri.
/// </summary>
/// <param name="url">The post_logout_redirect_uri 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 client application corresponding to the post_logout_redirect_uri.
/// </returns>
public virtual Task<TApplication> FindByLogoutRedirectUri(string url) {
return Store.FindByLogoutRedirectUri(url, CancellationToken);
public virtual Task<TApplication> FindByLogoutRedirectUri(string url, CancellationToken cancellationToken) {
return Store.FindByLogoutRedirectUri(url, cancellationToken);
}
/// <summary>
/// Retrieves the client type 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 type of the application (by default, "public").
/// </returns>
public virtual async Task<string> GetClientTypeAsync(TApplication application) {
public virtual async Task<string> GetClientTypeAsync(TApplication application, CancellationToken cancellationToken) {
if (application == null) {
throw new ArgumentNullException(nameof(application));
}
var type = await Store.GetClientTypeAsync(application, CancellationToken);
var type = await Store.GetClientTypeAsync(application, cancellationToken);
// Ensure the application type returned by the store is supported by the manager.
if (!string.Equals(type, OpenIddictConstants.ClientTypes.Confidential, StringComparison.OrdinalIgnoreCase) &&
@ -130,44 +121,50 @@ namespace OpenIddict {
/// Retrieves the display name 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 display name associated with the application.
/// </returns>
public virtual Task<string> GetDisplayNameAsync(TApplication application) {
public virtual Task<string> GetDisplayNameAsync(TApplication application, CancellationToken cancellationToken) {
if (application == null) {
throw new ArgumentNullException(nameof(application));
}
return Store.GetDisplayNameAsync(application, CancellationToken);
return Store.GetDisplayNameAsync(application, cancellationToken);
}
/// <summary>
/// Retrieves the token identifiers 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 tokens associated with the application.
/// </returns>
public virtual Task<IEnumerable<string>> GetTokensAsync(TApplication application) {
public virtual Task<IEnumerable<string>> GetTokensAsync(TApplication application, CancellationToken cancellationToken) {
if (application == null) {
throw new ArgumentNullException(nameof(application));
}
return Store.GetTokensAsync(application, CancellationToken);
return Store.GetTokensAsync(application, cancellationToken);
}
/// <summary>
/// Determines whether an application is a confidential client.
/// </summary>
/// <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 confidential client, <c>false</c> otherwise.</returns>
public async Task<bool> IsConfidentialAsync([NotNull] TApplication application) {
public async Task<bool> IsConfidentialAsync([NotNull] TApplication application, CancellationToken cancellationToken) {
if (application == null) {
throw new ArgumentNullException(nameof(application));
}
var type = await GetClientTypeAsync(application);
var type = await GetClientTypeAsync(application, cancellationToken);
if (string.IsNullOrEmpty(type)) {
return false;
}
return string.Equals(type, OpenIddictConstants.ClientTypes.Confidential, StringComparison.OrdinalIgnoreCase);
}
@ -176,13 +173,18 @@ namespace OpenIddict {
/// Determines whether an application is a public client.
/// </summary>
/// <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) {
public async Task<bool> IsPublicAsync(TApplication application, CancellationToken cancellationToken) {
if (application == null) {
throw new ArgumentNullException(nameof(application));
}
var type = await GetClientTypeAsync(application);
// Assume client applications are public if their type is not explicitly set.
var type = await GetClientTypeAsync(application, cancellationToken);
if (string.IsNullOrEmpty(type)) {
return true;
}
return string.Equals(type, OpenIddictConstants.ClientTypes.Public, StringComparison.OrdinalIgnoreCase);
}
@ -192,18 +194,19 @@ namespace OpenIddict {
/// </summary>
/// <param name="application">The application.</param>
/// <param name="address">The address that should be compared to the redirect_uri stored in the database.</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 the redirect_uri was valid.
/// </returns>
public virtual async Task<bool> ValidateRedirectUriAsync(TApplication application, string address) {
public virtual async Task<bool> ValidateRedirectUriAsync(TApplication application, string address, CancellationToken cancellationToken) {
if (application == null) {
throw new ArgumentNullException(nameof(application));
}
if (!string.Equals(address, await Store.GetRedirectUriAsync(application, CancellationToken), StringComparison.Ordinal)) {
if (!string.Equals(address, await Store.GetRedirectUriAsync(application, cancellationToken), StringComparison.Ordinal)) {
Logger.LogWarning("Client validation failed because {RedirectUri} was not a valid redirect_uri " +
"for {Client}", address, await GetDisplayNameAsync(application));
"for {Client}", address, await GetDisplayNameAsync(application, cancellationToken));
return false;
}
@ -216,24 +219,24 @@ namespace OpenIddict {
/// </summary>
/// <param name="application">The application.</param>
/// <param name="secret">The secret that should be compared to the client_secret stored in the database.</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>
/// <returns>
/// 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) {
public virtual async Task<bool> ValidateSecretAsync(TApplication application, string secret, CancellationToken cancellationToken) {
if (application == null) {
throw new ArgumentNullException(nameof(application));
}
var type = await GetClientTypeAsync(application);
if (type != OpenIddictConstants.ClientTypes.Confidential) {
if (!await IsConfidentialAsync(application, cancellationToken)) {
Logger.LogWarning("Client authentication cannot be enforced for non-confidential applications.");
return false;
}
var hash = await Store.GetHashedSecretAsync(application, CancellationToken);
var hash = await Store.GetHashedSecretAsync(application, cancellationToken);
if (string.IsNullOrEmpty(hash)) {
Logger.LogError("Client authentication failed for {Client} because " +
"no client secret was associated with the application.");
@ -242,7 +245,8 @@ namespace OpenIddict {
}
if (!Crypto.VerifyHashedPassword(hash, secret)) {
Logger.LogWarning("Client authentication failed for {Client}.", await GetDisplayNameAsync(application));
Logger.LogWarning("Client authentication failed for {Client}.",
await GetDisplayNameAsync(application, cancellationToken));
return false;
}

18
src/OpenIddict.Core/Managers/OpenIddictAuthorizationManager.cs

@ -4,38 +4,22 @@
* the license and the contributors participating to this project.
*/
using System;
using System.Threading;
using JetBrains.Annotations;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
namespace OpenIddict {
namespace OpenIddict.Core {
/// <summary>
/// Provides methods allowing to manage the authorizations stored in the store.
/// </summary>
/// <typeparam name="TAuthorization">The type of the Authorization entity.</typeparam>
public class OpenIddictAuthorizationManager<TAuthorization> where TAuthorization : class {
public OpenIddictAuthorizationManager(
[NotNull] IServiceProvider services,
[NotNull] IOpenIddictAuthorizationStore<TAuthorization> store,
[NotNull] ILogger<OpenIddictAuthorizationManager<TAuthorization>> logger) {
Context = services?.GetService<IHttpContextAccessor>()?.HttpContext;
Logger = logger;
Store = store;
}
/// <summary>
/// Gets the HTTP context associated with the current manager.
/// </summary>
protected HttpContext Context { get; }
/// <summary>
/// Gets the cancellation token used to abort async operations.
/// </summary>
protected CancellationToken CancellationToken => Context?.RequestAborted ?? CancellationToken.None;
/// <summary>
/// Gets the logger associated with the current manager.
/// </summary>

18
src/OpenIddict.Core/Managers/OpenIddictScopeManager.cs

@ -4,38 +4,22 @@
* the license and the contributors participating to this project.
*/
using System;
using System.Threading;
using JetBrains.Annotations;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
namespace OpenIddict {
namespace OpenIddict.Core {
/// <summary>
/// Provides methods allowing to manage the scopes stored in the store.
/// </summary>
/// <typeparam name="TScope">The type of the Scope entity.</typeparam>
public class OpenIddictScopeManager<TScope> where TScope : class {
public OpenIddictScopeManager(
[NotNull] IServiceProvider services,
[NotNull] IOpenIddictScopeStore<TScope> store,
[NotNull] ILogger<OpenIddictScopeManager<TScope>> logger) {
Context = services?.GetService<IHttpContextAccessor>()?.HttpContext;
Logger = logger;
Store = store;
}
/// <summary>
/// Gets the HTTP context associated with the current manager.
/// </summary>
protected HttpContext Context { get; }
/// <summary>
/// Gets the cancellation token used to abort async operations.
/// </summary>
protected CancellationToken CancellationToken => Context?.RequestAborted ?? CancellationToken.None;
/// <summary>
/// Gets the logger associated with the current manager.
/// </summary>

31
src/OpenIddict.Core/Managers/OpenIddictTokenManager.cs

@ -8,35 +8,21 @@ using System;
using System.Threading;
using System.Threading.Tasks;
using JetBrains.Annotations;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
namespace OpenIddict {
namespace OpenIddict.Core {
/// <summary>
/// Provides methods allowing to manage the tokens stored in the store.
/// </summary>
/// <typeparam name="TToken">The type of the Token entity.</typeparam>
public class OpenIddictTokenManager<TToken> where TToken : class {
public OpenIddictTokenManager(
[NotNull] IServiceProvider services,
[NotNull] IOpenIddictTokenStore<TToken> store,
[NotNull] ILogger<OpenIddictTokenManager<TToken>> logger) {
Context = services?.GetService<IHttpContextAccessor>()?.HttpContext;
Logger = logger;
Store = store;
}
/// <summary>
/// Gets the cancellation token used to abort async operations.
/// </summary>
protected CancellationToken CancellationToken => Context?.RequestAborted ?? CancellationToken.None;
/// <summary>
/// Gets the HTTP context associated with the current manager.
/// </summary>
protected HttpContext Context { get; }
/// <summary>
/// Gets the logger associated with the current manager.
/// </summary>
@ -51,41 +37,44 @@ namespace OpenIddict {
/// Creates a new token, which is not associated with a particular user or client.
/// </summary>
/// <param name="type">The token type.</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 token.
/// </returns>
public virtual Task<string> CreateAsync(string type) {
public virtual Task<string> CreateAsync(string type, CancellationToken cancellationToken) {
if (string.IsNullOrEmpty(type)) {
throw new ArgumentException("The token type cannot be null or empty.", nameof(type));
}
return Store.CreateAsync(type, CancellationToken);
return Store.CreateAsync(type, cancellationToken);
}
/// <summary>
/// Retrieves a token using its unique identifier.
/// </summary>
/// <param name="identifier">The unique identifier associated with the token.</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 token corresponding to the unique identifier.
/// </returns>
public virtual Task<TToken> FindByIdAsync(string identifier) {
return Store.FindByIdAsync(identifier, CancellationToken);
public virtual Task<TToken> FindByIdAsync(string identifier, CancellationToken cancellationToken) {
return Store.FindByIdAsync(identifier, cancellationToken);
}
/// <summary>
/// Revokes a token.
/// </summary>
/// <param name="token">The token to revoke.</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 RevokeAsync(TToken token) {
public virtual Task RevokeAsync(TToken token, CancellationToken cancellationToken) {
if (token == null) {
throw new ArgumentNullException(nameof(token));
}
return Store.RevokeAsync(token, CancellationToken);
return Store.RevokeAsync(token, cancellationToken);
}
}
}

463
src/OpenIddict.Core/OpenIddictBuilder.cs

@ -6,19 +6,14 @@
using System;
using System.ComponentModel;
using System.IdentityModel.Tokens.Jwt;
using System.IO;
using System.Reflection;
using System.Security.Cryptography.X509Certificates;
using AspNet.Security.OpenIdConnect.Primitives;
using JetBrains.Annotations;
using Microsoft.AspNetCore.DataProtection;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.IdentityModel.Tokens;
using OpenIddict;
using OpenIddict.Core;
#if NETSTANDARD1_3
using System.Reflection;
#endif
namespace Microsoft.AspNetCore.Builder {
namespace Microsoft.Extensions.DependencyInjection {
/// <summary>
/// Exposes the necessary methods required to configure OpenIddict.
/// </summary>
@ -27,7 +22,11 @@ namespace Microsoft.AspNetCore.Builder {
/// Initializes a new instance of <see cref="OpenIddictBuilder"/>.
/// </summary>
/// <param name="services">The services collection.</param>
public OpenIddictBuilder(IServiceCollection services) {
public OpenIddictBuilder([NotNull] IServiceCollection services) {
if (services == null) {
throw new ArgumentNullException(nameof(services));
}
Services = services;
}
@ -61,22 +60,6 @@ namespace Microsoft.AspNetCore.Builder {
[EditorBrowsable(EditorBrowsableState.Never)]
public IServiceCollection Services { get; }
/// <summary>
/// Amends the default OpenIddict configuration.
/// </summary>
/// <param name="configuration">The delegate used to configure the OpenIddict options.</param>
/// <remarks>This extension can be safely called multiple times.</remarks>
/// <returns>The <see cref="OpenIddictBuilder"/>.</returns>
public virtual OpenIddictBuilder Configure([NotNull] Action<OpenIddictOptions> configuration) {
if (configuration == null) {
throw new ArgumentNullException(nameof(configuration));
}
Services.Configure(configuration);
return this;
}
/// <summary>
/// Adds a custom application manager.
/// </summary>
@ -300,429 +283,5 @@ namespace Microsoft.AspNetCore.Builder {
return this;
}
/// <summary>
/// Registers a new ephemeral key used to sign the tokens issued by OpenIddict: the key
/// is discarded when the application shuts down and tokens signed using this key are
/// automatically invalidated. This method should only be used during development.
/// On production, using a X.509 certificate stored in the machine store is recommended.
/// </summary>
/// <returns>The <see cref="OpenIddictBuilder"/>.</returns>
public virtual OpenIddictBuilder AddEphemeralSigningKey() {
return Configure(options => options.SigningCredentials.AddEphemeralKey());
}
/// <summary>
/// Registers a new ephemeral key used to sign the tokens issued by OpenIddict: the key
/// is discarded when the application shuts down and tokens signed using this key are
/// automatically invalidated. This method should only be used during development.
/// On production, using a X.509 certificate stored in the machine store is recommended.
/// </summary>
/// <param name="algorithm">The algorithm associated with the signing key.</param>
/// <returns>The <see cref="OpenIddictBuilder"/>.</returns>
public virtual OpenIddictBuilder AddEphemeralSigningKey([NotNull] string algorithm) {
if (string.IsNullOrEmpty(algorithm)) {
throw new ArgumentException("The algorithm cannot be null or empty.", nameof(algorithm));
}
return Configure(options => options.SigningCredentials.AddEphemeralKey(algorithm));
}
/// <summary>
/// Registers a <see cref="X509Certificate2"/> that is used to sign the tokens issued by OpenIddict.
/// </summary>
/// <param name="certificate">The certificate used to sign the security tokens issued by the server.</param>
/// <returns>The <see cref="OpenIddictBuilder"/>.</returns>
public virtual OpenIddictBuilder AddSigningCertificate([NotNull] X509Certificate2 certificate) {
if (certificate == null) {
throw new ArgumentNullException(nameof(certificate));
}
if (!certificate.HasPrivateKey) {
throw new InvalidOperationException("The certificate doesn't contain the required private key.");
}
return Configure(options => options.SigningCredentials.AddCertificate(certificate));
}
/// <summary>
/// Registers a <see cref="X509Certificate2"/> retrieved from an
/// embedded resource and used to sign the tokens issued by OpenIddict.
/// </summary>
/// <param name="assembly">The assembly containing the certificate.</param>
/// <param name="resource">The name of the embedded resource.</param>
/// <param name="password">The password used to open the certificate.</param>
/// <returns>The <see cref="OpenIddictBuilder"/>.</returns>
public virtual OpenIddictBuilder AddSigningCertificate(
[NotNull] Assembly assembly, [NotNull] string resource, [NotNull] string password) {
if (assembly == null) {
throw new ArgumentNullException(nameof(assembly));
}
if (string.IsNullOrEmpty(resource)) {
throw new ArgumentNullException(nameof(resource));
}
if (string.IsNullOrEmpty(password)) {
throw new ArgumentNullException(nameof(password));
}
return Configure(options => options.SigningCredentials.AddCertificate(assembly, resource, password));
}
/// <summary>
/// Registers a <see cref="X509Certificate2"/> extracted from a
/// stream and used to sign the tokens issued by OpenIddict.
/// </summary>
/// <param name="stream">The stream containing the certificate.</param>
/// <param name="password">The password used to open the certificate.</param>
/// <returns>The <see cref="OpenIddictBuilder"/>.</returns>
public virtual OpenIddictBuilder AddSigningCertificate([NotNull] Stream stream, [NotNull] string password) {
if (stream == null) {
throw new ArgumentNullException(nameof(stream));
}
if (string.IsNullOrEmpty(password)) {
throw new ArgumentNullException(nameof(password));
}
return Configure(options => options.SigningCredentials.AddCertificate(stream, password));
}
/// <summary>
/// Registers a <see cref="X509Certificate2"/> extracted from a
/// stream and used to sign the tokens issued by OpenIddict.
/// </summary>
/// <param name="stream">The stream containing the certificate.</param>
/// <param name="password">The password used to open the certificate.</param>
/// <param name="flags">
/// An enumeration of flags indicating how and where
/// to store the private key of the certificate.
/// </param>
/// <returns>The <see cref="OpenIddictBuilder"/>.</returns>
public virtual OpenIddictBuilder AddSigningCertificate(
[NotNull] Stream stream, [NotNull] string password, X509KeyStorageFlags flags) {
if (stream == null) {
throw new ArgumentNullException(nameof(stream));
}
if (string.IsNullOrEmpty(password)) {
throw new ArgumentNullException(nameof(password));
}
return Configure(options => options.SigningCredentials.AddCertificate(stream, password, flags));
}
/// <summary>
/// Registers a <see cref="X509Certificate2"/> retrieved from the X.509
/// machine store and used to sign the tokens issued by OpenIddict.
/// </summary>
/// <param name="thumbprint">The thumbprint of the certificate used to identify it in the X.509 store.</param>
/// <returns>The <see cref="OpenIddictBuilder"/>.</returns>
public virtual OpenIddictBuilder AddSigningCertificate([NotNull] string thumbprint) {
if (string.IsNullOrEmpty(thumbprint)) {
throw new ArgumentNullException(nameof(thumbprint));
}
return Configure(options => options.SigningCredentials.AddCertificate(thumbprint));
}
/// <summary>
/// Registers a <see cref="X509Certificate2"/> retrieved from the given
/// X.509 store and used to sign the tokens issued by OpenIddict.
/// </summary>
/// <param name="thumbprint">The thumbprint of the certificate used to identify it in the X.509 store.</param>
/// <param name="name">The name of the X.509 store.</param>
/// <param name="location">The location of the X.509 store.</param>
/// <returns>The <see cref="OpenIddictBuilder"/>.</returns>
public virtual OpenIddictBuilder AddSigningCertificate(
[NotNull] string thumbprint, StoreName name, StoreLocation location) {
if (string.IsNullOrEmpty(thumbprint)) {
throw new ArgumentNullException(nameof(thumbprint));
}
return Configure(options => options.SigningCredentials.AddCertificate(thumbprint, name, location));
}
/// <summary>
/// Registers a <see cref="SecurityKey"/> used to sign the tokens issued by OpenIddict.
/// Note: using <see cref="RsaSecurityKey"/> asymmetric keys is recommended on production.
/// </summary>
/// <param name="key">The security key.</param>
/// <returns>The <see cref="OpenIddictBuilder"/>.</returns>
public virtual OpenIddictBuilder AddSigningKey([NotNull] SecurityKey key) {
if (key == null) {
throw new ArgumentNullException(nameof(key));
}
return Configure(options => options.SigningCredentials.AddKey(key));
}
/// <summary>
/// Enables authorization code flow support. For more information
/// about this specific OAuth2/OpenID Connect flow, visit
/// https://tools.ietf.org/html/rfc6749#section-4.1 and
/// http://openid.net/specs/openid-connect-core-1_0.html#CodeFlowAuth.
/// </summary>
/// <returns>The <see cref="OpenIddictBuilder"/>.</returns>
public virtual OpenIddictBuilder AllowAuthorizationCodeFlow() {
return Configure(options => options.GrantTypes.Add(
OpenIdConnectConstants.GrantTypes.AuthorizationCode));
}
/// <summary>
/// Enables client credentials flow support. For more information about this
/// specific OAuth2 flow, visit https://tools.ietf.org/html/rfc6749#section-4.4.
/// </summary>
/// <returns>The <see cref="OpenIddictBuilder"/>.</returns>
public virtual OpenIddictBuilder AllowClientCredentialsFlow() {
return Configure(options => options.GrantTypes.Add(
OpenIdConnectConstants.GrantTypes.ClientCredentials));
}
/// <summary>
/// Enables custom grant type support.
/// </summary>
/// <param name="type">The grant type associated with the flow.</param>
/// <returns>The <see cref="OpenIddictBuilder"/>.</returns>
public virtual OpenIddictBuilder AllowCustomFlow([NotNull] string type) {
if (string.IsNullOrEmpty(type)) {
throw new ArgumentException("The grant type cannot be null or empty.", nameof(type));
}
return Configure(options => options.GrantTypes.Add(type));
}
/// <summary>
/// Enables implicit flow support. For more information
/// about this specific OAuth2/OpenID Connect flow, visit
/// https://tools.ietf.org/html/rfc6749#section-4.2 and
/// http://openid.net/specs/openid-connect-core-1_0.html#ImplicitFlowAuth.
/// </summary>
/// <returns>The <see cref="OpenIddictBuilder"/>.</returns>
public virtual OpenIddictBuilder AllowImplicitFlow() {
return Configure(options => options.GrantTypes.Add(
OpenIdConnectConstants.GrantTypes.Implicit));
}
/// <summary>
/// Enables password flow support. For more information about this specific
/// OAuth2 flow, visit https://tools.ietf.org/html/rfc6749#section-4.3.
/// </summary>
/// <returns>The <see cref="OpenIddictBuilder"/>.</returns>
public virtual OpenIddictBuilder AllowPasswordFlow() {
return Configure(options => options.GrantTypes.Add(
OpenIdConnectConstants.GrantTypes.Password));
}
/// <summary>
/// Enables refresh token flow support. For more information about this
/// specific OAuth2 flow, visit https://tools.ietf.org/html/rfc6749#section-6.
/// </summary>
/// <returns>The <see cref="OpenIddictBuilder"/>.</returns>
public virtual OpenIddictBuilder AllowRefreshTokenFlow() {
return Configure(options => options.GrantTypes.Add(
OpenIdConnectConstants.GrantTypes.RefreshToken));
}
/// <summary>
/// Disables the configuration endpoint.
/// </summary>
/// <returns>The <see cref="OpenIddictBuilder"/>.</returns>
public virtual OpenIddictBuilder DisableConfigurationEndpoint() {
return Configure(options => options.ConfigurationEndpointPath = PathString.Empty);
}
/// <summary>
/// Disables the cryptography endpoint.
/// </summary>
/// <returns>The <see cref="OpenIddictBuilder"/>.</returns>
public virtual OpenIddictBuilder DisableCryptographyEndpoint() {
return Configure(options => options.CryptographyEndpointPath = PathString.Empty);
}
/// <summary>
/// Disables the HTTPS requirement during development.
/// </summary>
/// <returns>The <see cref="OpenIddictBuilder"/>.</returns>
public virtual OpenIddictBuilder DisableHttpsRequirement() {
return Configure(options => options.AllowInsecureHttp = true);
}
/// <summary>
/// Disables sliding expiration, which prevents OpenIddict from issuing a new
/// refresh token when receiving a grant_type=refresh_token token request.
/// </summary>
/// <returns>The <see cref="OpenIddictBuilder"/>.</returns>
public virtual OpenIddictBuilder DisableSlidingExpiration() {
return Configure(options => options.UseSlidingExpiration = false);
}
/// <summary>
/// Enables the authorization endpoint.
/// </summary>
/// <param name="path">The relative path of the authorization endpoint.</param>
/// <returns>The <see cref="OpenIddictBuilder"/>.</returns>
public virtual OpenIddictBuilder EnableAuthorizationEndpoint(PathString path) {
if (!path.HasValue) {
throw new ArgumentException("The path cannot be empty.", nameof(path));
}
return Configure(options => options.AuthorizationEndpointPath = path);
}
/// <summary>
/// Enables the introspection endpoint.
/// </summary>
/// <param name="path">The relative path of the logout endpoint.</param>
/// <returns>The <see cref="OpenIddictBuilder"/>.</returns>
public virtual OpenIddictBuilder EnableIntrospectionEndpoint(PathString path) {
if (!path.HasValue) {
throw new ArgumentException("The path cannot be empty.", nameof(path));
}
return Configure(options => options.IntrospectionEndpointPath = path);
}
/// <summary>
/// Enables the logout endpoint.
/// </summary>
/// <param name="path">The relative path of the logout endpoint.</param>
/// <returns>The <see cref="OpenIddictBuilder"/>.</returns>
public virtual OpenIddictBuilder EnableLogoutEndpoint(PathString path) {
if (!path.HasValue) {
throw new ArgumentException("The path cannot be empty.", nameof(path));
}
return Configure(options => options.LogoutEndpointPath = path);
}
/// <summary>
/// Enables request caching, so that both authorization and logout requests
/// are automatically stored in the distributed cache, which allows flowing
/// large payloads across requests. Enabling this option is recommended
/// when using external authentication providers or when large GET or POST
/// OpenID Connect authorization requests support is required.
/// </summary>
/// <returns>The <see cref="OpenIddictBuilder"/>.</returns>
public virtual OpenIddictBuilder EnableRequestCaching() {
return Configure(options => options.EnableRequestCaching = true);
}
/// <summary>
/// Enables the revocation endpoint.
/// </summary>
/// <param name="path">The relative path of the revocation endpoint.</param>
/// <returns>The <see cref="OpenIddictBuilder"/>.</returns>
public virtual OpenIddictBuilder EnableRevocationEndpoint(PathString path) {
if (!path.HasValue) {
throw new ArgumentException("The path cannot be empty.", nameof(path));
}
return Configure(options => options.RevocationEndpointPath = path);
}
/// <summary>
/// Enables the token endpoint.
/// </summary>
/// <param name="path">The relative path of the token endpoint.</param>
/// <returns>The <see cref="OpenIddictBuilder"/>.</returns>
public virtual OpenIddictBuilder EnableTokenEndpoint(PathString path) {
if (!path.HasValue) {
throw new ArgumentException("The path cannot be empty.", nameof(path));
}
return Configure(options => options.TokenEndpointPath = path);
}
/// <summary>
/// Enables the userinfo endpoint.
/// </summary>
/// <param name="path">The relative path of the userinfo endpoint.</param>
/// <returns>The <see cref="OpenIddictBuilder"/>.</returns>
public virtual OpenIddictBuilder EnableUserinfoEndpoint(PathString path) {
if (!path.HasValue) {
throw new ArgumentException("The path cannot be empty.", nameof(path));
}
return Configure(options => options.UserinfoEndpointPath = path);
}
/// <summary>
/// Makes client identification mandatory so that token and revocation
/// requests that don't specify a client_id are automatically rejected.
/// Note: enabling this option doesn't prevent public clients from using
/// the token and revocation endpoints, but specifying a client_id is required.
/// </summary>
public virtual OpenIddictBuilder RequireClientIdentification() {
return Configure(options => options.RequireClientIdentification = true);
}
/// <summary>
/// Sets the access token lifetime, after which client applications must retrieve
/// a new access token by making a grant_type=refresh_token token request
/// or a prompt=none authorization request, depending on the selected flow.
/// Using long-lived access tokens or tokens that never expire is not recommended.
/// </summary>
/// <param name="lifetime">The access token lifetime.</param>
/// <returns>The <see cref="OpenIddictBuilder"/>.</returns>
public virtual OpenIddictBuilder SetAccessTokenLifetime(TimeSpan lifetime) {
return Configure(options => options.AccessTokenLifetime = lifetime);
}
/// <summary>
/// Sets the authorization code lifetime, after which client applications
/// are unable to send a grant_type=authorization_code token request.
/// Using short-lived authorization codes is strongly recommended.
/// </summary>
/// <param name="lifetime">The authorization code lifetime.</param>
/// <returns>The <see cref="OpenIddictBuilder"/>.</returns>
public virtual OpenIddictBuilder SetAuthorizationCodeLifetime(TimeSpan lifetime) {
return Configure(options => options.AuthorizationCodeLifetime = lifetime);
}
/// <summary>
/// Sets the identity token lifetime, after which client
/// applications should refuse processing identity tokens.
/// </summary>
/// <param name="lifetime">The identity token lifetime.</param>
/// <returns>The <see cref="OpenIddictBuilder"/>.</returns>
public virtual OpenIddictBuilder SetIdentityTokenLifetime(TimeSpan lifetime) {
return Configure(options => options.IdentityTokenLifetime = lifetime);
}
/// <summary>
/// Sets the refresh token lifetime, after which client applications must get
/// a new authorization from the user. When sliding expiration is enabled,
/// a new refresh token is always issued to the client application,
/// which prolongs the validity period of the refresh token.
/// </summary>
/// <param name="lifetime">The refresh token lifetime.</param>
/// <returns>The <see cref="OpenIddictBuilder"/>.</returns>
public virtual OpenIddictBuilder SetRefreshTokenLifetime(TimeSpan lifetime) {
return Configure(options => options.RefreshTokenLifetime = lifetime);
}
/// <summary>
/// Configures OpenIddict to use a specific data protection provider
/// instead of relying on the default instance provided by the DI container.
/// </summary>
/// <param name="provider">The data protection provider used to create token protectors.</param>
/// <returns>The <see cref="OpenIddictBuilder"/>.</returns>
public virtual OpenIddictBuilder UseDataProtectionProvider(IDataProtectionProvider provider) {
if (provider == null) {
throw new ArgumentNullException(nameof(provider));
}
return Configure(options => options.DataProtectionProvider = provider);
}
/// <summary>
/// Sets JWT as the default token format for access tokens.
/// </summary>
/// <returns>The <see cref="OpenIddictBuilder"/>.</returns>
public virtual OpenIddictBuilder UseJsonWebTokens() {
return Configure(options => options.AccessTokenHandler = new JwtSecurityTokenHandler());
}
}
}

2
src/OpenIddict.Core/OpenIddictConstants.cs

@ -4,7 +4,7 @@
* the license and the contributors participating to this project.
*/
namespace OpenIddict {
namespace OpenIddict.Core {
public static class OpenIddictConstants {
public static class Claims {
public const string Roles = "roles";

184
src/OpenIddict.Core/OpenIddictExtensions.cs

@ -5,20 +5,48 @@
*/
using System;
using AspNet.Security.OpenIdConnect.Primitives;
using JetBrains.Annotations;
using Microsoft.Extensions.Caching.Distributed;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection.Extensions;
using Microsoft.Extensions.Options;
using OpenIddict;
using OpenIddict.Infrastructure;
using OpenIddict.Core;
using OpenIddict.Models;
namespace Microsoft.AspNetCore.Builder {
namespace Microsoft.Extensions.DependencyInjection {
public static class OpenIddictExtensions {
/// <summary>
/// Registers the OpenIddict core services in the DI container.
/// When using this method, custom stores must be manually registered.
/// Registers the default OpenIddict services in the DI container,
/// using the default entities and the default entity key type.
/// </summary>
/// <param name="services">The services collection.</param>
/// <returns>The <see cref="OpenIddictBuilder"/>.</returns>
public static OpenIddictBuilder AddOpenIddict([NotNull] this IServiceCollection services) {
if (services == null) {
throw new ArgumentNullException(nameof(services));
}
return services.AddOpenIddict<OpenIddictApplication, OpenIddictAuthorization, OpenIddictScope, OpenIddictToken>();
}
/// <summary>
/// Registers the default OpenIddict services in the DI container,
/// using the default entities and the specified entity key type.
/// </summary>
/// <typeparam name="TKey">The type of the entity primary keys.</typeparam>
/// <param name="services">The services collection.</param>
/// <returns>The <see cref="OpenIddictBuilder"/>.</returns>
public static OpenIddictBuilder AddOpenIddict<TKey>([NotNull] this IServiceCollection services)
where TKey : IEquatable<TKey> {
if (services == null) {
throw new ArgumentNullException(nameof(services));
}
return services.AddOpenIddict<OpenIddictApplication<TKey, OpenIddictToken<TKey>>,
OpenIddictAuthorization<TKey, OpenIddictToken<TKey>>,
OpenIddictScope<TKey>,
OpenIddictToken<TKey>>();
}
/// <summary>
/// Registers the default OpenIddict services in the DI container, using the specified entities.
/// </summary>
/// <typeparam name="TApplication">The type of the Application entity.</typeparam>
/// <typeparam name="TAuthorization">The type of the Authorization entity.</typeparam>
@ -26,8 +54,7 @@ namespace Microsoft.AspNetCore.Builder {
/// <typeparam name="TToken">The type of the Token entity.</typeparam>
/// <param name="services">The services collection.</param>
/// <returns>The <see cref="OpenIddictBuilder"/>.</returns>
public static OpenIddictBuilder AddOpenIddict<TApplication, TAuthorization, TScope, TToken>(
[NotNull] this IServiceCollection services)
public static OpenIddictBuilder AddOpenIddict<TApplication, TAuthorization, TScope, TToken>([NotNull] this IServiceCollection services)
where TApplication : class
where TAuthorization : class
where TScope : class
@ -36,6 +63,8 @@ namespace Microsoft.AspNetCore.Builder {
throw new ArgumentNullException(nameof(services));
}
services.AddOptions();
var builder = new OpenIddictBuilder(services) {
ApplicationType = typeof(TApplication),
AuthorizationType = typeof(TAuthorization),
@ -43,145 +72,14 @@ namespace Microsoft.AspNetCore.Builder {
TokenType = typeof(TToken)
};
// Register the authentication services, as they are
// required by the OpenID Connect server middleware.
builder.Services.AddAuthentication();
builder.Configure(options => {
// Register the OpenID Connect server provider in the OpenIddict options.
options.Provider = new OpenIddictProvider<TApplication, TAuthorization, TScope, TToken>();
});
// Register the OpenIddict core services in the DI container.
builder.Services.TryAddSingleton(builder);
builder.Services.TryAddScoped<OpenIddictApplicationManager<TApplication>>();
builder.Services.TryAddScoped<OpenIddictAuthorizationManager<TAuthorization>>();
builder.Services.TryAddScoped<OpenIddictScopeManager<TScope>>();
builder.Services.TryAddScoped<OpenIddictTokenManager<TToken>>();
builder.Services.TryAddScoped<OpenIddictServices<TApplication, TAuthorization, TScope, TToken>>();
return builder;
}
/// <summary>
/// Registers OpenIddict in the ASP.NET Core pipeline.
/// </summary>
/// <param name="app">The application builder used to register middleware instances.</param>
/// <returns>The <see cref="IApplicationBuilder"/>.</returns>
public static IApplicationBuilder UseOpenIddict([NotNull] this IApplicationBuilder app) {
if (app == null) {
throw new ArgumentNullException(nameof(app));
}
// Resolve the OpenIddict options from the DI container.
var options = app.ApplicationServices.GetRequiredService<IOptions<OpenIddictOptions>>().Value;
// When no distributed cache has been registered in the options,
// try to resolve it from the dependency injection container.
if (options.Cache == null) {
options.Cache = app.ApplicationServices.GetService<IDistributedCache>();
if (options.EnableRequestCaching && options.Cache == null) {
throw new InvalidOperationException("A distributed cache implementation must be registered in the OpenIddict options " +
"or in the dependency injection container when enabling request caching support.");
}
}
// Ensure at least one signing certificate/key has been registered.
if (options.SigningCredentials.Count == 0) {
throw new InvalidOperationException("At least one signing key must be registered. Consider registering a X.509 " +
"certificate using 'services.AddOpenIddict().AddSigningCertificate()' or call " +
"'services.AddOpenIddict().AddEphemeralSigningKey()' to use an ephemeral key.");
}
// Ensure at least one flow has been enabled.
if (options.GrantTypes.Count == 0) {
throw new InvalidOperationException("At least one OAuth2/OpenID Connect flow must be enabled.");
}
// Ensure the authorization endpoint has been enabled when
// the authorization code or implicit grants are supported.
if (!options.AuthorizationEndpointPath.HasValue && (options.IsAuthorizationCodeFlowEnabled() ||
options.IsImplicitFlowEnabled())) {
throw new InvalidOperationException("The authorization endpoint must be enabled to use " +
"the authorization code and implicit flows.");
}
// Ensure the token endpoint has been enabled when the authorization code,
// client credentials, password or refresh token grants are supported.
else if (!options.TokenEndpointPath.HasValue && (options.IsAuthorizationCodeFlowEnabled() ||
options.IsClientCredentialsFlowEnabled() ||
options.IsPasswordFlowEnabled() ||
options.IsRefreshTokenFlowEnabled())) {
throw new InvalidOperationException("The token endpoint must be enabled to use the authorization code, " +
"client credentials, password and refresh token flows.");
}
return app.UseOpenIdConnectServer(options);
}
/// <summary>
/// Determines whether the authorization code flow has been enabled.
/// </summary>
/// <param name="options">The OpenIddict options.</param>
/// <returns><c>true</c> if the authorization code flow has been enabled, <c>false</c> otherwise.</returns>
public static bool IsAuthorizationCodeFlowEnabled([NotNull] this OpenIddictOptions options) {
if (options == null) {
throw new ArgumentNullException(nameof(options));
}
return options.GrantTypes.Contains(OpenIdConnectConstants.GrantTypes.AuthorizationCode);
}
/// <summary>
/// Determines whether the client credentials flow has been enabled.
/// </summary>
/// <param name="options">The OpenIddict options.</param>
/// <returns><c>true</c> if the client credentials flow has been enabled, <c>false</c> otherwise.</returns>
public static bool IsClientCredentialsFlowEnabled([NotNull] this OpenIddictOptions options) {
if (options == null) {
throw new ArgumentNullException(nameof(options));
}
return options.GrantTypes.Contains(OpenIdConnectConstants.GrantTypes.ClientCredentials);
}
/// <summary>
/// Determines whether the implicit flow has been enabled.
/// </summary>
/// <param name="options">The OpenIddict options.</param>
/// <returns><c>true</c> if the implicit flow has been enabled, <c>false</c> otherwise.</returns>
public static bool IsImplicitFlowEnabled([NotNull] this OpenIddictOptions options) {
if (options == null) {
throw new ArgumentNullException(nameof(options));
}
return options.GrantTypes.Contains(OpenIdConnectConstants.GrantTypes.Implicit);
}
/// <summary>
/// Determines whether the password flow has been enabled.
/// </summary>
/// <param name="options">The OpenIddict options.</param>
/// <returns><c>true</c> if the password flow has been enabled, <c>false</c> otherwise.</returns>
public static bool IsPasswordFlowEnabled([NotNull] this OpenIddictOptions options) {
if (options == null) {
throw new ArgumentNullException(nameof(options));
}
return options.GrantTypes.Contains(OpenIdConnectConstants.GrantTypes.Password);
}
/// <summary>
/// Determines whether the refresh token flow has been enabled.
/// </summary>
/// <param name="options">The OpenIddict options.</param>
/// <returns><c>true</c> if the refresh token flow has been enabled, <c>false</c> otherwise.</returns>
public static bool IsRefreshTokenFlowEnabled([NotNull] this OpenIddictOptions options) {
if (options == null) {
throw new ArgumentNullException(nameof(options));
}
return options.GrantTypes.Contains(OpenIdConnectConstants.GrantTypes.RefreshToken);
}
}
}

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

@ -8,7 +8,7 @@ using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
namespace OpenIddict {
namespace OpenIddict.Core {
/// <summary>
/// Provides methods allowing to manage the applications stored in a database.
/// </summary>

2
src/OpenIddict.Core/Stores/IOpenIddictAuthorizationStore.cs

@ -4,7 +4,7 @@
* the license and the contributors participating to this project.
*/
namespace OpenIddict {
namespace OpenIddict.Core {
/// <summary>
/// Provides methods allowing to manage the authorizations stored in a database.
/// </summary>

2
src/OpenIddict.Core/Stores/IOpenIddictScopeStore.cs

@ -4,7 +4,7 @@
* the license and the contributors participating to this project.
*/
namespace OpenIddict {
namespace OpenIddict.Core {
/// <summary>
/// Provides methods allowing to manage the scopes stored in a database.
/// </summary>

2
src/OpenIddict.Core/Stores/IOpenIddictTokenStore.cs

@ -7,7 +7,7 @@
using System.Threading;
using System.Threading.Tasks;
namespace OpenIddict {
namespace OpenIddict.Core {
/// <summary>
/// Provides methods allowing to manage the tokens stored in a database.
/// </summary>

17
src/OpenIddict.Core/project.json

@ -1,5 +1,5 @@
{
"version": "1.0.0-beta1-*",
"version": "1.0.0-beta2-*",
"description": "Core components of OpenIddict.",
"authors": [ "Kévin Chalet" ],
@ -33,19 +33,24 @@
},
"dependencies": {
"AspNet.Security.OpenIdConnect.Server": "1.0.0-beta7-final",
"CryptoHelper": "2.0.0",
"JetBrains.Annotations": { "type": "build", "version": "10.1.4" },
"Microsoft.AspNetCore.Diagnostics.Abstractions": "1.0.0",
"Microsoft.Extensions.Caching.Abstractions": "1.0.0"
"Microsoft.Extensions.DependencyInjection.Abstractions": "1.0.0",
"Microsoft.Extensions.Logging.Abstractions": "1.0.0",
"Microsoft.Extensions.Options": "1.0.0",
"OpenIddict.Models": { "target": "project" }
},
"frameworks": {
"net451": { },
"netstandard1.4": {
"netstandard1.3": {
"dependencies": {
"System.Reflection.TypeExtensions": "4.1.0"
},
"imports": [
"dotnet5.5",
"dotnet5.4",
"portable-net451+win8"
]
}

2
src/OpenIddict.EntityFramework/OpenIddict.EntityFramework.xproj

@ -7,7 +7,7 @@
<Import Project="$(VSToolsPath)\DotNet\Microsoft.DotNet.Props" Condition="'$(VSToolsPath)' != ''" />
<PropertyGroup Label="Globals">
<ProjectGuid>d2450929-ed0e-420d-b475-327924f9701c</ProjectGuid>
<RootNamespace>OpenIddict.EF</RootNamespace>
<RootNamespace>OpenIddict.EntityFramework</RootNamespace>
<BaseIntermediateOutputPath Condition="'$(BaseIntermediateOutputPath)'=='' ">.\obj</BaseIntermediateOutputPath>
<OutputPath Condition="'$(OutputPath)'=='' ">.\bin\</OutputPath>
</PropertyGroup>

40
src/OpenIddict.EntityFramework/OpenIddictCustomizer.cs

@ -0,0 +1,40 @@
/*
* Licensed under the Apache License, Version 2.0 (http://www.apache.org/licenses/LICENSE-2.0)
* See https://github.com/openiddict/openiddict-core for more information concerning
* the license and the contributors participating to this project.
*/
using System;
using JetBrains.Annotations;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.Extensions.DependencyInjection;
using OpenIddict.Models;
namespace OpenIddict.EntityFramework {
/// <summary>
/// Represents a model customizer able to register the entity sets
/// required by the OpenIddict stack in an Entity Framework context.
/// </summary>
public class OpenIddictCustomizer<TApplication, TAuthorization, TScope, TToken, TKey> : ModelCustomizer
where TApplication : OpenIddictApplication<TKey, TToken>
where TAuthorization : OpenIddictAuthorization<TKey, TToken>
where TScope : OpenIddictScope<TKey>
where TToken : OpenIddictToken<TKey>
where TKey : IEquatable<TKey> {
public override void Customize([NotNull] ModelBuilder builder, [NotNull] DbContext context) {
if (builder == null) {
throw new ArgumentNullException(nameof(builder));
}
if (context == null) {
throw new ArgumentNullException(nameof(context));
}
// Register the OpenIddict entity sets.
builder.UseOpenIddict<TApplication, TAuthorization, TScope, TToken, TKey>();
base.Customize(builder, context);
}
}
}

199
src/OpenIddict.EntityFramework/OpenIddictDbContext.cs

@ -1,199 +0,0 @@
/*
* Licensed under the Apache License, Version 2.0 (http://www.apache.org/licenses/LICENSE-2.0)
* See https://github.com/openiddict/openiddict-core for more information concerning
* the license and the contributors participating to this project.
*/
using System;
using Microsoft.AspNetCore.Identity.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore;
namespace OpenIddict {
/// <summary>
/// Represents an OpenIddict-powered Entity Framework context.
/// </summary>
public class OpenIddictDbContext : OpenIddictDbContext<IdentityUser> {
/// <summary>
/// Initializes a new OpenIddict context without configuring the Entity Framework options.
/// </summary>
protected OpenIddictDbContext() { }
/// <summary>
/// Initializes a new OpenIddict context.
/// </summary>
/// <param name="options">The options used to configure the Entity Framework context.</param>
public OpenIddictDbContext(DbContextOptions options) : base(options) { }
}
/// <summary>
/// Represents an OpenIddict-powered Entity Framework context.
/// </summary>
/// <typeparam name="TUser">The type of the User entity.</typeparam>
public class OpenIddictDbContext<TUser> : OpenIddictDbContext<TUser, IdentityRole, OpenIddictApplication,
OpenIddictAuthorization,
OpenIddictScope,
OpenIddictToken, string>
where TUser : IdentityUser {
/// <summary>
/// Initializes a new OpenIddict context without configuring the Entity Framework options.
/// </summary>
protected OpenIddictDbContext() { }
/// <summary>
/// Initializes a new OpenIddict context.
/// </summary>
/// <param name="options">The options used to configure the Entity Framework context.</param>
public OpenIddictDbContext(DbContextOptions options) : base(options) { }
}
/// <summary>
/// Represents an OpenIddict-powered Entity Framework context.
/// </summary>
/// <typeparam name="TUser">The type of the User entity.</typeparam>
/// <typeparam name="TRole">The type of the Role entity.</typeparam>
public class OpenIddictDbContext<TUser, TRole> : OpenIddictDbContext<TUser, TRole, OpenIddictApplication,
OpenIddictAuthorization,
OpenIddictScope,
OpenIddictToken, string>
where TUser : IdentityUser
where TRole : IdentityRole {
/// <summary>
/// Initializes a new OpenIddict context without configuring the Entity Framework options.
/// </summary>
protected OpenIddictDbContext() { }
/// <summary>
/// Initializes a new OpenIddict context.
/// </summary>
/// <param name="options">The options used to configure the Entity Framework context.</param>
public OpenIddictDbContext(DbContextOptions options) : base(options) { }
}
/// <summary>
/// Represents an OpenIddict-powered Entity Framework context.
/// </summary>
/// <typeparam name="TUser">The type of the User entity.</typeparam>
/// <typeparam name="TRole">The type of the Role entity.</typeparam>
/// <typeparam name="TKey">The type of the primary key used by the Identity/OpenIddict entities.</typeparam>
public class OpenIddictDbContext<TUser, TRole, TKey> : OpenIddictDbContext<TUser, TRole, OpenIddictApplication<TKey>,
OpenIddictAuthorization<TKey>,
OpenIddictScope<TKey>,
OpenIddictToken<TKey>, TKey>
where TUser : IdentityUser<TKey>
where TRole : IdentityRole<TKey>
where TKey : IEquatable<TKey> {
/// <summary>
/// Initializes a new OpenIddict context without configuring the Entity Framework options.
/// </summary>
protected OpenIddictDbContext() { }
/// <summary>
/// Initializes a new OpenIddict context.
/// </summary>
/// <param name="options">The options used to configure the Entity Framework context.</param>
public OpenIddictDbContext(DbContextOptions options) : base(options) { }
}
/// <summary>
/// Represents an OpenIddict-powered Entity Framework context.
/// </summary>
/// <typeparam name="TUser">The type of the User entity.</typeparam>
/// <typeparam name="TRole">The type of the Role entity.</typeparam>
/// <typeparam name="TApplication">The type of the Application entity.</typeparam>
/// <typeparam name="TAuthorization">The type of the Authorization entity.</typeparam>
/// <typeparam name="TScope">The type of the Scope entity.</typeparam>
/// <typeparam name="TToken">The type of the Token entity.</typeparam>
/// <typeparam name="TKey">The type of the primary key used by the Identity/OpenIddict entities.</typeparam>
public class OpenIddictDbContext<TUser, TRole, TApplication, TAuthorization, TScope, TToken, TKey> : IdentityDbContext<TUser, TRole, TKey>
where TUser : IdentityUser<TKey>
where TRole : IdentityRole<TKey>
where TApplication : OpenIddictApplication<TKey, TToken>
where TAuthorization : OpenIddictAuthorization<TKey, TToken>
where TScope : OpenIddictScope<TKey>
where TToken : OpenIddictToken<TKey>
where TKey : IEquatable<TKey> {
/// <summary>
/// Initializes a new OpenIddict context without configuring the Entity Framework options.
/// </summary>
protected OpenIddictDbContext() { }
/// <summary>
/// Initializes a new OpenIddict context.
/// </summary>
/// <param name="options">The options used to configure the Entity Framework context.</param>
public OpenIddictDbContext(DbContextOptions options) : base(options) { }
/// <summary>
/// Gets or sets the database set containing the applications.
/// </summary>
public DbSet<TApplication> Applications { get; set; }
/// <summary>
/// Gets or sets the database set containing the authorizations.
/// </summary>
public DbSet<TAuthorization> Authorizations { get; set; }
/// <summary>
/// Gets or sets the database set containing the scopes.
/// </summary>
public DbSet<TScope> Scopes { get; set; }
/// <summary>
/// Gets or sets the database set containing the tokens.
/// </summary>
public DbSet<TToken> Tokens { get; set; }
/// <summary>
/// Registers the OpenIddict entities in the Entity Framework context.
/// </summary>
/// <param name="builder">The model builder used by Entity Framework.</param>
protected override void OnModelCreating(ModelBuilder builder) {
base.OnModelCreating(builder);
// Warning: optional foreign keys MUST NOT be added as CLR properties because
// Entity Framework would throw an exception due to the TKey generic parameter
// being non-nullable when using value types like short, int, long or Guid.
// Configure the TApplication entity.
builder.Entity<TApplication>(entity => {
entity.HasKey(application => application.Id);
entity.HasIndex(application => application.ClientId)
.IsUnique(unique: true);
entity.HasMany(application => application.Tokens)
.WithOne()
.HasForeignKey("ApplicationId")
.IsRequired(required: false);
entity.ToTable("OpenIddictApplications");
});
// Configure the TAuthorization entity.
builder.Entity<TAuthorization>(entity => {
entity.HasKey(authorization => authorization.Id);
entity.HasMany(authorization => authorization.Tokens)
.WithOne()
.HasForeignKey("AuthorizationId")
.IsRequired(required: false);
entity.ToTable("OpenIddictAuthorizations");
});
// Configure the TScope entity.
builder.Entity<TScope>(entity => {
entity.HasKey(scope => scope.Id);
entity.ToTable("OpenIddictScopes");
});
// Configure the TToken entity.
builder.Entity<TToken>(entity => {
entity.HasKey(token => token.Id);
entity.ToTable("OpenIddictTokens");
});
}
}
}

27
src/OpenIddict.EntityFramework/OpenIddictExtension.cs

@ -0,0 +1,27 @@
/*
* Licensed under the Apache License, Version 2.0 (http://www.apache.org/licenses/LICENSE-2.0)
* See https://github.com/openiddict/openiddict-core for more information concerning
* the license and the contributors participating to this project.
*/
using System;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.Extensions.DependencyInjection;
using OpenIddict.Models;
namespace OpenIddict.EntityFramework {
public class OpenIddictExtension<TApplication, TAuthorization, TScope, TToken, TKey> : IDbContextOptionsExtension
where TApplication : OpenIddictApplication<TKey, TToken>
where TAuthorization : OpenIddictAuthorization<TKey, TToken>
where TScope : OpenIddictScope<TKey>
where TToken : OpenIddictToken<TKey>
where TKey : IEquatable<TKey> {
public void ApplyServices(IServiceCollection services) {
if (services == null) {
throw new ArgumentNullException(nameof(services));
}
services.AddSingleton<IModelCustomizer, OpenIddictCustomizer<TApplication, TAuthorization, TScope, TToken, TKey>>();
}
}
}

151
src/OpenIddict.EntityFramework/OpenIddictExtensions.cs

@ -8,27 +8,32 @@ using System;
using System.Diagnostics;
using JetBrains.Annotations;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.Extensions.DependencyInjection.Extensions;
using OpenIddict;
using OpenIddict.Core;
using OpenIddict.EntityFramework;
using OpenIddict.Models;
namespace Microsoft.AspNetCore.Builder {
namespace Microsoft.Extensions.DependencyInjection {
public static class OpenIddictExtensions {
/// <summary>
/// Registers the Entity Framework stores.
/// Registers the Entity Framework stores. Note: when using the built-in Entity Framework stores,
/// the entities MUST be derived from the models contained in the OpenIddict.Models package.
/// </summary>
/// <param name="builder">The services builder used by OpenIddict to register new services.</param>
/// <returns>The <see cref="OpenIddictBuilder"/>.</returns>
public static OpenIddictBuilder AddEntityFramework<TContext>([NotNull] this OpenIddictBuilder builder)
public static OpenIddictBuilder AddEntityFrameworkStores<TContext>([NotNull] this OpenIddictBuilder builder)
where TContext : DbContext {
return builder.AddEntityFramework<TContext, string>();
return builder.AddEntityFrameworkStores<TContext, string>();
}
/// <summary>
/// Registers the Entity Framework stores.
/// Registers the Entity Framework stores. Note: when using the built-in Entity Framework stores,
/// the entities MUST be derived from the models contained in the OpenIddict.Models package.
/// </summary>
/// <param name="builder">The services builder used by OpenIddict to register new services.</param>
/// <returns>The <see cref="OpenIddictBuilder"/>.</returns>
public static OpenIddictBuilder AddEntityFramework<TContext, TKey>([NotNull] this OpenIddictBuilder builder)
public static OpenIddictBuilder AddEntityFrameworkStores<TContext, TKey>([NotNull] this OpenIddictBuilder builder)
where TContext : DbContext
where TKey : IEquatable<TKey> {
if (builder == null) {
@ -77,5 +82,137 @@ namespace Microsoft.AspNetCore.Builder {
return builder;
}
/// <summary>
/// Registers the OpenIddict entity sets in the Entity Framework context
/// using the default OpenIddict models and the default key type (string).
/// </summary>
/// <param name="builder">The builder used to configure the Entity Framework context.</param>
/// <returns>The Entity Framework context builder.</returns>
public static DbContextOptionsBuilder UseOpenIddict([NotNull] this DbContextOptionsBuilder builder) {
return builder.UseOpenIddict<OpenIddictApplication, OpenIddictAuthorization, OpenIddictScope, OpenIddictToken, string>();
}
/// <summary>
/// Registers the OpenIddict entity sets in the Entity Framework context
/// using the default OpenIddict models and the specified key type.
/// </summary>
/// <param name="builder">The builder used to configure the Entity Framework context.</param>
/// <returns>The Entity Framework context builder.</returns>
public static DbContextOptionsBuilder UseOpenIddict<TKey>([NotNull] this DbContextOptionsBuilder builder) where TKey : IEquatable<TKey> {
return builder.UseOpenIddict<OpenIddictApplication<TKey, OpenIddictToken<TKey>>,
OpenIddictAuthorization<TKey, OpenIddictToken<TKey>>,
OpenIddictScope<TKey>,
OpenIddictToken<TKey>, TKey>();
}
/// <summary>
/// Registers the OpenIddict entity sets in the Entity Framework context
/// using the specified entities and the specified key type.
/// </summary>
/// <param name="builder">The builder used to configure the Entity Framework context.</param>
/// <returns>The Entity Framework context builder.</returns>
public static DbContextOptionsBuilder UseOpenIddict<TApplication, TAuthorization, TScope, TToken, TKey>([NotNull] this DbContextOptionsBuilder builder)
where TApplication : OpenIddictApplication<TKey, TToken>
where TAuthorization : OpenIddictAuthorization<TKey, TToken>
where TScope : OpenIddictScope<TKey>
where TToken : OpenIddictToken<TKey>
where TKey : IEquatable<TKey> {
if (builder == null) {
throw new ArgumentNullException(nameof(builder));
}
var extension = new OpenIddictExtension<TApplication, TAuthorization, TScope, TToken, TKey>();
((IDbContextOptionsBuilderInfrastructure) builder).AddOrUpdateExtension(extension);
return builder;
}
/// <summary>
/// Registers the OpenIddict entity sets in the Entity Framework context
/// using the default OpenIddict models and the default key type (string).
/// </summary>
/// <param name="builder">The builder used to configure the Entity Framework context.</param>
/// <returns>The Entity Framework context builder.</returns>
public static ModelBuilder UseOpenIddict([NotNull] this ModelBuilder builder) {
return builder.UseOpenIddict<OpenIddictApplication, OpenIddictAuthorization, OpenIddictScope, OpenIddictToken, string>();
}
/// <summary>
/// Registers the OpenIddict entity sets in the Entity Framework context
/// using the default OpenIddict models and the specified key type.
/// </summary>
/// <param name="builder">The builder used to configure the Entity Framework context.</param>
/// <returns>The Entity Framework context builder.</returns>
public static ModelBuilder UseOpenIddict<TKey>([NotNull] this ModelBuilder builder) where TKey : IEquatable<TKey> {
return builder.UseOpenIddict<OpenIddictApplication<TKey, OpenIddictToken<TKey>>,
OpenIddictAuthorization<TKey, OpenIddictToken<TKey>>,
OpenIddictScope<TKey>,
OpenIddictToken<TKey>, TKey>();
}
/// <summary>
/// Registers the OpenIddict entity sets in the Entity Framework context
/// using the specified entities and the specified key type.
/// </summary>
/// <param name="builder">The builder used to configure the Entity Framework context.</param>
/// <returns>The Entity Framework context builder.</returns>
public static ModelBuilder UseOpenIddict<TApplication, TAuthorization, TScope, TToken, TKey>([NotNull] this ModelBuilder builder)
where TApplication : OpenIddictApplication<TKey, TToken>
where TAuthorization : OpenIddictAuthorization<TKey, TToken>
where TScope : OpenIddictScope<TKey>
where TToken : OpenIddictToken<TKey>
where TKey : IEquatable<TKey> {
if (builder == null) {
throw new ArgumentNullException(nameof(builder));
}
// Warning: optional foreign keys MUST NOT be added as CLR properties because
// Entity Framework would throw an exception due to the TKey generic parameter
// being non-nullable when using value types like short, int, long or Guid.
// Configure the TApplication entity.
builder.Entity<TApplication>(entity => {
entity.HasKey(application => application.Id);
entity.HasIndex("ClientId")
.IsUnique(unique: true);
entity.HasMany(application => application.Tokens)
.WithOne()
.HasForeignKey("ApplicationId")
.IsRequired(required: false);
entity.ToTable("OpenIddictApplications");
});
// Configure the TAuthorization entity.
builder.Entity<TAuthorization>(entity => {
entity.HasKey(authorization => authorization.Id);
entity.HasMany(application => application.Tokens)
.WithOne()
.HasForeignKey("AuthorizationId")
.IsRequired(required: false);
entity.ToTable("OpenIddictAuthorizations");
});
// Configure the TScope entity.
builder.Entity<TScope>(entity => {
entity.HasKey(scope => scope.Id);
entity.ToTable("OpenIddictScopes");
});
// Configure the TToken entity.
builder.Entity<TToken>(entity => {
entity.HasKey(token => token.Id);
entity.ToTable("OpenIddictTokens");
});
return builder;
}
}
}

11
src/OpenIddict.EntityFramework/Stores/OpenIddictApplicationStore.cs

@ -10,9 +10,12 @@ using System.ComponentModel;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using JetBrains.Annotations;
using Microsoft.EntityFrameworkCore;
using OpenIddict.Core;
using OpenIddict.Models;
namespace OpenIddict {
namespace OpenIddict.EntityFramework {
/// <summary>
/// Provides methods allowing to manage the applications stored in a database.
/// </summary>
@ -25,7 +28,11 @@ namespace OpenIddict {
where TToken : OpenIddictToken<TKey>, new()
where TContext : DbContext
where TKey : IEquatable<TKey> {
public OpenIddictApplicationStore(TContext context) {
public OpenIddictApplicationStore([NotNull] TContext context) {
if (context == null) {
throw new ArgumentNullException(nameof(context));
}
Context = context;
}

11
src/OpenIddict.EntityFramework/Stores/OpenIddictAuthorizationStore.cs

@ -5,9 +5,12 @@
*/
using System;
using JetBrains.Annotations;
using Microsoft.EntityFrameworkCore;
using OpenIddict.Core;
using OpenIddict.Models;
namespace OpenIddict {
namespace OpenIddict.EntityFramework {
/// <summary>
/// Provides methods allowing to manage the authorizations stored in a database.
/// </summary>
@ -20,7 +23,11 @@ namespace OpenIddict {
where TToken : OpenIddictToken<TKey>
where TContext : DbContext
where TKey : IEquatable<TKey> {
public OpenIddictAuthorizationStore(TContext context) {
public OpenIddictAuthorizationStore([NotNull] TContext context) {
if (context == null) {
throw new ArgumentNullException(nameof(context));
}
Context = context;
}

11
src/OpenIddict.EntityFramework/Stores/OpenIddictScopeStore.cs

@ -5,9 +5,12 @@
*/
using System;
using JetBrains.Annotations;
using Microsoft.EntityFrameworkCore;
using OpenIddict.Core;
using OpenIddict.Models;
namespace OpenIddict {
namespace OpenIddict.EntityFramework {
/// <summary>
/// Provides methods allowing to manage the scopes stored in a database.
/// </summary>
@ -18,7 +21,11 @@ namespace OpenIddict {
where TScope : OpenIddictScope<TKey>
where TContext : DbContext
where TKey : IEquatable<TKey> {
public OpenIddictScopeStore(TContext context) {
public OpenIddictScopeStore([NotNull] TContext context) {
if (context == null) {
throw new ArgumentNullException(nameof(context));
}
Context = context;
}

11
src/OpenIddict.EntityFramework/Stores/OpenIddictTokenStore.cs

@ -8,9 +8,12 @@ using System;
using System.ComponentModel;
using System.Threading;
using System.Threading.Tasks;
using JetBrains.Annotations;
using Microsoft.EntityFrameworkCore;
using OpenIddict.Core;
using OpenIddict.Models;
namespace OpenIddict {
namespace OpenIddict.EntityFramework {
/// <summary>
/// Provides methods allowing to manage the tokens stored in a database.
/// </summary>
@ -23,7 +26,11 @@ namespace OpenIddict {
where TAuthorization : OpenIddictAuthorization<TKey, TToken>
where TContext : DbContext
where TKey : IEquatable<TKey> {
public OpenIddictTokenStore(TContext context) {
public OpenIddictTokenStore([NotNull] TContext context) {
if (context == null) {
throw new ArgumentNullException(nameof(context));
}
Context = context;
}

8
src/OpenIddict.EntityFramework/project.json

@ -1,5 +1,5 @@
{
"version": "1.0.0-beta1-*",
"version": "1.0.0-beta2-*",
"description": "Entity Framework adapter for OpenIddict.",
"authors": [ "Kévin Chalet" ],
@ -34,7 +34,7 @@
"dependencies": {
"JetBrains.Annotations": { "type": "build", "version": "10.1.4" },
"Microsoft.AspNetCore.Identity.EntityFrameworkCore": "1.0.0",
"Microsoft.EntityFrameworkCore.Relational": "1.0.0",
"OpenIddict.Core": { "target": "project" }
},
@ -42,6 +42,10 @@
"net451": { },
"netstandard1.4": {
"dependencies": {
"System.ComponentModel.TypeConverter": "4.1.0"
},
"imports": [
"dotnet5.5",
"portable-net451+win8"

21
src/OpenIddict.Models/OpenIddict.Models.xproj

@ -0,0 +1,21 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="14.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<VisualStudioVersion Condition="'$(VisualStudioVersion)' == ''">14.0</VisualStudioVersion>
<VSToolsPath Condition="'$(VSToolsPath)' == ''">$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)</VSToolsPath>
</PropertyGroup>
<Import Project="$(VSToolsPath)\DotNet\Microsoft.DotNet.Props" Condition="'$(VSToolsPath)' != ''" />
<PropertyGroup Label="Globals">
<ProjectGuid>0102a6cc-41a6-4b34-b49e-65afe95882bb</ProjectGuid>
<RootNamespace>OpenIddict.Models</RootNamespace>
<BaseIntermediateOutputPath Condition="'$(BaseIntermediateOutputPath)'=='' ">.\obj</BaseIntermediateOutputPath>
<OutputPath Condition="'$(OutputPath)'=='' ">.\bin\</OutputPath>
<TargetFrameworkVersion>v4.5.1</TargetFrameworkVersion>
</PropertyGroup>
<PropertyGroup>
<SchemaVersion>2.0</SchemaVersion>
</PropertyGroup>
<Import Project="$(VSToolsPath)\DotNet\Microsoft.DotNet.targets" Condition="'$(VSToolsPath)' != ''" />
</Project>

4
src/OpenIddict.EntityFramework/Models/OpenIddictApplication.cs → src/OpenIddict.Models/OpenIddictApplication.cs

@ -7,7 +7,7 @@
using System;
using System.Collections.Generic;
namespace OpenIddict {
namespace OpenIddict.Models {
/// <summary>
/// Represents an OpenIddict application.
/// </summary>
@ -73,6 +73,6 @@ namespace OpenIddict {
/// Gets or sets the application type
/// associated with the current application.
/// </summary>
public virtual string Type { get; set; } = OpenIddictConstants.ClientTypes.Public;
public virtual string Type { get; set; } = "public";
}
}

2
src/OpenIddict.EntityFramework/Models/OpenIddictAuthorization.cs → src/OpenIddict.Models/OpenIddictAuthorization.cs

@ -7,7 +7,7 @@
using System;
using System.Collections.Generic;
namespace OpenIddict {
namespace OpenIddict.Models {
/// <summary>
/// Represents an OpenIddict authorization.
/// </summary>

2
src/OpenIddict.EntityFramework/Models/OpenIddictScope.cs → src/OpenIddict.Models/OpenIddictScope.cs

@ -6,7 +6,7 @@
using System;
namespace OpenIddict {
namespace OpenIddict.Models {
/// <summary>
/// Represents an OpenIddict scope.
/// </summary>

2
src/OpenIddict.EntityFramework/Models/OpenIddictToken.cs → src/OpenIddict.Models/OpenIddictToken.cs

@ -6,7 +6,7 @@
using System;
namespace OpenIddict {
namespace OpenIddict.Models {
/// <summary>
/// Represents an OpenIddict token.
/// </summary>

43
src/OpenIddict.Models/project.json

@ -0,0 +1,43 @@
{
"version": "1.0.0-beta2-*",
"description": "Provides default entities for OpenIddict, that can be used by the EntityFramework stores.",
"authors": [ "Kévin Chalet" ],
"packOptions": {
"owners": [ "Kévin Chalet" ],
"projectUrl": "https://github.com/openiddict/openiddict-core",
"iconUrl": "https://avatars3.githubusercontent.com/u/13908567?s=64",
"licenseUrl": "http://www.apache.org/licenses/LICENSE-2.0.html",
"repository": {
"type": "git",
"url": "git://github.com/openiddict/openiddict-core"
},
"tags": [
"aspnetcore",
"authentication",
"jwt",
"openidconnect",
"openiddict",
"security"
]
},
"buildOptions": {
"warningsAsErrors": true,
"nowarn": [ "CS1591" ],
"xmlDoc": true
},
"dependencies": {
"System.Collections": "4.0.11",
"System.Runtime": "4.1.0"
},
"frameworks": {
"netstandard1.0": { }
}
}

11
src/OpenIddict.Mvc/OpenIddictExtensions.cs

@ -7,10 +7,9 @@
using System;
using JetBrains.Annotations;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.DependencyInjection;
using OpenIddict.Mvc;
namespace Microsoft.AspNetCore.Builder {
namespace Microsoft.Extensions.DependencyInjection {
public static class OpenIddictExtensions {
/// <summary>
/// Registers the ASP.NET Core MVC model binders used by OpenIddict.
@ -23,6 +22,14 @@ namespace Microsoft.AspNetCore.Builder {
}
builder.Services.Configure<MvcOptions>(options => {
// Skip the binder registration if it was already added to the providers collection.
for (var index = 0; index < options.ModelBinderProviders.Count; index++) {
var provider = options.ModelBinderProviders[index];
if (provider is OpenIddictModelBinder) {
return;
}
}
options.ModelBinderProviders.Insert(0, new OpenIddictModelBinder());
});

3
src/OpenIddict.Mvc/project.json

@ -1,5 +1,5 @@
{
"version": "1.0.0-beta1-*",
"version": "1.0.0-beta2-*",
"description": "OpenIddict binders for ASP.NET Core MVC.",
"authors": [ "Kévin Chalet" ],
@ -33,6 +33,7 @@
},
"dependencies": {
"AspNet.Security.OpenIdConnect.Server": "1.0.0-beta7-final",
"JetBrains.Annotations": { "type": "build", "version": "10.1.4" },
"Microsoft.AspNetCore.Mvc.Core": "1.0.0",
"OpenIddict.Core": { "target": "project" }

811
src/OpenIddict/OpenIddictExtensions.cs

@ -5,80 +5,791 @@
*/
using System;
using System.IdentityModel.Tokens.Jwt;
using System.IO;
using System.Reflection;
using System.Security.Cryptography.X509Certificates;
using AspNet.Security.OpenIdConnect.Primitives;
using AspNet.Security.OpenIdConnect.Server;
using JetBrains.Annotations;
using Microsoft.EntityFrameworkCore;
using Microsoft.AspNetCore.DataProtection;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Caching.Distributed;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;
using Microsoft.IdentityModel.Tokens;
using OpenIddict;
namespace Microsoft.AspNetCore.Builder {
public static class OpenIddictExtensions {
/// <summary>
/// Registers the default OpenIddict services in the DI container,
/// including the Entity Framework stores and the built-in entities.
/// Registers OpenIddict in the ASP.NET Core pipeline.
/// </summary>
/// <typeparam name="TContext">The type of the Entity Framework database context.</typeparam>
/// <param name="services">The services collection.</param>
/// <param name="app">The application builder used to register middleware instances.</param>
/// <returns>The <see cref="IApplicationBuilder"/>.</returns>
public static IApplicationBuilder UseOpenIddict([NotNull] this IApplicationBuilder app) {
if (app == null) {
throw new ArgumentNullException(nameof(app));
}
// Resolve the OpenIddict builder from the DI container.
// If it cannot be found, throw an invalid operation exception.
var builder = app.ApplicationServices.GetService<OpenIddictBuilder>();
if (builder == null) {
throw new InvalidOperationException("The OpenIddict services cannot be resolved from the dependency injection container. " +
"Make sure 'services.AddOpenIddict()' is correctly called from 'ConfigureServices()'.");
}
// Resolve the OpenIddict options from the DI container.
var options = app.ApplicationServices.GetRequiredService<IOptions<OpenIddictOptions>>().Value;
// When no authorization provider has been registered in the options,
// create a new OpenIddictProvider instance using the specified entities.
if (options.Provider == null) {
options.Provider = (OpenIdConnectServerProvider) Activator.CreateInstance(
typeof(OpenIddictProvider<,,,>).MakeGenericType(
/* TApplication: */ builder.ApplicationType,
/* TAuthorization: */ builder.AuthorizationType,
/* TScope: */ builder.ScopeType,
/* TToken: */ builder.TokenType));
}
// When no distributed cache has been registered in the options,
// try to resolve it from the dependency injection container.
if (options.Cache == null) {
options.Cache = app.ApplicationServices.GetService<IDistributedCache>();
if (options.EnableRequestCaching && options.Cache == null) {
throw new InvalidOperationException("A distributed cache implementation must be registered in the OpenIddict options " +
"or in the dependency injection container when enabling request caching support.");
}
}
// Ensure at least one signing certificate/key has been registered.
if (options.SigningCredentials.Count == 0) {
throw new InvalidOperationException("At least one signing key must be registered. Consider registering a X.509 " +
"certificate using 'services.AddOpenIddict().AddSigningCertificate()' or call " +
"'services.AddOpenIddict().AddEphemeralSigningKey()' to use an ephemeral key.");
}
// Ensure at least one flow has been enabled.
if (options.GrantTypes.Count == 0) {
throw new InvalidOperationException("At least one OAuth2/OpenID Connect flow must be enabled.");
}
// Ensure the authorization endpoint has been enabled when
// the authorization code or implicit grants are supported.
if (!options.AuthorizationEndpointPath.HasValue && (options.IsAuthorizationCodeFlowEnabled() ||
options.IsImplicitFlowEnabled())) {
throw new InvalidOperationException("The authorization endpoint must be enabled to use " +
"the authorization code and implicit flows.");
}
// Ensure the token endpoint has been enabled when the authorization code,
// client credentials, password or refresh token grants are supported.
if (!options.TokenEndpointPath.HasValue && (options.IsAuthorizationCodeFlowEnabled() ||
options.IsClientCredentialsFlowEnabled() ||
options.IsPasswordFlowEnabled() ||
options.IsRefreshTokenFlowEnabled())) {
throw new InvalidOperationException("The token endpoint must be enabled to use the authorization code, " +
"client credentials, password and refresh token flows.");
}
return app.UseOpenIdConnectServer(options);
}
/// <summary>
/// Amends the default OpenIddict configuration.
/// </summary>
/// <param name="builder">The services builder used by OpenIddict to register new services.</param>
/// <param name="configuration">The delegate used to configure the OpenIddict options.</param>
/// <remarks>This extension can be safely called multiple times.</remarks>
/// <returns>The <see cref="OpenIddictBuilder"/>.</returns>
public static OpenIddictBuilder Configure(
[NotNull] this OpenIddictBuilder builder,
[NotNull] Action<OpenIddictOptions> configuration) {
if (builder == null) {
throw new ArgumentNullException(nameof(builder));
}
if (configuration == null) {
throw new ArgumentNullException(nameof(configuration));
}
builder.Services.Configure(configuration);
return builder;
}
/// <summary>
/// Registers a new ephemeral key used to sign the tokens issued by OpenIddict: the key
/// is discarded when the application shuts down and tokens signed using this key are
/// automatically invalidated. This method should only be used during development.
/// On production, using a X.509 certificate stored in the machine store is recommended.
/// </summary>
/// <param name="builder">The services builder used by OpenIddict to register new services.</param>
/// <returns>The <see cref="OpenIddictBuilder"/>.</returns>
public static OpenIddictBuilder AddEphemeralSigningKey([NotNull] this OpenIddictBuilder builder) {
if (builder == null) {
throw new ArgumentNullException(nameof(builder));
}
return builder.Configure(options => options.SigningCredentials.AddEphemeralKey());
}
/// <summary>
/// Registers a new ephemeral key used to sign the tokens issued by OpenIddict: the key
/// is discarded when the application shuts down and tokens signed using this key are
/// automatically invalidated. This method should only be used during development.
/// On production, using a X.509 certificate stored in the machine store is recommended.
/// </summary>
/// <param name="builder">The services builder used by OpenIddict to register new services.</param>
/// <param name="algorithm">The algorithm associated with the signing key.</param>
/// <returns>The <see cref="OpenIddictBuilder"/>.</returns>
public static OpenIddictBuilder AddEphemeralSigningKey(
[NotNull] this OpenIddictBuilder builder, [NotNull] string algorithm) {
if (builder == null) {
throw new ArgumentNullException(nameof(builder));
}
if (string.IsNullOrEmpty(algorithm)) {
throw new ArgumentException("The algorithm cannot be null or empty.", nameof(algorithm));
}
return builder.Configure(options => options.SigningCredentials.AddEphemeralKey(algorithm));
}
/// <summary>
/// Registers a <see cref="X509Certificate2"/> that is used to sign the tokens issued by OpenIddict.
/// </summary>
/// <param name="builder">The services builder used by OpenIddict to register new services.</param>
/// <param name="certificate">The certificate used to sign the security tokens issued by the server.</param>
/// <returns>The <see cref="OpenIddictBuilder"/>.</returns>
public static OpenIddictBuilder AddSigningCertificate(
[NotNull] this OpenIddictBuilder builder,
[NotNull] X509Certificate2 certificate) {
if (builder == null) {
throw new ArgumentNullException(nameof(builder));
}
if (certificate == null) {
throw new ArgumentNullException(nameof(certificate));
}
if (!certificate.HasPrivateKey) {
throw new InvalidOperationException("The certificate doesn't contain the required private key.");
}
return builder.Configure(options => options.SigningCredentials.AddCertificate(certificate));
}
/// <summary>
/// Registers a <see cref="X509Certificate2"/> retrieved from an
/// embedded resource and used to sign the tokens issued by OpenIddict.
/// </summary>
/// <param name="builder">The services builder used by OpenIddict to register new services.</param>
/// <param name="assembly">The assembly containing the certificate.</param>
/// <param name="resource">The name of the embedded resource.</param>
/// <param name="password">The password used to open the certificate.</param>
/// <returns>The <see cref="OpenIddictBuilder"/>.</returns>
public static OpenIddictBuilder AddSigningCertificate(
[NotNull] this OpenIddictBuilder builder, [NotNull] Assembly assembly,
[NotNull] string resource, [NotNull] string password) {
if (builder == null) {
throw new ArgumentNullException(nameof(builder));
}
if (assembly == null) {
throw new ArgumentNullException(nameof(assembly));
}
if (string.IsNullOrEmpty(resource)) {
throw new ArgumentNullException(nameof(resource));
}
if (string.IsNullOrEmpty(password)) {
throw new ArgumentNullException(nameof(password));
}
return builder.Configure(options => options.SigningCredentials.AddCertificate(assembly, resource, password));
}
/// <summary>
/// Registers a <see cref="X509Certificate2"/> extracted from a
/// stream and used to sign the tokens issued by OpenIddict.
/// </summary>
/// <param name="builder">The services builder used by OpenIddict to register new services.</param>
/// <param name="stream">The stream containing the certificate.</param>
/// <param name="password">The password used to open the certificate.</param>
/// <returns>The <see cref="OpenIddictBuilder"/>.</returns>
public static OpenIddictBuilder AddSigningCertificate(
[NotNull] this OpenIddictBuilder builder,
[NotNull] Stream stream, [NotNull] string password) {
if (builder == null) {
throw new ArgumentNullException(nameof(builder));
}
if (stream == null) {
throw new ArgumentNullException(nameof(stream));
}
if (string.IsNullOrEmpty(password)) {
throw new ArgumentNullException(nameof(password));
}
return builder.Configure(options => options.SigningCredentials.AddCertificate(stream, password));
}
/// <summary>
/// Registers a <see cref="X509Certificate2"/> extracted from a
/// stream and used to sign the tokens issued by OpenIddict.
/// </summary>
/// <param name="builder">The services builder used by OpenIddict to register new services.</param>
/// <param name="stream">The stream containing the certificate.</param>
/// <param name="password">The password used to open the certificate.</param>
/// <param name="flags">
/// An enumeration of flags indicating how and where
/// to store the private key of the certificate.
/// </param>
/// <returns>The <see cref="OpenIddictBuilder"/>.</returns>
public static OpenIddictBuilder AddSigningCertificate(
[NotNull] this OpenIddictBuilder builder, [NotNull] Stream stream,
[NotNull] string password, X509KeyStorageFlags flags) {
if (builder == null) {
throw new ArgumentNullException(nameof(builder));
}
if (stream == null) {
throw new ArgumentNullException(nameof(stream));
}
if (string.IsNullOrEmpty(password)) {
throw new ArgumentNullException(nameof(password));
}
return builder.Configure(options => options.SigningCredentials.AddCertificate(stream, password, flags));
}
/// <summary>
/// Registers a <see cref="X509Certificate2"/> retrieved from the X.509
/// machine store and used to sign the tokens issued by OpenIddict.
/// </summary>
/// <param name="builder">The services builder used by OpenIddict to register new services.</param>
/// <param name="thumbprint">The thumbprint of the certificate used to identify it in the X.509 store.</param>
/// <returns>The <see cref="OpenIddictBuilder"/>.</returns>
public static OpenIddictBuilder AddSigningCertificate(
[NotNull] this OpenIddictBuilder builder, [NotNull] string thumbprint) {
if (builder == null) {
throw new ArgumentNullException(nameof(builder));
}
if (string.IsNullOrEmpty(thumbprint)) {
throw new ArgumentNullException(nameof(thumbprint));
}
return builder.Configure(options => options.SigningCredentials.AddCertificate(thumbprint));
}
/// <summary>
/// Registers a <see cref="X509Certificate2"/> retrieved from the given
/// X.509 store and used to sign the tokens issued by OpenIddict.
/// </summary>
/// <param name="builder">The services builder used by OpenIddict to register new services.</param>
/// <param name="thumbprint">The thumbprint of the certificate used to identify it in the X.509 store.</param>
/// <param name="name">The name of the X.509 store.</param>
/// <param name="location">The location of the X.509 store.</param>
/// <returns>The <see cref="OpenIddictBuilder"/>.</returns>
public static OpenIddictBuilder AddSigningCertificate(
[NotNull] this OpenIddictBuilder builder,
[NotNull] string thumbprint, StoreName name, StoreLocation location) {
if (builder == null) {
throw new ArgumentNullException(nameof(builder));
}
if (string.IsNullOrEmpty(thumbprint)) {
throw new ArgumentNullException(nameof(thumbprint));
}
return builder.Configure(options => options.SigningCredentials.AddCertificate(thumbprint, name, location));
}
/// <summary>
/// Registers a <see cref="SecurityKey"/> used to sign the tokens issued by OpenIddict.
/// Note: using <see cref="RsaSecurityKey"/> asymmetric keys is recommended on production.
/// </summary>
/// <param name="builder">The services builder used by OpenIddict to register new services.</param>
/// <param name="key">The security key.</param>
/// <returns>The <see cref="OpenIddictBuilder"/>.</returns>
public static OpenIddictBuilder AddSigningKey(
[NotNull] this OpenIddictBuilder builder, [NotNull] SecurityKey key) {
if (builder == null) {
throw new ArgumentNullException(nameof(builder));
}
if (key == null) {
throw new ArgumentNullException(nameof(key));
}
return builder.Configure(options => options.SigningCredentials.AddKey(key));
}
/// <summary>
/// Enables authorization code flow support. For more information
/// about this specific OAuth2/OpenID Connect flow, visit
/// https://tools.ietf.org/html/rfc6749#section-4.1 and
/// http://openid.net/specs/openid-connect-core-1_0.html#CodeFlowAuth.
/// </summary>
/// <param name="builder">The services builder used by OpenIddict to register new services.</param>
/// <returns>The <see cref="OpenIddictBuilder"/>.</returns>
public static OpenIddictBuilder AllowAuthorizationCodeFlow([NotNull] this OpenIddictBuilder builder) {
if (builder == null) {
throw new ArgumentNullException(nameof(builder));
}
return builder.Configure(options => options.GrantTypes.Add(OpenIdConnectConstants.GrantTypes.AuthorizationCode));
}
/// <summary>
/// Enables client credentials flow support. For more information about this
/// specific OAuth2 flow, visit https://tools.ietf.org/html/rfc6749#section-4.4.
/// </summary>
/// <param name="builder">The services builder used by OpenIddict to register new services.</param>
/// <returns>The <see cref="OpenIddictBuilder"/>.</returns>
public static OpenIddictBuilder AllowClientCredentialsFlow([NotNull] this OpenIddictBuilder builder) {
if (builder == null) {
throw new ArgumentNullException(nameof(builder));
}
return builder.Configure(options => options.GrantTypes.Add(OpenIdConnectConstants.GrantTypes.ClientCredentials));
}
/// <summary>
/// Enables custom grant type support.
/// </summary>
/// <param name="builder">The services builder used by OpenIddict to register new services.</param>
/// <param name="type">The grant type associated with the flow.</param>
/// <returns>The <see cref="OpenIddictBuilder"/>.</returns>
public static OpenIddictBuilder AllowCustomFlow(
[NotNull] this OpenIddictBuilder builder, [NotNull] string type) {
if (builder == null) {
throw new ArgumentNullException(nameof(builder));
}
if (string.IsNullOrEmpty(type)) {
throw new ArgumentException("The grant type cannot be null or empty.", nameof(type));
}
return builder.Configure(options => options.GrantTypes.Add(type));
}
/// <summary>
/// Enables implicit flow support. For more information
/// about this specific OAuth2/OpenID Connect flow, visit
/// https://tools.ietf.org/html/rfc6749#section-4.2 and
/// http://openid.net/specs/openid-connect-core-1_0.html#ImplicitFlowAuth.
/// </summary>
/// <param name="builder">The services builder used by OpenIddict to register new services.</param>
/// <returns>The <see cref="OpenIddictBuilder"/>.</returns>
public static OpenIddictBuilder AllowImplicitFlow([NotNull] this OpenIddictBuilder builder) {
if (builder == null) {
throw new ArgumentNullException(nameof(builder));
}
return builder.Configure(options => options.GrantTypes.Add(OpenIdConnectConstants.GrantTypes.Implicit));
}
/// <summary>
/// Enables password flow support. For more information about this specific
/// OAuth2 flow, visit https://tools.ietf.org/html/rfc6749#section-4.3.
/// </summary>
/// <param name="builder">The services builder used by OpenIddict to register new services.</param>
/// <returns>The <see cref="OpenIddictBuilder"/>.</returns>
public static OpenIddictBuilder AllowPasswordFlow([NotNull] this OpenIddictBuilder builder) {
if (builder == null) {
throw new ArgumentNullException(nameof(builder));
}
return builder.Configure(options => options.GrantTypes.Add(OpenIdConnectConstants.GrantTypes.Password));
}
/// <summary>
/// Enables refresh token flow support. For more information about this
/// specific OAuth2 flow, visit https://tools.ietf.org/html/rfc6749#section-6.
/// </summary>
/// <param name="builder">The services builder used by OpenIddict to register new services.</param>
/// <returns>The <see cref="OpenIddictBuilder"/>.</returns>
public static OpenIddictBuilder AllowRefreshTokenFlow([NotNull] this OpenIddictBuilder builder) {
if (builder == null) {
throw new ArgumentNullException(nameof(builder));
}
return builder.Configure(options => options.GrantTypes.Add(OpenIdConnectConstants.GrantTypes.RefreshToken));
}
/// <summary>
/// Disables the configuration endpoint.
/// </summary>
/// <param name="builder">The services builder used by OpenIddict to register new services.</param>
/// <returns>The <see cref="OpenIddictBuilder"/>.</returns>
public static OpenIddictBuilder DisableConfigurationEndpoint([NotNull] this OpenIddictBuilder builder) {
if (builder == null) {
throw new ArgumentNullException(nameof(builder));
}
return builder.Configure(options => options.ConfigurationEndpointPath = PathString.Empty);
}
/// <summary>
/// Disables the cryptography endpoint.
/// </summary>
/// <param name="builder">The services builder used by OpenIddict to register new services.</param>
/// <returns>The <see cref="OpenIddictBuilder"/>.</returns>
public static OpenIddictBuilder DisableCryptographyEndpoint([NotNull] this OpenIddictBuilder builder) {
if (builder == null) {
throw new ArgumentNullException(nameof(builder));
}
return builder.Configure(options => options.CryptographyEndpointPath = PathString.Empty);
}
/// <summary>
/// Disables the HTTPS requirement during development.
/// </summary>
/// <param name="builder">The services builder used by OpenIddict to register new services.</param>
/// <returns>The <see cref="OpenIddictBuilder"/>.</returns>
public static OpenIddictBuilder DisableHttpsRequirement([NotNull] this OpenIddictBuilder builder) {
if (builder == null) {
throw new ArgumentNullException(nameof(builder));
}
return builder.Configure(options => options.AllowInsecureHttp = true);
}
/// <summary>
/// Disables sliding expiration, which prevents OpenIddict from issuing a new
/// refresh token when receiving a grant_type=refresh_token token request.
/// </summary>
/// <param name="builder">The services builder used by OpenIddict to register new services.</param>
/// <returns>The <see cref="OpenIddictBuilder"/>.</returns>
public static OpenIddictBuilder DisableSlidingExpiration([NotNull] this OpenIddictBuilder builder) {
if (builder == null) {
throw new ArgumentNullException(nameof(builder));
}
return builder.Configure(options => options.UseSlidingExpiration = false);
}
/// <summary>
/// Enables the authorization endpoint.
/// </summary>
/// <param name="builder">The services builder used by OpenIddict to register new services.</param>
/// <param name="path">The relative path of the authorization endpoint.</param>
/// <returns>The <see cref="OpenIddictBuilder"/>.</returns>
public static OpenIddictBuilder AddOpenIddict<TContext>([NotNull] this IServiceCollection services)
where TContext : DbContext {
if (services == null) {
throw new ArgumentNullException(nameof(services));
public static OpenIddictBuilder EnableAuthorizationEndpoint(
[NotNull] this OpenIddictBuilder builder, PathString path) {
if (builder == null) {
throw new ArgumentNullException(nameof(builder));
}
if (!path.HasValue) {
throw new ArgumentException("The path cannot be empty.", nameof(path));
}
return services.AddOpenIddict<OpenIddictApplication,
OpenIddictAuthorization,
OpenIddictScope,
OpenIddictToken, TContext, string>();
return builder.Configure(options => options.AuthorizationEndpointPath = path);
}
/// <summary>
/// Registers the default OpenIddict services in the DI container,
/// including the Entity Framework stores and the specified entities.
/// Enables the introspection endpoint.
/// </summary>
/// <typeparam name="TContext">The type of the Entity Framework database context.</typeparam>
/// <typeparam name="TKey">The type of the entity primary keys.</typeparam>
/// <param name="services">The services collection.</param>
/// <param name="builder">The services builder used by OpenIddict to register new services.</param>
/// <param name="path">The relative path of the logout endpoint.</param>
/// <returns>The <see cref="OpenIddictBuilder"/>.</returns>
public static OpenIddictBuilder AddOpenIddict<TContext, TKey>([NotNull] this IServiceCollection services)
where TContext : DbContext
where TKey : IEquatable<TKey> {
if (services == null) {
throw new ArgumentNullException(nameof(services));
public static OpenIddictBuilder EnableIntrospectionEndpoint(
[NotNull] this OpenIddictBuilder builder, PathString path) {
if (builder == null) {
throw new ArgumentNullException(nameof(builder));
}
if (!path.HasValue) {
throw new ArgumentException("The path cannot be empty.", nameof(path));
}
return builder.Configure(options => options.IntrospectionEndpointPath = path);
}
/// <summary>
/// Enables the logout endpoint.
/// </summary>
/// <param name="builder">The services builder used by OpenIddict to register new services.</param>
/// <param name="path">The relative path of the logout endpoint.</param>
/// <returns>The <see cref="OpenIddictBuilder"/>.</returns>
public static OpenIddictBuilder EnableLogoutEndpoint(
[NotNull] this OpenIddictBuilder builder, PathString path) {
if (builder == null) {
throw new ArgumentNullException(nameof(builder));
}
if (!path.HasValue) {
throw new ArgumentException("The path cannot be empty.", nameof(path));
}
return builder.Configure(options => options.LogoutEndpointPath = path);
}
/// <summary>
/// Enables request caching, so that both authorization and logout requests
/// are automatically stored in the distributed cache, which allows flowing
/// large payloads across requests. Enabling this option is recommended
/// when using external authentication providers or when large GET or POST
/// OpenID Connect authorization requests support is required.
/// </summary>
/// <param name="builder">The services builder used by OpenIddict to register new services.</param>
/// <returns>The <see cref="OpenIddictBuilder"/>.</returns>
public static OpenIddictBuilder EnableRequestCaching([NotNull] this OpenIddictBuilder builder) {
if (builder == null) {
throw new ArgumentNullException(nameof(builder));
}
return builder.Configure(options => options.EnableRequestCaching = true);
}
/// <summary>
/// Enables the revocation endpoint.
/// </summary>
/// <param name="builder">The services builder used by OpenIddict to register new services.</param>
/// <param name="path">The relative path of the revocation endpoint.</param>
/// <returns>The <see cref="OpenIddictBuilder"/>.</returns>
public static OpenIddictBuilder EnableRevocationEndpoint(
[NotNull] this OpenIddictBuilder builder, PathString path) {
if (builder == null) {
throw new ArgumentNullException(nameof(builder));
}
if (!path.HasValue) {
throw new ArgumentException("The path cannot be empty.", nameof(path));
}
return builder.Configure(options => options.RevocationEndpointPath = path);
}
/// <summary>
/// Enables the token endpoint.
/// </summary>
/// <param name="builder">The services builder used by OpenIddict to register new services.</param>
/// <param name="path">The relative path of the token endpoint.</param>
/// <returns>The <see cref="OpenIddictBuilder"/>.</returns>
public static OpenIddictBuilder EnableTokenEndpoint(
[NotNull] this OpenIddictBuilder builder, PathString path) {
if (builder == null) {
throw new ArgumentNullException(nameof(builder));
}
if (!path.HasValue) {
throw new ArgumentException("The path cannot be empty.", nameof(path));
}
return builder.Configure(options => options.TokenEndpointPath = path);
}
/// <summary>
/// Enables the userinfo endpoint.
/// </summary>
/// <param name="builder">The services builder used by OpenIddict to register new services.</param>
/// <param name="path">The relative path of the userinfo endpoint.</param>
/// <returns>The <see cref="OpenIddictBuilder"/>.</returns>
public static OpenIddictBuilder EnableUserinfoEndpoint(
[NotNull] this OpenIddictBuilder builder, PathString path) {
if (builder == null) {
throw new ArgumentNullException(nameof(builder));
}
if (!path.HasValue) {
throw new ArgumentException("The path cannot be empty.", nameof(path));
}
return builder.Configure(options => options.UserinfoEndpointPath = path);
}
/// <summary>
/// Makes client identification mandatory so that token and revocation
/// requests that don't specify a client_id are automatically rejected.
/// Note: enabling this option doesn't prevent public clients from using
/// the token and revocation endpoints, but specifying a client_id is required.
/// </summary>
/// <param name="builder">The services builder used by OpenIddict to register new services.</param>
public static OpenIddictBuilder RequireClientIdentification([NotNull] this OpenIddictBuilder builder) {
if (builder == null) {
throw new ArgumentNullException(nameof(builder));
}
return builder.Configure(options => options.RequireClientIdentification = true);
}
/// <summary>
/// Sets the access token lifetime, after which client applications must retrieve
/// a new access token by making a grant_type=refresh_token token request
/// or a prompt=none authorization request, depending on the selected flow.
/// Using long-lived access tokens or tokens that never expire is not recommended.
/// </summary>
/// <param name="builder">The services builder used by OpenIddict to register new services.</param>
/// <param name="lifetime">The access token lifetime.</param>
/// <returns>The <see cref="OpenIddictBuilder"/>.</returns>
public static OpenIddictBuilder SetAccessTokenLifetime(
[NotNull] this OpenIddictBuilder builder, TimeSpan lifetime) {
if (builder == null) {
throw new ArgumentNullException(nameof(builder));
}
return builder.Configure(options => options.AccessTokenLifetime = lifetime);
}
/// <summary>
/// Sets the authorization code lifetime, after which client applications
/// are unable to send a grant_type=authorization_code token request.
/// Using short-lived authorization codes is strongly recommended.
/// </summary>
/// <param name="builder">The services builder used by OpenIddict to register new services.</param>
/// <param name="lifetime">The authorization code lifetime.</param>
/// <returns>The <see cref="OpenIddictBuilder"/>.</returns>
public static OpenIddictBuilder SetAuthorizationCodeLifetime(
[NotNull] this OpenIddictBuilder builder, TimeSpan lifetime) {
if (builder == null) {
throw new ArgumentNullException(nameof(builder));
}
return builder.Configure(options => options.AuthorizationCodeLifetime = lifetime);
}
/// <summary>
/// Sets the identity token lifetime, after which client
/// applications should refuse processing identity tokens.
/// </summary>
/// <param name="builder">The services builder used by OpenIddict to register new services.</param>
/// <param name="lifetime">The identity token lifetime.</param>
/// <returns>The <see cref="OpenIddictBuilder"/>.</returns>
public static OpenIddictBuilder SetIdentityTokenLifetime(
[NotNull] this OpenIddictBuilder builder, TimeSpan lifetime) {
if (builder == null) {
throw new ArgumentNullException(nameof(builder));
}
return builder.Configure(options => options.IdentityTokenLifetime = lifetime);
}
/// <summary>
/// Sets the refresh token lifetime, after which client applications must get
/// a new authorization from the user. When sliding expiration is enabled,
/// a new refresh token is always issued to the client application,
/// which prolongs the validity period of the refresh token.
/// </summary>
/// <param name="builder">The services builder used by OpenIddict to register new services.</param>
/// <param name="lifetime">The refresh token lifetime.</param>
/// <returns>The <see cref="OpenIddictBuilder"/>.</returns>
public static OpenIddictBuilder SetRefreshTokenLifetime(
[NotNull] this OpenIddictBuilder builder, TimeSpan lifetime) {
if (builder == null) {
throw new ArgumentNullException(nameof(builder));
}
return builder.Configure(options => options.RefreshTokenLifetime = lifetime);
}
/// <summary>
/// Configures OpenIddict to use a specific data protection provider
/// instead of relying on the default instance provided by the DI container.
/// </summary>
/// <param name="builder">The services builder used by OpenIddict to register new services.</param>
/// <param name="provider">The data protection provider used to create token protectors.</param>
/// <returns>The <see cref="OpenIddictBuilder"/>.</returns>
public static OpenIddictBuilder UseDataProtectionProvider(
[NotNull] this OpenIddictBuilder builder, [NotNull] IDataProtectionProvider provider) {
if (builder == null) {
throw new ArgumentNullException(nameof(builder));
}
if (provider == null) {
throw new ArgumentNullException(nameof(provider));
}
return builder.Configure(options => options.DataProtectionProvider = provider);
}
/// <summary>
/// Sets JWT as the default token format for access tokens.
/// </summary>
/// <param name="builder">The services builder used by OpenIddict to register new services.</param>
/// <returns>The <see cref="OpenIddictBuilder"/>.</returns>
public static OpenIddictBuilder UseJsonWebTokens([NotNull] this OpenIddictBuilder builder) {
if (builder == null) {
throw new ArgumentNullException(nameof(builder));
}
return builder.Configure(options => options.AccessTokenHandler = new JwtSecurityTokenHandler());
}
/// <summary>
/// Determines whether the authorization code flow has been enabled.
/// </summary>
/// <param name="options">The OpenIddict options.</param>
/// <returns><c>true</c> if the authorization code flow has been enabled, <c>false</c> otherwise.</returns>
public static bool IsAuthorizationCodeFlowEnabled([NotNull] this OpenIddictOptions options) {
if (options == null) {
throw new ArgumentNullException(nameof(options));
}
return options.GrantTypes.Contains(OpenIdConnectConstants.GrantTypes.AuthorizationCode);
}
/// <summary>
/// Determines whether the client credentials flow has been enabled.
/// </summary>
/// <param name="options">The OpenIddict options.</param>
/// <returns><c>true</c> if the client credentials flow has been enabled, <c>false</c> otherwise.</returns>
public static bool IsClientCredentialsFlowEnabled([NotNull] this OpenIddictOptions options) {
if (options == null) {
throw new ArgumentNullException(nameof(options));
}
return options.GrantTypes.Contains(OpenIdConnectConstants.GrantTypes.ClientCredentials);
}
/// <summary>
/// Determines whether the implicit flow has been enabled.
/// </summary>
/// <param name="options">The OpenIddict options.</param>
/// <returns><c>true</c> if the implicit flow has been enabled, <c>false</c> otherwise.</returns>
public static bool IsImplicitFlowEnabled([NotNull] this OpenIddictOptions options) {
if (options == null) {
throw new ArgumentNullException(nameof(options));
}
return options.GrantTypes.Contains(OpenIdConnectConstants.GrantTypes.Implicit);
}
/// <summary>
/// Determines whether the password flow has been enabled.
/// </summary>
/// <param name="options">The OpenIddict options.</param>
/// <returns><c>true</c> if the password flow has been enabled, <c>false</c> otherwise.</returns>
public static bool IsPasswordFlowEnabled([NotNull] this OpenIddictOptions options) {
if (options == null) {
throw new ArgumentNullException(nameof(options));
}
return services.AddOpenIddict<OpenIddictApplication<TKey>,
OpenIddictAuthorization<TKey>,
OpenIddictScope<TKey>,
OpenIddictToken<TKey>, TContext, TKey>();
return options.GrantTypes.Contains(OpenIdConnectConstants.GrantTypes.Password);
}
/// <summary>
/// Registers the default OpenIddict services in the DI container,
/// including the Entity Framework stores and the specified entities.
/// Determines whether the refresh token flow has been enabled.
/// </summary>
/// <typeparam name="TApplication">The type of the Application entity.</typeparam>
/// <typeparam name="TAuthorization">The type of the Authorization entity.</typeparam>
/// <typeparam name="TScope">The type of the Scope entity.</typeparam>
/// <typeparam name="TToken">The type of the Token entity.</typeparam>
/// <typeparam name="TContext">The type of the Entity Framework database context.</typeparam>
/// <typeparam name="TKey">The type of the entity primary keys.</typeparam>
/// <param name="services">The services collection.</param>
/// <returns>The <see cref="IServiceCollection"/>.</returns>
public static OpenIddictBuilder AddOpenIddict<TApplication, TAuthorization, TScope, TToken, TContext, TKey>(
[NotNull] this IServiceCollection services)
where TApplication : OpenIddictApplication<TKey, TToken>
where TAuthorization : OpenIddictAuthorization<TKey, TToken>
where TScope : OpenIddictScope<TKey>
where TToken : OpenIddictToken<TKey>
where TContext : DbContext
where TKey : IEquatable<TKey> {
if (services == null) {
throw new ArgumentNullException(nameof(services));
/// <param name="options">The OpenIddict options.</param>
/// <returns><c>true</c> if the refresh token flow has been enabled, <c>false</c> otherwise.</returns>
public static bool IsRefreshTokenFlowEnabled([NotNull] this OpenIddictOptions options) {
if (options == null) {
throw new ArgumentNullException(nameof(options));
}
// Register the OpenIddict core services and the default EntityFramework stores.
return services.AddOpenIddict<TApplication, TAuthorization, TScope, TToken>()
.AddEntityFramework<TContext, TKey>();
return options.GrantTypes.Contains(OpenIdConnectConstants.GrantTypes.RefreshToken);
}
}
}

2
src/OpenIddict.Core/OpenIddictOptions.cs → src/OpenIddict/OpenIddictOptions.cs

@ -15,6 +15,8 @@ namespace OpenIddict {
/// </summary>
public class OpenIddictOptions : OpenIdConnectServerOptions {
public OpenIddictOptions() {
Provider = null;
// Use the same lifespan as the default security stamp
// verification interval used by ASP.NET Core Identity.
AccessTokenLifetime = TimeSpan.FromMinutes(30);

109
src/OpenIddict.Core/Infrastructure/OpenIddictProvider.Authentication.cs → src/OpenIddict/OpenIddictProvider.Authentication.cs

@ -19,20 +19,23 @@ using Microsoft.AspNetCore.WebUtilities;
using Microsoft.Extensions.Caching.Distributed;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using Newtonsoft.Json;
using Newtonsoft.Json.Bson;
using Newtonsoft.Json.Linq;
using OpenIddict.Core;
namespace OpenIddict.Infrastructure {
namespace OpenIddict {
public partial class OpenIddictProvider<TApplication, TAuthorization, TScope, TToken> : OpenIdConnectServerProvider
where TApplication : class where TAuthorization : class where TScope : class where TToken : class {
public override async Task ExtractAuthorizationRequest([NotNull] ExtractAuthorizationRequestContext context) {
var services = context.HttpContext.RequestServices.GetRequiredService<OpenIddictServices<TApplication, TAuthorization, TScope, TToken>>();
var logger = context.HttpContext.RequestServices.GetRequiredService<ILogger<OpenIddictProvider<TApplication, TAuthorization, TScope, TToken>>>();
var options = context.HttpContext.RequestServices.GetRequiredService<IOptions<OpenIddictOptions>>();
// Reject requests using the unsupported request parameter.
if (!string.IsNullOrEmpty(context.Request.Request)) {
services.Logger.LogError("The authorization request was rejected because it contained " +
"an unsupported parameter: {Parameter}.", "request");
logger.LogError("The authorization request was rejected because it contained " +
"an unsupported parameter: {Parameter}.", "request");
context.Reject(
error: OpenIdConnectConstants.Errors.RequestNotSupported,
@ -43,8 +46,8 @@ namespace OpenIddict.Infrastructure {
// Reject requests using the unsupported request_uri parameter.
if (!string.IsNullOrEmpty(context.Request.RequestUri)) {
services.Logger.LogError("The authorization request was rejected because it contained " +
"an unsupported parameter: {Parameter}.", "request_uri");
logger.LogError("The authorization request was rejected because it contained " +
"an unsupported parameter: {Parameter}.", "request_uri");
context.Reject(
error: OpenIdConnectConstants.Errors.RequestUriNotSupported,
@ -57,9 +60,9 @@ namespace OpenIddict.Infrastructure {
// restore the complete authorization request from the distributed cache.
if (!string.IsNullOrEmpty(context.Request.RequestId)) {
// Return an error if request caching support was not enabled.
if (!services.Options.EnableRequestCaching) {
services.Logger.LogError("The authorization request was rejected because " +
"request caching support was not enabled.");
if (!options.Value.EnableRequestCaching) {
logger.LogError("The authorization request was rejected because " +
"request caching support was not enabled.");
context.Reject(
error: OpenIdConnectConstants.Errors.InvalidRequest,
@ -72,10 +75,10 @@ namespace OpenIddict.Infrastructure {
// to avoid collisions with the other types of cached requests.
var key = OpenIddictConstants.Environment.AuthorizationRequest + context.Request.RequestId;
var payload = await services.Options.Cache.GetAsync(key);
var payload = await options.Value.Cache.GetAsync(key);
if (payload == null) {
services.Logger.LogError("The authorization request was rejected because an unknown " +
"or invalid request_id parameter was specified.");
logger.LogError("The authorization request was rejected because an unknown " +
"or invalid request_id parameter was specified.");
context.Reject(
error: OpenIdConnectConstants.Errors.InvalidRequest,
@ -99,14 +102,16 @@ namespace OpenIddict.Infrastructure {
}
public override async Task ValidateAuthorizationRequest([NotNull] ValidateAuthorizationRequestContext context) {
var services = context.HttpContext.RequestServices.GetRequiredService<OpenIddictServices<TApplication, TAuthorization, TScope, TToken>>();
var applications = context.HttpContext.RequestServices.GetRequiredService<OpenIddictApplicationManager<TApplication>>();
var logger = context.HttpContext.RequestServices.GetRequiredService<ILogger<OpenIddictProvider<TApplication, TAuthorization, TScope, TToken>>>();
var options = context.HttpContext.RequestServices.GetRequiredService<IOptions<OpenIddictOptions>>();
// Note: the OpenID Connect server middleware supports authorization code, implicit, hybrid,
// none and custom flows but OpenIddict uses a stricter policy rejecting unknown flows.
if (!context.Request.IsAuthorizationCodeFlow() && !context.Request.IsHybridFlow() &&
!context.Request.IsImplicitFlow() && !context.Request.IsNoneFlow()) {
services.Logger.LogError("The authorization request was rejected because the '{ResponseType}' " +
"response type is not supported.", context.Request.ResponseType);
logger.LogError("The authorization request was rejected because the '{ResponseType}' " +
"response type is not supported.", context.Request.ResponseType);
context.Reject(
error: OpenIdConnectConstants.Errors.UnsupportedResponseType,
@ -116,9 +121,9 @@ namespace OpenIddict.Infrastructure {
}
// Reject code flow authorization requests if the authorization code flow is not enabled.
if (context.Request.IsAuthorizationCodeFlow() && !services.Options.IsAuthorizationCodeFlowEnabled()) {
services.Logger.LogError("The authorization request was rejected because " +
"the authorization code flow was not enabled.");
if (context.Request.IsAuthorizationCodeFlow() && !options.Value.IsAuthorizationCodeFlowEnabled()) {
logger.LogError("The authorization request was rejected because " +
"the authorization code flow was not enabled.");
context.Reject(
error: OpenIdConnectConstants.Errors.UnsupportedResponseType,
@ -128,8 +133,8 @@ namespace OpenIddict.Infrastructure {
}
// Reject implicit flow authorization requests if the implicit flow is not enabled.
if (context.Request.IsImplicitFlow() && !services.Options.IsImplicitFlowEnabled()) {
services.Logger.LogError("The authorization request was rejected because the implicit flow was not enabled.");
if (context.Request.IsImplicitFlow() && !options.Value.IsImplicitFlowEnabled()) {
logger.LogError("The authorization request was rejected because the implicit flow was not enabled.");
context.Reject(
error: OpenIdConnectConstants.Errors.UnsupportedResponseType,
@ -139,10 +144,10 @@ namespace OpenIddict.Infrastructure {
}
// Reject hybrid flow authorization requests if the authorization code or the implicit flows are not enabled.
if (context.Request.IsHybridFlow() && (!services.Options.IsAuthorizationCodeFlowEnabled() ||
!services.Options.IsImplicitFlowEnabled())) {
services.Logger.LogError("The authorization request was rejected because the " +
"authorization code flow or the implicit flow was not enabled.");
if (context.Request.IsHybridFlow() && (!options.Value.IsAuthorizationCodeFlowEnabled() ||
!options.Value.IsImplicitFlowEnabled())) {
logger.LogError("The authorization request was rejected because the " +
"authorization code flow or the implicit flow was not enabled.");
context.Reject(
error: OpenIdConnectConstants.Errors.UnsupportedResponseType,
@ -152,7 +157,7 @@ namespace OpenIddict.Infrastructure {
}
// Reject authorization requests that specify scope=offline_access if the refresh token flow is not enabled.
if (context.Request.HasScope(OpenIdConnectConstants.Scopes.OfflineAccess) && !services.Options.IsRefreshTokenFlowEnabled()) {
if (context.Request.HasScope(OpenIdConnectConstants.Scopes.OfflineAccess) && !options.Value.IsRefreshTokenFlowEnabled()) {
context.Reject(
error: OpenIdConnectConstants.Errors.InvalidRequest,
description: "The 'offline_access' scope is not allowed.");
@ -166,8 +171,8 @@ namespace OpenIddict.Infrastructure {
if (!string.IsNullOrEmpty(context.Request.ResponseMode) && !context.Request.IsFormPostResponseMode() &&
!context.Request.IsFragmentResponseMode() &&
!context.Request.IsQueryResponseMode()) {
services.Logger.LogError("The authorization request was rejected because the '{ResponseMode}' " +
"response mode is not supported.", context.Request.ResponseMode);
logger.LogError("The authorization request was rejected because the '{ResponseMode}' " +
"response mode is not supported.", context.Request.ResponseMode);
context.Reject(
error: OpenIdConnectConstants.Errors.InvalidRequest,
@ -194,8 +199,8 @@ namespace OpenIddict.Infrastructure {
// Since the default challenge method (plain) is explicitly disallowed,
// reject the authorization request if the code_challenge_method is missing.
if (string.IsNullOrEmpty(context.Request.CodeChallengeMethod)) {
services.Logger.LogError("The authorization request was rejected because the " +
"required 'code_challenge_method' parameter was missing.");
logger.LogError("The authorization request was rejected because the " +
"required 'code_challenge_method' parameter was missing.");
context.Reject(
error: OpenIdConnectConstants.Errors.InvalidRequest,
@ -207,8 +212,8 @@ namespace OpenIddict.Infrastructure {
// Disallow the use of the unsecure code_challenge_method=plain method.
// See https://tools.ietf.org/html/rfc7636#section-7.2 for more information.
if (string.Equals(context.Request.CodeChallengeMethod, OpenIdConnectConstants.CodeChallengeMethods.Plain)) {
services.Logger.LogError("The authorization request was rejected because the " +
"'code_challenge_method' parameter was set to 'plain'.");
logger.LogError("The authorization request was rejected because the " +
"'code_challenge_method' parameter was set to 'plain'.");
context.Reject(
error: OpenIdConnectConstants.Errors.InvalidRequest,
@ -219,8 +224,8 @@ namespace OpenIddict.Infrastructure {
// Reject authorization requests that contain response_type=token when a code_challenge is specified.
if (context.Request.HasResponseType(OpenIdConnectConstants.ResponseTypes.Token)) {
services.Logger.LogError("The authorization request was rejected because the " +
"specified response type was not compatible with PKCE.");
logger.LogError("The authorization request was rejected because the " +
"specified response type was not compatible with PKCE.");
context.Reject(
error: OpenIdConnectConstants.Errors.InvalidRequest,
@ -231,10 +236,10 @@ namespace OpenIddict.Infrastructure {
}
// Retrieve the application details corresponding to the requested client_id.
var application = await services.Applications.FindByClientIdAsync(context.ClientId);
var application = await applications.FindByClientIdAsync(context.ClientId, context.HttpContext.RequestAborted);
if (application == null) {
services.Logger.LogError("The authorization request was rejected because the client " +
"application was not found: '{ClientId}'.", context.ClientId);
logger.LogError("The authorization request was rejected because the client " +
"application was not found: '{ClientId}'.", context.ClientId);
context.Reject(
error: OpenIdConnectConstants.Errors.InvalidClient,
@ -243,9 +248,9 @@ namespace OpenIddict.Infrastructure {
return;
}
if (!await services.Applications.ValidateRedirectUriAsync(application, context.RedirectUri)) {
services.Logger.LogError("The authorization request was rejected because the redirect_uri " +
"was invalid: '{RedirectUri}'.", context.RedirectUri);
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.InvalidClient,
@ -258,7 +263,7 @@ namespace OpenIddict.Infrastructure {
// 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 services.Applications.IsConfidentialAsync(application) &&
if (await applications.IsConfidentialAsync(application, context.HttpContext.RequestAborted) &&
context.Request.HasResponseType(OpenIdConnectConstants.ResponseTypes.Token)) {
context.Reject(
error: OpenIdConnectConstants.Errors.InvalidRequest,
@ -273,7 +278,7 @@ namespace OpenIddict.Infrastructure {
// If the user is not authenticated, return an error to the client application.
// See http://openid.net/specs/openid-connect-core-1_0.html#Authenticates
if (!context.HttpContext.User.Identities.Any(identity => identity.IsAuthenticated)) {
services.Logger.LogError("The prompt=none authorization request was rejected because the user was not logged in.");
logger.LogError("The prompt=none authorization request was rejected because the user was not logged in.");
context.Reject(
error: OpenIdConnectConstants.Errors.LoginRequired,
@ -285,8 +290,8 @@ namespace OpenIddict.Infrastructure {
// Ensure that the authentication cookie contains the required NameIdentifier claim.
var identifier = context.HttpContext.User.GetClaim(ClaimTypes.NameIdentifier);
if (string.IsNullOrEmpty(identifier)) {
services.Logger.LogError("The prompt=none authorization request was rejected because the user session " +
"was invalid and didn't contain the mandatory ClaimTypes.NameIdentifier claim.");
logger.LogError("The prompt=none authorization request was rejected because the user session " +
"was invalid and didn't contain the mandatory ClaimTypes.NameIdentifier claim.");
context.Reject(
error: OpenIdConnectConstants.Errors.ServerError,
@ -310,7 +315,7 @@ namespace OpenIddict.Infrastructure {
// and that the identity token corresponds to the authenticated user.
if (!principal.HasClaim(OpenIdConnectConstants.Claims.Audience, context.Request.ClientId) ||
!principal.HasClaim(ClaimTypes.NameIdentifier, identifier)) {
services.Logger.LogError("The prompt=none authorization request was rejected because the id_token_hint was invalid.");
logger.LogError("The prompt=none authorization request was rejected because the id_token_hint was invalid.");
context.Reject(
error: OpenIdConnectConstants.Errors.InvalidRequest,
@ -324,12 +329,12 @@ namespace OpenIddict.Infrastructure {
}
public override async Task HandleAuthorizationRequest([NotNull] HandleAuthorizationRequestContext context) {
var services = context.HttpContext.RequestServices.GetRequiredService<OpenIddictServices<TApplication, TAuthorization, TScope, TToken>>();
var options = context.HttpContext.RequestServices.GetRequiredService<IOptions<OpenIddictOptions>>();
// If no request_id parameter can be found in the current request, assume the OpenID Connect request
// was not serialized yet and store the entire payload in the distributed cache to make it easier
// to flow across requests and internal/external authentication/registration workflows.
if (services.Options.EnableRequestCaching && string.IsNullOrEmpty(context.Request.RequestId)) {
if (options.Value.EnableRequestCaching && string.IsNullOrEmpty(context.Request.RequestId)) {
// Generate a request identifier. Note: using a crypto-secure
// random number generator is not necessary in this case.
context.Request.RequestId = Guid.NewGuid().ToString();
@ -347,7 +352,7 @@ namespace OpenIddict.Infrastructure {
// to avoid collisions with the other types of cached requests.
var key = OpenIddictConstants.Environment.AuthorizationRequest + context.Request.RequestId;
await services.Options.Cache.SetAsync(key, stream.ToArray(), new DistributedCacheEntryOptions {
await options.Value.Cache.SetAsync(key, stream.ToArray(), new DistributedCacheEntryOptions {
AbsoluteExpiration = context.Options.SystemClock.UtcNow + TimeSpan.FromMinutes(30),
SlidingExpiration = TimeSpan.FromMinutes(10)
});
@ -371,10 +376,10 @@ namespace OpenIddict.Infrastructure {
}
public override async Task ApplyAuthorizationResponse([NotNull] ApplyAuthorizationResponseContext context) {
var services = context.HttpContext.RequestServices.GetRequiredService<OpenIddictServices<TApplication, TAuthorization, TScope, TToken>>();
var options = context.HttpContext.RequestServices.GetRequiredService<IOptions<OpenIddictOptions>>();
// Remove the authorization request from the distributed cache.
if (services.Options.EnableRequestCaching && !string.IsNullOrEmpty(context.Request.RequestId)) {
if (options.Value.EnableRequestCaching && !string.IsNullOrEmpty(context.Request.RequestId)) {
// Note: the cache key is always prefixed with a specific marker
// to avoid collisions with the other types of cached requests.
var key = OpenIddictConstants.Environment.AuthorizationRequest + context.Request.RequestId;
@ -382,11 +387,11 @@ namespace OpenIddict.Infrastructure {
// Note: the ApplyAuthorizationResponse event is called for both successful
// and errored authorization responses but discrimination is not necessary here,
// as the authorization request must be removed from the distributed cache in both cases.
await services.Options.Cache.RemoveAsync(key);
await options.Value.Cache.RemoveAsync(key);
}
if (!context.Options.ApplicationCanDisplayErrors && !string.IsNullOrEmpty(context.Response.Error) &&
string.IsNullOrEmpty(context.Response.RedirectUri)) {
if (!options.Value.ApplicationCanDisplayErrors && !string.IsNullOrEmpty(context.Response.Error) &&
string.IsNullOrEmpty(context.Response.RedirectUri)) {
// Determine if the status code pages middleware has been enabled for this request.
// If it was not registered or enabled, let the OpenID Connect server middleware render
// a default error page instead of delegating the rendering to the status code middleware.

10
src/OpenIddict.Core/Infrastructure/OpenIddictProvider.Discovery.cs → src/OpenIddict/OpenIddictProvider.Discovery.cs

@ -10,12 +10,14 @@ using AspNet.Security.OpenIdConnect.Server;
using JetBrains.Annotations;
using Microsoft.AspNetCore.Builder;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;
using OpenIddict.Core;
namespace OpenIddict.Infrastructure {
namespace OpenIddict {
public partial class OpenIddictProvider<TApplication, TAuthorization, TScope, TToken> : OpenIdConnectServerProvider
where TApplication : class where TAuthorization : class where TScope : class where TToken : class {
public override Task HandleConfigurationRequest([NotNull] HandleConfigurationRequestContext context) {
var services = context.HttpContext.RequestServices.GetRequiredService<OpenIddictServices<TApplication, TAuthorization, TScope, TToken>>();
var options = context.HttpContext.RequestServices.GetRequiredService<IOptions<OpenIddictOptions>>();
// Note: though it's natively supported by the OpenID Connect server middleware,
// OpenIddict disallows the use of the unsecure code_challenge_method=plain method,
@ -30,7 +32,7 @@ namespace OpenIddict.Infrastructure {
context.GrantTypes.Clear();
// Copy the supported grant types list to the discovery document.
foreach (var type in services.Options.GrantTypes) {
foreach (var type in options.Value.GrantTypes) {
context.GrantTypes.Add(type);
}
@ -43,7 +45,7 @@ namespace OpenIddict.Infrastructure {
// Only add the "offline_access" scope if the refresh
// token flow is enabled in the OpenIddict options.
if (services.Options.IsRefreshTokenFlowEnabled()) {
if (options.Value.IsRefreshTokenFlowEnabled()) {
context.Scopes.Add(OpenIdConnectConstants.Scopes.OfflineAccess);
}

73
src/OpenIddict.Core/Infrastructure/OpenIddictProvider.Exchange.cs → src/OpenIddict/OpenIddictProvider.Exchange.cs

@ -13,17 +13,21 @@ using JetBrains.Annotations;
using Microsoft.AspNetCore.Builder;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using OpenIddict.Core;
namespace OpenIddict.Infrastructure {
namespace OpenIddict {
public partial class OpenIddictProvider<TApplication, TAuthorization, TScope, TToken> : OpenIdConnectServerProvider
where TApplication : class where TAuthorization : class where TScope : class where TToken : class {
public override async Task ValidateTokenRequest([NotNull] ValidateTokenRequestContext context) {
var services = context.HttpContext.RequestServices.GetRequiredService<OpenIddictServices<TApplication, TAuthorization, TScope, TToken>>();
var applications = context.HttpContext.RequestServices.GetRequiredService<OpenIddictApplicationManager<TApplication>>();
var logger = context.HttpContext.RequestServices.GetRequiredService<ILogger<OpenIddictProvider<TApplication, TAuthorization, TScope, TToken>>>();
var options = context.HttpContext.RequestServices.GetRequiredService<IOptions<OpenIddictOptions>>();
// Reject token requests that don't specify a supported grant type.
if (!services.Options.GrantTypes.Contains(context.Request.GrantType)) {
services.Logger.LogError("The token request was rejected because the '{Grant}' " +
"grant is not supported.", context.Request.GrantType);
if (!options.Value.GrantTypes.Contains(context.Request.GrantType)) {
logger.LogError("The token request was rejected because the '{Grant}' " +
"grant is not supported.", context.Request.GrantType);
context.Reject(
error: OpenIdConnectConstants.Errors.UnsupportedGrantType,
@ -33,7 +37,7 @@ namespace OpenIddict.Infrastructure {
}
// Reject token requests that specify scope=offline_access if the refresh token flow is not enabled.
if (context.Request.HasScope(OpenIdConnectConstants.Scopes.OfflineAccess) && !services.Options.IsRefreshTokenFlowEnabled()) {
if (context.Request.HasScope(OpenIdConnectConstants.Scopes.OfflineAccess) && !options.Value.IsRefreshTokenFlowEnabled()) {
context.Reject(
error: OpenIdConnectConstants.Errors.InvalidRequest,
description: "The 'offline_access' scope is not allowed.");
@ -60,7 +64,7 @@ namespace OpenIddict.Infrastructure {
// database roundtrips to retrieve the client application corresponding to the client_id.
if (context.Request.IsClientCredentialsGrantType() && (string.IsNullOrEmpty(context.Request.ClientId) ||
string.IsNullOrEmpty(context.Request.ClientSecret))) {
services.Logger.LogError("The token request was rejected because the client credentials were missing.");
logger.LogError("The token request was rejected because the client credentials were missing.");
context.Reject(
error: OpenIdConnectConstants.Errors.InvalidRequest,
@ -83,9 +87,9 @@ namespace OpenIddict.Infrastructure {
// if it's not the intended audience, even if client authentication was skipped.
if (string.IsNullOrEmpty(context.ClientId)) {
// Reject the request if client identification is mandatory.
if (services.Options.RequireClientIdentification) {
services.Logger.LogError("The token request was rejected becaused the " +
"mandatory client_id parameter was missing or empty.");
if (options.Value.RequireClientIdentification) {
logger.LogError("The token request was rejected becaused the " +
"mandatory client_id parameter was missing or empty.");
context.Reject(
error: OpenIdConnectConstants.Errors.InvalidRequest,
@ -94,8 +98,8 @@ namespace OpenIddict.Infrastructure {
return;
}
services.Logger.LogInformation("The token request validation process was skipped " +
"because the client_id parameter was missing or empty.");
logger.LogInformation("The token request validation process was skipped " +
"because the client_id parameter was missing or empty.");
context.Skip();
@ -103,10 +107,10 @@ namespace OpenIddict.Infrastructure {
}
// Retrieve the application details corresponding to the requested client_id.
var application = await services.Applications.FindByClientIdAsync(context.ClientId);
var application = await applications.FindByClientIdAsync(context.ClientId, context.HttpContext.RequestAborted);
if (application == null) {
services.Logger.LogError("The token request was rejected because the client " +
"application was not found: '{ClientId}'.", context.ClientId);
logger.LogError("The token request was rejected because the client " +
"application was not found: '{ClientId}'.", context.ClientId);
context.Reject(
error: OpenIdConnectConstants.Errors.InvalidClient,
@ -115,11 +119,11 @@ namespace OpenIddict.Infrastructure {
return;
}
if (await services.Applications.IsPublicAsync(application)) {
if (await applications.IsPublicAsync(application, context.HttpContext.RequestAborted)) {
// Note: public applications are not allowed to use the client credentials grant.
if (context.Request.IsClientCredentialsGrantType()) {
services.Logger.LogError("The token request was rejected because the public client application '{ClientId}' " +
"was not allowed to use the client credentials grant.", context.Request.ClientId);
logger.LogError("The token request was rejected because the public client application '{ClientId}' " +
"was not allowed to use the client credentials grant.", context.Request.ClientId);
context.Reject(
error: OpenIdConnectConstants.Errors.UnauthorizedClient,
@ -130,8 +134,8 @@ namespace OpenIddict.Infrastructure {
// Reject tokens requests containing a client_secret when the client is a public application.
if (!string.IsNullOrEmpty(context.ClientSecret)) {
services.Logger.LogError("The token request was rejected because the public application '{ClientId}' " +
"was not allowed to send a client secret.", context.ClientId);
logger.LogError("The token request was rejected because the public application '{ClientId}' " +
"was not allowed to send a client secret.", context.ClientId);
context.Reject(
error: OpenIdConnectConstants.Errors.InvalidRequest,
@ -140,8 +144,8 @@ namespace OpenIddict.Infrastructure {
return;
}
services.Logger.LogInformation("The token request validation process was not fully validated because " +
"the client '{ClientId}' was a public application.", context.ClientId);
logger.LogInformation("The token request validation process was not fully validated because " +
"the client '{ClientId}' was a public application.", context.ClientId);
// If client authentication cannot be enforced, call context.Skip() to inform
// the OpenID Connect server middleware that the caller cannot be fully trusted.
@ -153,8 +157,8 @@ namespace OpenIddict.Infrastructure {
// Confidential applications MUST authenticate
// to protect them from impersonation attacks.
if (string.IsNullOrEmpty(context.ClientSecret)) {
services.Logger.LogError("The token request was rejected because the confidential application " +
"'{ClientId}' didn't specify a client secret.", context.ClientId);
logger.LogError("The token request was rejected because the confidential application " +
"'{ClientId}' didn't specify a client secret.", context.ClientId);
context.Reject(
error: OpenIdConnectConstants.Errors.InvalidClient,
@ -163,9 +167,9 @@ namespace OpenIddict.Infrastructure {
return;
}
if (!await services.Applications.ValidateSecretAsync(application, context.ClientSecret)) {
services.Logger.LogError("The token request was rejected because the confidential application " +
"'{ClientId}' didn't specify valid client credentials.", context.ClientId);
if (!await applications.ValidateSecretAsync(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);
context.Reject(
error: OpenIdConnectConstants.Errors.InvalidClient,
@ -178,7 +182,8 @@ namespace OpenIddict.Infrastructure {
}
public override async Task HandleTokenRequest([NotNull] HandleTokenRequestContext context) {
var services = context.HttpContext.RequestServices.GetRequiredService<OpenIddictServices<TApplication, TAuthorization, TScope, TToken>>();
var logger = context.HttpContext.RequestServices.GetRequiredService<ILogger<OpenIddictProvider<TApplication, TAuthorization, TScope, TToken>>>();
var tokens = context.HttpContext.RequestServices.GetRequiredService<OpenIddictTokenManager<TToken>>();
if (context.Request.IsAuthorizationCodeGrantType()) {
Debug.Assert(context.Ticket != null, "The authentication ticket shouldn't be null.");
@ -189,9 +194,9 @@ namespace OpenIddict.Infrastructure {
"The authorization code should contain a ticket identifier.");
// Retrieve the token from the database and ensure it is still valid.
var token = await services.Tokens.FindByIdAsync(identifier);
var token = await tokens.FindByIdAsync(identifier, context.HttpContext.RequestAborted);
if (token == null) {
services.Logger.LogError("The token request was rejected because the authorization code was revoked.");
logger.LogError("The token request was rejected because the authorization code was revoked.");
context.Reject(
error: OpenIdConnectConstants.Errors.InvalidGrant,
@ -201,7 +206,7 @@ namespace OpenIddict.Infrastructure {
}
// Revoke the authorization code to prevent token reuse.
await services.Tokens.RevokeAsync(token);
await tokens.RevokeAsync(token, context.HttpContext.RequestAborted);
context.Validate(context.Ticket);
@ -217,9 +222,9 @@ namespace OpenIddict.Infrastructure {
"The refresh token should contain a ticket identifier.");
// Retrieve the token from the database and ensure it is still valid.
var token = await services.Tokens.FindByIdAsync(identifier);
var token = await tokens.FindByIdAsync(identifier, context.HttpContext.RequestAborted);
if (token == null) {
services.Logger.LogError("The token request was rejected because the refresh token was revoked.");
logger.LogError("The token request was rejected because the refresh token was revoked.");
context.Reject(
error: OpenIdConnectConstants.Errors.InvalidGrant,
@ -232,7 +237,7 @@ namespace OpenIddict.Infrastructure {
// revoke the refresh token to prevent future reuse.
// See https://tools.ietf.org/html/rfc6749#section-6.
if (context.Options.UseSlidingExpiration) {
await services.Tokens.RevokeAsync(token);
await tokens.RevokeAsync(token, context.HttpContext.RequestAborted);
}
}

39
src/OpenIddict.Core/Infrastructure/OpenIddictProvider.Introspection.cs → src/OpenIddict/OpenIddictProvider.Introspection.cs

@ -13,8 +13,9 @@ using AspNet.Security.OpenIdConnect.Server;
using JetBrains.Annotations;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using OpenIddict.Core;
namespace OpenIddict.Infrastructure {
namespace OpenIddict {
public partial class OpenIddictProvider<TApplication, TAuthorization, TScope, TToken> : OpenIdConnectServerProvider
where TApplication : class where TAuthorization : class where TScope : class where TToken : class {
public override Task ExtractIntrospectionRequest([NotNull] ExtractIntrospectionRequestContext context) {
@ -32,7 +33,8 @@ namespace OpenIddict.Infrastructure {
}
public override async Task ValidateIntrospectionRequest([NotNull] ValidateIntrospectionRequestContext context) {
var services = context.HttpContext.RequestServices.GetRequiredService<OpenIddictServices<TApplication, TAuthorization, TScope, TToken>>();
var applications = context.HttpContext.RequestServices.GetRequiredService<OpenIddictApplicationManager<TApplication>>();
var logger = context.HttpContext.RequestServices.GetRequiredService<ILogger<OpenIddictProvider<TApplication, TAuthorization, TScope, TToken>>>();
// Note: the OpenID Connect server middleware supports unauthenticated introspection requests
// but OpenIddict uses a stricter policy preventing unauthenticated/public applications
@ -47,10 +49,10 @@ namespace OpenIddict.Infrastructure {
}
// Retrieve the application details corresponding to the requested client_id.
var application = await services.Applications.FindByClientIdAsync(context.ClientId);
var application = await applications.FindByClientIdAsync(context.ClientId, context.HttpContext.RequestAborted);
if (application == null) {
services.Logger.LogError("The introspection request was rejected because the client " +
"application was not found: '{ClientId}'.", context.ClientId);
logger.LogError("The introspection request was rejected because the client " +
"application was not found: '{ClientId}'.", context.ClientId);
context.Reject(
error: OpenIdConnectConstants.Errors.InvalidClient,
@ -60,9 +62,9 @@ namespace OpenIddict.Infrastructure {
}
// Reject non-confidential applications.
if (!await services.Applications.IsConfidentialAsync(application)) {
services.Logger.LogError("The introspection request was rejected because the public application " +
"'{ClientId}' was not allowed to use this endpoint.", context.ClientId);
if (!await applications.IsConfidentialAsync(application, context.HttpContext.RequestAborted)) {
logger.LogError("The introspection request was rejected because the public application " +
"'{ClientId}' was not allowed to use this endpoint.", context.ClientId);
context.Reject(
error: OpenIdConnectConstants.Errors.InvalidClient,
@ -72,9 +74,9 @@ namespace OpenIddict.Infrastructure {
}
// Validate the client credentials.
if (!await services.Applications.ValidateSecretAsync(application, context.ClientSecret)) {
services.Logger.LogError("The introspection request was rejected because the confidential application " +
"'{ClientId}' didn't specify valid client credentials.", context.ClientId);
if (!await applications.ValidateSecretAsync(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);
context.Reject(
error: OpenIdConnectConstants.Errors.InvalidClient,
@ -87,7 +89,8 @@ namespace OpenIddict.Infrastructure {
}
public override async Task HandleIntrospectionRequest([NotNull] HandleIntrospectionRequestContext context) {
var services = context.HttpContext.RequestServices.GetRequiredService<OpenIddictServices<TApplication, TAuthorization, TScope, TToken>>();
var logger = context.HttpContext.RequestServices.GetRequiredService<ILogger<OpenIddictProvider<TApplication, TAuthorization, TScope, TToken>>>();
var tokens = context.HttpContext.RequestServices.GetRequiredService<OpenIddictTokenManager<TToken>>();
Debug.Assert(context.Ticket != null, "The authentication ticket shouldn't be null.");
Debug.Assert(!string.IsNullOrEmpty(context.Request.ClientId), "The client_id parameter shouldn't be null.");
@ -96,9 +99,9 @@ namespace OpenIddict.Infrastructure {
// but OpenIddict uses a stricter policy that only allows resource servers to use the introspection endpoint, unless the ticket
// doesn't have any audience: in this case, the caller is allowed to introspect the token even if it's not listed as a valid audience.
if (context.Ticket.IsAccessToken() && context.Ticket.HasAudience() && !context.Ticket.HasAudience(context.Request.ClientId)) {
services.Logger.LogWarning("The client application '{ClientId}' is not allowed to introspect the access " +
"token '{Identifier}' because it's not listed as a valid audience.",
context.Request.ClientId, context.Ticket.GetTicketId());
logger.LogWarning("The client application '{ClientId}' is not allowed to introspect the access " +
"token '{Identifier}' because it's not listed as a valid audience.",
context.Request.ClientId, context.Ticket.GetTicketId());
context.Claims.RemoveAll();
context.Active = false;
@ -110,10 +113,10 @@ namespace OpenIddict.Infrastructure {
if (context.Ticket.IsAuthorizationCode() || context.Ticket.IsRefreshToken()) {
// Retrieve the token from the database using the unique identifier stored in the authentication ticket:
// if the corresponding entry cannot be found, return Active = false to indicate that is is no longer valid.
var token = await services.Tokens.FindByIdAsync(context.Ticket.GetTicketId());
var token = await tokens.FindByIdAsync(context.Ticket.GetTicketId(), context.HttpContext.RequestAborted);
if (token == null) {
services.Logger.LogInformation("The token {Identifier} was declared as inactive because " +
"it was revoked.", context.Ticket.GetTicketId());
logger.LogInformation("The token {Identifier} was declared as inactive because " +
"it was revoked.", context.Ticket.GetTicketId());
context.Claims.RemoveAll();
context.Active = false;

41
src/OpenIddict.Core/Infrastructure/OpenIddictProvider.Revocation.cs → src/OpenIddict/OpenIddictProvider.Revocation.cs

@ -12,12 +12,16 @@ using AspNet.Security.OpenIdConnect.Server;
using JetBrains.Annotations;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using OpenIddict.Core;
namespace OpenIddict.Infrastructure {
namespace OpenIddict {
public partial class OpenIddictProvider<TApplication, TAuthorization, TScope, TToken> : OpenIdConnectServerProvider
where TApplication : class where TAuthorization : class where TScope : class where TToken : class {
public override async Task ValidateRevocationRequest([NotNull] ValidateRevocationRequestContext context) {
var services = context.HttpContext.RequestServices.GetRequiredService<OpenIddictServices<TApplication, TAuthorization, TScope, TToken>>();
var applications = context.HttpContext.RequestServices.GetRequiredService<OpenIddictApplicationManager<TApplication>>();
var logger = context.HttpContext.RequestServices.GetRequiredService<ILogger<OpenIddictProvider<TApplication, TAuthorization, TScope, TToken>>>();
var options = context.HttpContext.RequestServices.GetRequiredService<IOptions<OpenIddictOptions>>();
// When token_type_hint is specified, reject the request if it doesn't correspond to a revocable token.
if (!string.IsNullOrEmpty(context.Request.TokenTypeHint) &&
@ -38,9 +42,9 @@ namespace OpenIddict.Infrastructure {
// the intended audience, even if client authentication was skipped.
if (string.IsNullOrEmpty(context.ClientId)) {
// Reject the request if client identification is mandatory.
if (services.Options.RequireClientIdentification) {
services.Logger.LogError("The revocation request was rejected becaused the " +
"mandatory client_id parameter was missing or empty.");
if (options.Value.RequireClientIdentification) {
logger.LogError("The revocation request was rejected becaused the " +
"mandatory client_id parameter was missing or empty.");
context.Reject(
error: OpenIdConnectConstants.Errors.InvalidRequest,
@ -49,8 +53,8 @@ namespace OpenIddict.Infrastructure {
return;
}
services.Logger.LogInformation("The revocation request validation process was skipped " +
"because the client_id parameter was missing or empty.");
logger.LogInformation("The revocation request validation process was skipped " +
"because the client_id parameter was missing or empty.");
context.Skip();
@ -58,7 +62,7 @@ namespace OpenIddict.Infrastructure {
}
// Retrieve the application details corresponding to the requested client_id.
var application = await services.Applications.FindByClientIdAsync(context.ClientId);
var application = await applications.FindByClientIdAsync(context.ClientId, context.HttpContext.RequestAborted);
if (application == null) {
context.Reject(
error: OpenIdConnectConstants.Errors.InvalidClient,
@ -68,7 +72,7 @@ namespace OpenIddict.Infrastructure {
}
// Reject revocation requests containing a client_secret if the client application is not confidential.
if (await services.Applications.IsPublicAsync(application)) {
if (await applications.IsPublicAsync(application, context.HttpContext.RequestAborted)) {
// Reject tokens requests containing a client_secret when the client is a public application.
if (!string.IsNullOrEmpty(context.ClientSecret)) {
context.Reject(
@ -78,8 +82,8 @@ namespace OpenIddict.Infrastructure {
return;
}
services.Logger.LogInformation("The revocation request validation process was not fully validated because " +
"the client '{ClientId}' was a public application.", context.ClientId);
logger.LogInformation("The revocation request validation process was not fully validated because " +
"the client '{ClientId}' was a public application.", context.ClientId);
// If client authentication cannot be enforced, call context.Skip() to inform
// the OpenID Connect server middleware that the caller cannot be fully trusted.
@ -98,7 +102,7 @@ namespace OpenIddict.Infrastructure {
return;
}
if (!await services.Applications.ValidateSecretAsync(application, context.ClientSecret)) {
if (!await applications.ValidateSecretAsync(application, context.ClientSecret, context.HttpContext.RequestAborted)) {
context.Reject(
error: OpenIdConnectConstants.Errors.InvalidClient,
description: "Invalid credentials: ensure that you specified a correct client_secret.");
@ -110,14 +114,15 @@ namespace OpenIddict.Infrastructure {
}
public override async Task HandleRevocationRequest([NotNull] HandleRevocationRequestContext context) {
var services = context.HttpContext.RequestServices.GetRequiredService<OpenIddictServices<TApplication, TAuthorization, TScope, TToken>>();
var logger = context.HttpContext.RequestServices.GetRequiredService<ILogger<OpenIddictProvider<TApplication, TAuthorization, TScope, TToken>>>();
var tokens = context.HttpContext.RequestServices.GetRequiredService<OpenIddictTokenManager<TToken>>();
Debug.Assert(context.Ticket != null, "The authentication ticket shouldn't be null.");
// If the received token is not an authorization code or a refresh token,
// return an error to indicate that the token cannot be revoked.
if (!context.Ticket.IsAuthorizationCode() && !context.Ticket.IsRefreshToken()) {
services.Logger.LogError("The revocation request was rejected because the token was not revocable.");
logger.LogError("The revocation request was rejected because the token was not revocable.");
context.Reject(
error: OpenIdConnectConstants.Errors.UnsupportedTokenType,
@ -132,9 +137,9 @@ namespace OpenIddict.Infrastructure {
// Retrieve the token from the database. If the token cannot be found,
// assume it is invalid and consider the revocation as successful.
var token = await services.Tokens.FindByIdAsync(identifier);
var token = await tokens.FindByIdAsync(identifier, context.HttpContext.RequestAborted);
if (token == null) {
services.Logger.LogInformation("The token '{Identifier}' was already revoked.", identifier);
logger.LogInformation("The token '{Identifier}' was already revoked.", identifier);
context.Revoked = true;
@ -142,9 +147,9 @@ namespace OpenIddict.Infrastructure {
}
// Revoke the token.
await services.Tokens.RevokeAsync(token);
await tokens.RevokeAsync(token, context.HttpContext.RequestAborted);
services.Logger.LogInformation("The token '{Identifier}' was successfully revoked.", identifier);
logger.LogInformation("The token '{Identifier}' was successfully revoked.", identifier);
context.Revoked = true;
}

11
src/OpenIddict.Core/Infrastructure/OpenIddictProvider.Serialization.cs → src/OpenIddict/OpenIddictProvider.Serialization.cs

@ -11,14 +11,15 @@ using AspNet.Security.OpenIdConnect.Primitives;
using AspNet.Security.OpenIdConnect.Server;
using JetBrains.Annotations;
using Microsoft.Extensions.DependencyInjection;
using OpenIddict.Core;
namespace OpenIddict.Infrastructure {
namespace OpenIddict {
public partial class OpenIddictProvider<TApplication, TAuthorization, TScope, TToken> : OpenIdConnectServerProvider
where TApplication : class where TAuthorization : class where TScope : class where TToken : class {
public override async Task SerializeAuthorizationCode([NotNull] SerializeAuthorizationCodeContext context) {
var services = context.HttpContext.RequestServices.GetRequiredService<OpenIddictServices<TApplication, TAuthorization, TScope, TToken>>();
var tokens = context.HttpContext.RequestServices.GetRequiredService<OpenIddictTokenManager<TToken>>();
var identifier = await services.Tokens.CreateAsync(OpenIdConnectConstants.TokenTypeHints.AuthorizationCode);
var identifier = await tokens.CreateAsync(OpenIdConnectConstants.TokenTypeHints.AuthorizationCode, context.HttpContext.RequestAborted);
if (string.IsNullOrEmpty(identifier)) {
throw new InvalidOperationException("The unique key associated with an authorization code cannot be null or empty.");
}
@ -30,9 +31,9 @@ namespace OpenIddict.Infrastructure {
}
public override async Task SerializeRefreshToken([NotNull] SerializeRefreshTokenContext context) {
var services = context.HttpContext.RequestServices.GetRequiredService<OpenIddictServices<TApplication, TAuthorization, TScope, TToken>>();
var tokens = context.HttpContext.RequestServices.GetRequiredService<OpenIddictTokenManager<TToken>>();
var identifier = await services.Tokens.CreateAsync(OpenIdConnectConstants.TokenTypeHints.RefreshToken);
var identifier = await tokens.CreateAsync(OpenIdConnectConstants.TokenTypeHints.RefreshToken, context.HttpContext.RequestAborted);
if (string.IsNullOrEmpty(identifier)) {
throw new InvalidOperationException("The unique key associated with a refresh token cannot be null or empty.");
}

50
src/OpenIddict.Core/Infrastructure/OpenIddictProvider.Session.cs → src/OpenIddict/OpenIddictProvider.Session.cs

@ -15,23 +15,26 @@ using Microsoft.AspNetCore.WebUtilities;
using Microsoft.Extensions.Caching.Distributed;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using Newtonsoft.Json;
using Newtonsoft.Json.Bson;
using Newtonsoft.Json.Linq;
using OpenIddict.Core;
namespace OpenIddict.Infrastructure {
namespace OpenIddict {
public partial class OpenIddictProvider<TApplication, TAuthorization, TScope, TToken> : OpenIdConnectServerProvider
where TApplication : class where TAuthorization : class where TScope : class where TToken : class {
public override async Task ExtractLogoutRequest([NotNull] ExtractLogoutRequestContext context) {
var services = context.HttpContext.RequestServices.GetRequiredService<OpenIddictServices<TApplication, TAuthorization, TScope, TToken>>();
var logger = context.HttpContext.RequestServices.GetRequiredService<ILogger<OpenIddictProvider<TApplication, TAuthorization, TScope, TToken>>>();
var options = context.HttpContext.RequestServices.GetRequiredService<IOptions<OpenIddictOptions>>();
// If a request_id parameter can be found in the logout request,
// restore the complete logout request from the distributed cache.
if (!string.IsNullOrEmpty(context.Request.RequestId)) {
// Return an error if request caching support was not enabled.
if (!services.Options.EnableRequestCaching) {
services.Logger.LogError("The logout request was rejected because " +
"request caching support was not enabled.");
if (!options.Value.EnableRequestCaching) {
logger.LogError("The logout request was rejected because " +
"request caching support was not enabled.");
context.Reject(
error: OpenIdConnectConstants.Errors.InvalidRequest,
@ -44,10 +47,10 @@ namespace OpenIddict.Infrastructure {
// to avoid collisions with the other types of cached requests.
var key = OpenIddictConstants.Environment.LogoutRequest + context.Request.RequestId;
var payload = await services.Options.Cache.GetAsync(key);
var payload = await options.Value.Cache.GetAsync(key);
if (payload == null) {
services.Logger.LogError("The logout request was rejected because an unknown " +
"or invalid request_id parameter was specified.");
logger.LogError("The logout request was rejected because an unknown " +
"or invalid request_id parameter was specified.");
context.Reject(
error: OpenIdConnectConstants.Errors.InvalidRequest,
@ -71,24 +74,25 @@ namespace OpenIddict.Infrastructure {
}
public override async Task ValidateLogoutRequest([NotNull] ValidateLogoutRequestContext context) {
var services = context.HttpContext.RequestServices.GetRequiredService<OpenIddictServices<TApplication, TAuthorization, TScope, TToken>>();
var applications = context.HttpContext.RequestServices.GetRequiredService<OpenIddictApplicationManager<TApplication>>();
var logger = context.HttpContext.RequestServices.GetRequiredService<ILogger<OpenIddictProvider<TApplication, TAuthorization, TScope, TToken>>>();
// Skip validation if the optional post_logout_redirect_uri
// parameter was missing from the logout request.
if (string.IsNullOrEmpty(context.PostLogoutRedirectUri)) {
services.Logger.LogInformation("The logout request validation process was skipped because " +
"the post_logout_redirect_uri parameter was missing.");
logger.LogInformation("The logout request validation process was skipped because " +
"the post_logout_redirect_uri parameter was missing.");
context.Skip();
return;
}
var application = await services.Applications.FindByLogoutRedirectUri(context.PostLogoutRedirectUri);
var application = await applications.FindByLogoutRedirectUri(context.PostLogoutRedirectUri, context.HttpContext.RequestAborted);
if (application == null) {
services.Logger.LogError("The logout request was rejected because the client application corresponding " +
"to the specified post_logout_redirect_uri was not found in the database: " +
"'{PostLogoutRedirectUri}'.", context.PostLogoutRedirectUri);
logger.LogError("The logout request was rejected because the client application corresponding " +
"to the specified post_logout_redirect_uri was not found in the database: " +
"'{PostLogoutRedirectUri}'.", context.PostLogoutRedirectUri);
context.Reject(
error: OpenIdConnectConstants.Errors.InvalidClient,
@ -101,12 +105,12 @@ namespace OpenIddict.Infrastructure {
}
public override async Task HandleLogoutRequest([NotNull] HandleLogoutRequestContext context) {
var services = context.HttpContext.RequestServices.GetRequiredService<OpenIddictServices<TApplication, TAuthorization, TScope, TToken>>();
var options = context.HttpContext.RequestServices.GetRequiredService<IOptions<OpenIddictOptions>>();
// If no request_id parameter can be found in the current request, assume the OpenID Connect
// request was not serialized yet and store the entire payload in the distributed cache
// to make it easier to flow across requests and internal/external logout workflows.
if (services.Options.EnableRequestCaching && string.IsNullOrEmpty(context.Request.RequestId)) {
if (options.Value.EnableRequestCaching && string.IsNullOrEmpty(context.Request.RequestId)) {
// Generate a request identifier. Note: using a crypto-secure
// random number generator is not necessary in this case.
context.Request.RequestId = Guid.NewGuid().ToString();
@ -124,7 +128,7 @@ namespace OpenIddict.Infrastructure {
// to avoid collisions with the other types of cached requests.
var key = OpenIddictConstants.Environment.LogoutRequest + context.Request.RequestId;
await services.Options.Cache.SetAsync(key, stream.ToArray(), new DistributedCacheEntryOptions {
await options.Value.Cache.SetAsync(key, stream.ToArray(), new DistributedCacheEntryOptions {
AbsoluteExpiration = context.Options.SystemClock.UtcNow + TimeSpan.FromMinutes(30),
SlidingExpiration = TimeSpan.FromMinutes(10)
});
@ -146,10 +150,10 @@ namespace OpenIddict.Infrastructure {
}
public override async Task ApplyLogoutResponse([NotNull] ApplyLogoutResponseContext context) {
var services = context.HttpContext.RequestServices.GetRequiredService<OpenIddictServices<TApplication, TAuthorization, TScope, TToken>>();
var options = context.HttpContext.RequestServices.GetRequiredService<IOptions<OpenIddictOptions>>();
// Remove the logout request from the distributed cache.
if (services.Options.EnableRequestCaching && !string.IsNullOrEmpty(context.Request.RequestId)) {
if (options.Value.EnableRequestCaching && !string.IsNullOrEmpty(context.Request.RequestId)) {
// Note: the cache key is always prefixed with a specific marker
// to avoid collisions with the other types of cached requests.
var key = OpenIddictConstants.Environment.LogoutRequest + context.Request.RequestId;
@ -157,11 +161,11 @@ namespace OpenIddict.Infrastructure {
// Note: the ApplyLogoutResponse event is called for both successful
// and errored logout responses but discrimination is not necessary here,
// as the logout request must be removed from the distributed cache in both cases.
await services.Options.Cache.RemoveAsync(key);
await options.Value.Cache.RemoveAsync(key);
}
if (!context.Options.ApplicationCanDisplayErrors && !string.IsNullOrEmpty(context.Response.Error) &&
string.IsNullOrEmpty(context.Response.PostLogoutRedirectUri)) {
if (!options.Value.ApplicationCanDisplayErrors && !string.IsNullOrEmpty(context.Response.Error) &&
string.IsNullOrEmpty(context.Response.PostLogoutRedirectUri)) {
// Determine if the status code pages middleware has been enabled for this request.
// If it was not registered or enabled, let the OpenID Connect server middleware render
// a default error page instead of delegating the rendering to the status code middleware.

2
src/OpenIddict.Core/Infrastructure/OpenIddictProvider.Userinfo.cs → src/OpenIddict/OpenIddictProvider.Userinfo.cs

@ -8,7 +8,7 @@ using System.Threading.Tasks;
using AspNet.Security.OpenIdConnect.Server;
using JetBrains.Annotations;
namespace OpenIddict.Infrastructure {
namespace OpenIddict {
public partial class OpenIddictProvider<TApplication, TAuthorization, TScope, TToken> : OpenIdConnectServerProvider
where TApplication : class where TAuthorization : class where TScope : class where TToken : class {
public override Task HandleUserinfoRequest([NotNull] HandleUserinfoRequestContext context) {

4
src/OpenIddict.Core/Infrastructure/OpenIddictProvider.cs → src/OpenIddict/OpenIddictProvider.cs

@ -4,9 +4,11 @@
* the license and the contributors participating to this project.
*/
using System.ComponentModel;
using AspNet.Security.OpenIdConnect.Server;
namespace OpenIddict.Infrastructure {
namespace OpenIddict {
[EditorBrowsable(EditorBrowsableState.Never)]
public partial class OpenIddictProvider<TApplication, TAuthorization, TScope, TToken> : OpenIdConnectServerProvider
where TApplication : class where TAuthorization : class where TScope : class where TToken : class {
// Note: this class is split into specialized partial classes.

7
src/OpenIddict/project.json

@ -1,5 +1,5 @@
{
"version": "1.0.0-beta1-*",
"version": "1.0.0-beta2-*",
"description": "Easy-to-use OpenID Connect server for ASP.NET Core.",
"authors": [ "Kévin Chalet" ],
@ -33,8 +33,11 @@
},
"dependencies": {
"AspNet.Security.OpenIdConnect.Server": "1.0.0-beta7-final",
"JetBrains.Annotations": { "type": "build", "version": "10.1.4" },
"OpenIddict.EntityFramework": { "target": "project" }
"Microsoft.AspNetCore.Diagnostics.Abstractions": "1.0.0",
"Microsoft.Extensions.Caching.Abstractions": "1.0.0",
"OpenIddict.Core": { "target": "project" }
},
"frameworks": {

522
test/OpenIddict.Core.Tests/OpenIddictBuilderTests.cs

@ -1,37 +1,11 @@
using System;
using System.IdentityModel.Tokens.Jwt;
using System.Reflection;
using AspNet.Security.OpenIdConnect.Primitives;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.DataProtection;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using Microsoft.IdentityModel.Tokens;
using Moq;
using Xunit;
namespace OpenIddict.Core.Tests {
public class OpenIddictBuilderTests {
[Fact]
public void Configure_OptionsAreCorrectlyAmended() {
// Arrange
var services = new ServiceCollection();
services.AddOptions();
var builder = new OpenIddictBuilder(services);
// Act
builder.Configure(configuration => configuration.Description.DisplayName = "OpenIddict");
var provider = services.BuildServiceProvider();
var options = provider.GetRequiredService<IOptions<OpenIddictOptions>>();
// Assert
Assert.Equal("OpenIddict", options.Value.Description.DisplayName);
}
[Fact]
public void AddApplicationManager_ThrowsAnExceptionForInvalidManager() {
// Arrange
@ -57,7 +31,6 @@ namespace OpenIddict.Core.Tests {
builder.ApplicationType = typeof(object);
var type = new Mock<OpenIddictApplicationManager<object>>(
Mock.Of<IServiceProvider>(),
Mock.Of<IOpenIddictApplicationStore<object>>(),
Mock.Of<ILogger<OpenIddictApplicationManager<object>>>()).Object.GetType();
@ -132,7 +105,6 @@ namespace OpenIddict.Core.Tests {
builder.AuthorizationType = typeof(object);
var type = new Mock<OpenIddictAuthorizationManager<object>>(
Mock.Of<IServiceProvider>(),
Mock.Of<IOpenIddictAuthorizationStore<object>>(),
Mock.Of<ILogger<OpenIddictAuthorizationManager<object>>>()).Object.GetType();
@ -207,7 +179,6 @@ namespace OpenIddict.Core.Tests {
builder.ScopeType = typeof(object);
var type = new Mock<OpenIddictScopeManager<object>>(
Mock.Of<IServiceProvider>(),
Mock.Of<IOpenIddictScopeStore<object>>(),
Mock.Of<ILogger<OpenIddictScopeManager<object>>>()).Object.GetType();
@ -282,7 +253,6 @@ namespace OpenIddict.Core.Tests {
builder.TokenType = typeof(object);
var type = new Mock<OpenIddictTokenManager<object>>(
Mock.Of<IServiceProvider>(),
Mock.Of<IOpenIddictTokenStore<object>>(),
Mock.Of<ILogger<OpenIddictTokenManager<object>>>()).Object.GetType();
@ -331,497 +301,5 @@ namespace OpenIddict.Core.Tests {
// Assert
Assert.IsType(type, store);
}
[Fact]
public void AddEphemeralSigningKey_SigningKeyIsCorrectlyAdded() {
// Arrange
var services = new ServiceCollection();
services.AddOptions();
var builder = new OpenIddictBuilder(services);
// Act
builder.AddEphemeralSigningKey();
var provider = services.BuildServiceProvider();
var options = provider.GetRequiredService<IOptions<OpenIddictOptions>>();
// Assert
Assert.Equal(1, options.Value.SigningCredentials.Count);
}
[Theory]
[InlineData(SecurityAlgorithms.RsaSha256Signature)]
[InlineData(SecurityAlgorithms.RsaSha384Signature)]
[InlineData(SecurityAlgorithms.RsaSha512Signature)]
#if SUPPORTS_ECDSA
[InlineData(SecurityAlgorithms.EcdsaSha256Signature)]
[InlineData(SecurityAlgorithms.EcdsaSha384Signature)]
[InlineData(SecurityAlgorithms.EcdsaSha512Signature)]
#endif
public void AddEphemeralSigningKey_SigningCredentialsUseSpecifiedAlgorithm(string algorithm) {
// Arrange
var services = new ServiceCollection();
services.AddOptions();
var builder = new OpenIddictBuilder(services);
// Act
builder.AddEphemeralSigningKey(algorithm);
var provider = services.BuildServiceProvider();
var options = provider.GetRequiredService<IOptions<OpenIddictOptions>>();
var credentials = options.Value.SigningCredentials[0];
// Assert
Assert.Equal(algorithm, credentials.Algorithm);
}
[Theory]
[InlineData(SecurityAlgorithms.HmacSha256Signature)]
[InlineData(SecurityAlgorithms.RsaSha256Signature)]
#if SUPPORTS_ECDSA
[InlineData(SecurityAlgorithms.EcdsaSha256Signature)]
[InlineData(SecurityAlgorithms.EcdsaSha384Signature)]
[InlineData(SecurityAlgorithms.EcdsaSha512Signature)]
#endif
public void AddSigningKey_SigningKeyIsCorrectlyAdded(string algorithm) {
// Arrange
var services = new ServiceCollection();
services.AddOptions();
var builder = new OpenIddictBuilder(services);
var factory = Mock.Of<CryptoProviderFactory>(mock =>
mock.IsSupportedAlgorithm(algorithm, It.IsAny<SecurityKey>()));
var key = Mock.Of<SecurityKey>(mock => mock.CryptoProviderFactory == factory);
// Act
builder.AddSigningKey(key);
var provider = services.BuildServiceProvider();
var options = provider.GetRequiredService<IOptions<OpenIddictOptions>>();
// Assert
Assert.Same(key, options.Value.SigningCredentials[0].Key);
}
[Fact]
public void AddSigningCertificate_SigningKeyIsCorrectlyAdded() {
// Arrange
var services = new ServiceCollection();
services.AddOptions();
var builder = new OpenIddictBuilder(services);
// Act
builder.AddSigningCertificate(
assembly: typeof(OpenIddictBuilderTests).GetTypeInfo().Assembly,
resource: "OpenIddict.Core.Tests.Certificate.pfx",
password: "OpenIddict");
var provider = services.BuildServiceProvider();
var options = provider.GetRequiredService<IOptions<OpenIddictOptions>>();
// Assert
Assert.IsType(typeof(X509SecurityKey), options.Value.SigningCredentials[0].Key);
}
[Fact]
public void AllowAuthorizationCodeFlow_CodeFlowIsAddedToGrantTypes() {
// Arrange
var services = new ServiceCollection();
services.AddOptions();
var builder = new OpenIddictBuilder(services);
// Act
builder.AllowAuthorizationCodeFlow();
var provider = services.BuildServiceProvider();
var options = provider.GetRequiredService<IOptions<OpenIddictOptions>>();
// Assert
Assert.Contains(OpenIdConnectConstants.GrantTypes.AuthorizationCode, options.Value.GrantTypes);
}
[Fact]
public void AllowClientCredentialsFlow_ClientCredentialsFlowIsAddedToGrantTypes() {
// Arrange
var services = new ServiceCollection();
services.AddOptions();
var builder = new OpenIddictBuilder(services);
// Act
builder.AllowClientCredentialsFlow();
var provider = services.BuildServiceProvider();
var options = provider.GetRequiredService<IOptions<OpenIddictOptions>>();
// Assert
Assert.Contains(OpenIdConnectConstants.GrantTypes.ClientCredentials, options.Value.GrantTypes);
}
[Fact]
public void AllowCustomFlow_CustomFlowIsAddedToGrantTypes() {
// Arrange
var services = new ServiceCollection();
services.AddOptions();
var builder = new OpenIddictBuilder(services);
// Act
builder.AllowCustomFlow("urn:ietf:params:oauth:grant-type:custom_grant");
var provider = services.BuildServiceProvider();
var options = provider.GetRequiredService<IOptions<OpenIddictOptions>>();
// Assert
Assert.Contains("urn:ietf:params:oauth:grant-type:custom_grant", options.Value.GrantTypes);
}
[Fact]
public void AllowImplicitFlow_ImplicitFlowIsAddedToGrantTypes() {
// Arrange
var services = new ServiceCollection();
services.AddOptions();
var builder = new OpenIddictBuilder(services);
// Act
builder.AllowImplicitFlow();
var provider = services.BuildServiceProvider();
var options = provider.GetRequiredService<IOptions<OpenIddictOptions>>();
// Assert
Assert.Contains(OpenIdConnectConstants.GrantTypes.Implicit, options.Value.GrantTypes);
}
[Fact]
public void AllowPasswordFlow_PasswordFlowIsAddedToGrantTypes() {
// Arrange
var services = new ServiceCollection();
services.AddOptions();
var builder = new OpenIddictBuilder(services);
// Act
builder.AllowPasswordFlow();
var provider = services.BuildServiceProvider();
var options = provider.GetRequiredService<IOptions<OpenIddictOptions>>();
// Assert
Assert.Contains(OpenIdConnectConstants.GrantTypes.Password, options.Value.GrantTypes);
}
[Fact]
public void AllowRefreshTokenFlow_RefreshTokenFlowIsAddedToGrantTypes() {
// Arrange
var services = new ServiceCollection();
services.AddOptions();
var builder = new OpenIddictBuilder(services);
// Act
builder.AllowRefreshTokenFlow();
var provider = services.BuildServiceProvider();
var options = provider.GetRequiredService<IOptions<OpenIddictOptions>>();
// Assert
Assert.Contains(OpenIdConnectConstants.GrantTypes.RefreshToken, options.Value.GrantTypes);
}
[Fact]
public void DisableConfigurationEndpoint_ConfigurationEndpointIsDisabled() {
// Arrange
var services = new ServiceCollection();
services.AddOptions();
var builder = new OpenIddictBuilder(services);
// Act
builder.DisableConfigurationEndpoint();
var provider = services.BuildServiceProvider();
var options = provider.GetRequiredService<IOptions<OpenIddictOptions>>();
// Assert
Assert.Equal(PathString.Empty, options.Value.ConfigurationEndpointPath);
}
[Fact]
public void DisableCryptographyEndpoint_CryptographyEndpointIsDisabled() {
// Arrange
var services = new ServiceCollection();
services.AddOptions();
var builder = new OpenIddictBuilder(services);
// Act
builder.DisableCryptographyEndpoint();
var provider = services.BuildServiceProvider();
var options = provider.GetRequiredService<IOptions<OpenIddictOptions>>();
// Assert
Assert.Equal(PathString.Empty, options.Value.CryptographyEndpointPath);
}
[Fact]
public void EnableAuthorizationEndpoint_AuthorizationEndpointIsEnabled() {
// Arrange
var services = new ServiceCollection();
services.AddOptions();
var builder = new OpenIddictBuilder(services);
// Act
builder.EnableAuthorizationEndpoint("/endpoint-path");
var provider = services.BuildServiceProvider();
var options = provider.GetRequiredService<IOptions<OpenIddictOptions>>();
// Assert
Assert.Equal("/endpoint-path", options.Value.AuthorizationEndpointPath);
}
[Fact]
public void EnableIntrospectionEndpoint_IntrospectionEndpointIsEnabled() {
// Arrange
var services = new ServiceCollection();
services.AddOptions();
var builder = new OpenIddictBuilder(services);
// Act
builder.EnableIntrospectionEndpoint("/endpoint-path");
var provider = services.BuildServiceProvider();
var options = provider.GetRequiredService<IOptions<OpenIddictOptions>>();
// Assert
Assert.Equal("/endpoint-path", options.Value.IntrospectionEndpointPath);
}
[Fact]
public void EnableLogoutEndpoint_LogoutEndpointIsEnabled() {
// Arrange
var services = new ServiceCollection();
services.AddOptions();
var builder = new OpenIddictBuilder(services);
// Act
builder.EnableLogoutEndpoint("/endpoint-path");
var provider = services.BuildServiceProvider();
var options = provider.GetRequiredService<IOptions<OpenIddictOptions>>();
// Assert
Assert.Equal("/endpoint-path", options.Value.LogoutEndpointPath);
}
[Fact]
public void EnableRequestCaching_RequestCachingIsEnabled() {
// Arrange
var services = new ServiceCollection();
services.AddOptions();
var builder = new OpenIddictBuilder(services);
// Act
builder.EnableRequestCaching();
var provider = services.BuildServiceProvider();
var options = provider.GetRequiredService<IOptions<OpenIddictOptions>>();
// Assert
Assert.True(options.Value.EnableRequestCaching);
}
[Fact]
public void EnableRevocationEndpoint_RevocationEndpointIsEnabled() {
// Arrange
var services = new ServiceCollection();
services.AddOptions();
var builder = new OpenIddictBuilder(services);
// Act
builder.EnableRevocationEndpoint("/endpoint-path");
var provider = services.BuildServiceProvider();
var options = provider.GetRequiredService<IOptions<OpenIddictOptions>>();
// Assert
Assert.Equal("/endpoint-path", options.Value.RevocationEndpointPath);
}
[Fact]
public void EnableTokenEndpoint_TokenEndpointIsEnabled() {
// Arrange
var services = new ServiceCollection();
services.AddOptions();
var builder = new OpenIddictBuilder(services);
// Act
builder.EnableTokenEndpoint("/endpoint-path");
var provider = services.BuildServiceProvider();
var options = provider.GetRequiredService<IOptions<OpenIddictOptions>>();
// Assert
Assert.Equal("/endpoint-path", options.Value.TokenEndpointPath);
}
[Fact]
public void EnableUserinfoEndpoint_UserinfoEndpointIsEnabled() {
// Arrange
var services = new ServiceCollection();
services.AddOptions();
var builder = new OpenIddictBuilder(services);
// Act
builder.EnableUserinfoEndpoint("/endpoint-path");
var provider = services.BuildServiceProvider();
var options = provider.GetRequiredService<IOptions<OpenIddictOptions>>();
// Assert
Assert.Equal("/endpoint-path", options.Value.UserinfoEndpointPath);
}
[Fact]
public void RequireClientIdentification_ClientIdentificationIsEnforced() {
// Arrange
var services = new ServiceCollection();
services.AddOptions();
var builder = new OpenIddictBuilder(services);
// Act
builder.RequireClientIdentification();
var provider = services.BuildServiceProvider();
var options = provider.GetRequiredService<IOptions<OpenIddictOptions>>();
// Assert
Assert.True(options.Value.RequireClientIdentification);
}
[Fact]
public void SetAccessTokenLifetime_DefaultAccessTokenLifetimeIsReplaced() {
// Arrange
var services = new ServiceCollection();
services.AddOptions();
var builder = new OpenIddictBuilder(services);
// Act
builder.SetAccessTokenLifetime(TimeSpan.FromMinutes(42));
var provider = services.BuildServiceProvider();
var options = provider.GetRequiredService<IOptions<OpenIddictOptions>>();
// Assert
Assert.Equal(TimeSpan.FromMinutes(42), options.Value.AccessTokenLifetime);
}
[Fact]
public void SetAuthorizationCodeLifetime_DefaultAuthorizationCodeLifetimeIsReplaced() {
// Arrange
var services = new ServiceCollection();
services.AddOptions();
var builder = new OpenIddictBuilder(services);
// Act
builder.SetAuthorizationCodeLifetime(TimeSpan.FromMinutes(42));
var provider = services.BuildServiceProvider();
var options = provider.GetRequiredService<IOptions<OpenIddictOptions>>();
// Assert
Assert.Equal(TimeSpan.FromMinutes(42), options.Value.AuthorizationCodeLifetime);
}
[Fact]
public void SetIdentityTokenLifetime_DefaultIdentityTokenLifetimeIsReplaced() {
// Arrange
var services = new ServiceCollection();
services.AddOptions();
var builder = new OpenIddictBuilder(services);
// Act
builder.SetIdentityTokenLifetime(TimeSpan.FromMinutes(42));
var provider = services.BuildServiceProvider();
var options = provider.GetRequiredService<IOptions<OpenIddictOptions>>();
// Assert
Assert.Equal(TimeSpan.FromMinutes(42), options.Value.IdentityTokenLifetime);
}
[Fact]
public void SetRefreshTokenLifetime_DefaultRefreshTokenLifetimeIsReplaced() {
// Arrange
var services = new ServiceCollection();
services.AddOptions();
var builder = new OpenIddictBuilder(services);
// Act
builder.SetRefreshTokenLifetime(TimeSpan.FromMinutes(42));
var provider = services.BuildServiceProvider();
var options = provider.GetRequiredService<IOptions<OpenIddictOptions>>();
// Assert
Assert.Equal(TimeSpan.FromMinutes(42), options.Value.RefreshTokenLifetime);
}
[Fact]
public void UseDataProtectionProvider_DefaultProviderIsReplaced() {
// Arrange
var services = new ServiceCollection();
services.AddOptions();
var builder = new OpenIddictBuilder(services);
// Act
builder.UseDataProtectionProvider(new EphemeralDataProtectionProvider());
var provider = services.BuildServiceProvider();
var options = provider.GetRequiredService<IOptions<OpenIddictOptions>>();
// Assert
Assert.IsType(typeof(EphemeralDataProtectionProvider), options.Value.DataProtectionProvider);
}
[Fact]
public void UseJsonWebTokens_AccessTokenHandlerIsCorrectlySet() {
// Arrange
var services = new ServiceCollection();
services.AddOptions();
var builder = new OpenIddictBuilder(services);
// Act
builder.UseJsonWebTokens();
var provider = services.BuildServiceProvider();
var options = provider.GetRequiredService<IOptions<OpenIddictOptions>>();
// Assert
Assert.IsType(typeof(JwtSecurityTokenHandler), options.Value.AccessTokenHandler);
}
}
}

228
test/OpenIddict.Core.Tests/OpenIddictExtensionsTests.cs

@ -1,238 +1,56 @@
using System;
using AspNet.Security.OpenIdConnect.Primitives;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Builder.Internal;
using Microsoft.AspNetCore.DataProtection;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;
using Moq;
using OpenIddict.Infrastructure;
using OpenIddict.Models;
using Xunit;
namespace OpenIddict.Core.Tests {
public class OpenIddictExtensionsTests {
[Fact]
public void AddOpenIddict_RegistersProvider() {
[Theory]
[InlineData(typeof(OpenIddictApplicationManager<OpenIddictApplication>))]
[InlineData(typeof(OpenIddictAuthorizationManager<OpenIddictAuthorization>))]
[InlineData(typeof(OpenIddictScopeManager<OpenIddictScope>))]
[InlineData(typeof(OpenIddictTokenManager<OpenIddictToken>))]
public void AddOpenIddict_KeyTypeDefaultsToString(Type type) {
// Arrange
var services = new ServiceCollection();
// Act
services.AddOpenIddict<object, object, object, object>();
var provider = services.BuildServiceProvider();
var options = provider.GetRequiredService<IOptions<OpenIddictOptions>>();
services.AddOpenIddict();
// Assert
Assert.IsType(typeof(OpenIddictProvider<object, object, object, object>), options.Value.Provider);
Assert.Contains(services, service => service.ImplementationType == type);
}
[Theory]
[InlineData(typeof(IDataProtectionProvider))]
[InlineData(typeof(OpenIddictApplicationManager<object>))]
[InlineData(typeof(OpenIddictAuthorizationManager<object>))]
[InlineData(typeof(OpenIddictScopeManager<object>))]
[InlineData(typeof(OpenIddictTokenManager<object>))]
[InlineData(typeof(OpenIddictServices<object, object, object, object>))]
public void AddOpenIddict_RegistersCoreServices(Type type) {
[InlineData(typeof(OpenIddictApplicationManager<OpenIddictApplication<Guid, OpenIddictToken<Guid>>>))]
[InlineData(typeof(OpenIddictAuthorizationManager<OpenIddictAuthorization<Guid, OpenIddictToken<Guid>>>))]
[InlineData(typeof(OpenIddictScopeManager<OpenIddictScope<Guid>>))]
[InlineData(typeof(OpenIddictTokenManager<OpenIddictToken<Guid>>))]
public void AddOpenIddict_KeyTypeCanBeOverriden(Type type) {
// Arrange
var services = new ServiceCollection();
// Act
services.AddOpenIddict<object, object, object, object>();
services.AddOpenIddict<Guid>();
// Assert
Assert.Contains(services, service => service.ServiceType == type);
}
[Fact]
public void UseOpenIddict_AnExceptionIsThrownWhenNoDistributedCacheIsRegisteredIfRequestCachingIsEnabled() {
// Arrange
var services = new ServiceCollection();
services.AddOpenIddict<object, object, object, object>()
.EnableRequestCaching();
var builder = new ApplicationBuilder(services.BuildServiceProvider());
// Act and assert
var exception = Assert.Throws<InvalidOperationException>(() => builder.UseOpenIddict());
Assert.Equal("A distributed cache implementation must be registered in the OpenIddict options " +
"or in the dependency injection container when enabling request caching support.", exception.Message);
}
[Fact]
public void UseOpenIddict_AnExceptionIsThrownWhenNoSigningCredentialsIsRegistered() {
// Arrange
var services = new ServiceCollection();
services.AddOpenIddict<object, object, object, object>();
var builder = new ApplicationBuilder(services.BuildServiceProvider());
// Act and assert
var exception = Assert.Throws<InvalidOperationException>(() => builder.UseOpenIddict());
Assert.Equal("At least one signing key must be registered. Consider registering a X.509 " +
"certificate using 'services.AddOpenIddict().AddSigningCertificate()' or call " +
"'services.AddOpenIddict().AddEphemeralSigningKey()' to use an ephemeral key.", exception.Message);
}
[Fact]
public void UseOpenIddict_AnExceptionIsThrownWhenNoFlowIsEnabled() {
// Arrange
var services = new ServiceCollection();
services.AddOpenIddict<object, object, object, object>()
.AddEphemeralSigningKey();
var builder = new ApplicationBuilder(services.BuildServiceProvider());
// Act and assert
var exception = Assert.Throws<InvalidOperationException>(() => builder.UseOpenIddict());
Assert.Equal("At least one OAuth2/OpenID Connect flow must be enabled.", exception.Message);
Assert.Contains(services, service => service.ImplementationType == type);
}
[Theory]
[InlineData(OpenIdConnectConstants.GrantTypes.AuthorizationCode)]
[InlineData(OpenIdConnectConstants.GrantTypes.Implicit)]
public void UseOpenIddict_AnExceptionIsThrownWhenAuthorizationEndpointIsDisabled(string flow) {
// Arrange
var services = new ServiceCollection();
services.AddOpenIddict<object, object, object, object>()
.AddEphemeralSigningKey()
.Configure(options => options.GrantTypes.Add(flow))
.Configure(options => options.AuthorizationEndpointPath = PathString.Empty);
var builder = new ApplicationBuilder(services.BuildServiceProvider());
// Act and assert
var exception = Assert.Throws<InvalidOperationException>(() => builder.UseOpenIddict());
Assert.Equal("The authorization endpoint must be enabled to use " +
"the authorization code and implicit flows.", exception.Message);
}
[Theory]
[InlineData(OpenIdConnectConstants.GrantTypes.AuthorizationCode)]
[InlineData(OpenIdConnectConstants.GrantTypes.ClientCredentials)]
[InlineData(OpenIdConnectConstants.GrantTypes.Password)]
[InlineData(OpenIdConnectConstants.GrantTypes.RefreshToken)]
public void UseOpenIddict_AnExceptionIsThrownWhenTokenEndpointIsDisabled(string flow) {
// Arrange
var services = new ServiceCollection();
services.AddOpenIddict<object, object, object, object>()
.AddEphemeralSigningKey()
.EnableAuthorizationEndpoint("/connect/authorize")
.Configure(options => options.GrantTypes.Add(flow))
.Configure(options => options.TokenEndpointPath = PathString.Empty);
var builder = new ApplicationBuilder(services.BuildServiceProvider());
// Act and assert
var exception = Assert.Throws<InvalidOperationException>(() => builder.UseOpenIddict());
Assert.Equal("The token endpoint must be enabled to use the authorization code, " +
"client credentials, password and refresh token flows.", exception.Message);
}
[Fact]
public void UseOpenIddict_OpenIdConnectServerMiddlewareIsRegistered() {
[InlineData(typeof(OpenIddictApplicationManager<object>))]
[InlineData(typeof(OpenIddictAuthorizationManager<object>))]
[InlineData(typeof(OpenIddictScopeManager<object>))]
[InlineData(typeof(OpenIddictTokenManager<object>))]
public void AddOpenIddict_DefaultEntitiesCanBeReplaced(Type type) {
// Arrange
var services = new ServiceCollection();
services.AddOpenIddict<object, object, object, object>()
.AddEphemeralSigningKey()
.AllowImplicitFlow()
.EnableAuthorizationEndpoint("/connect/authorize");
var builder = new Mock<IApplicationBuilder>();
builder.SetupGet(mock => mock.ApplicationServices)
.Returns(services.BuildServiceProvider());
// Act
builder.Object.UseOpenIddict();
services.AddOpenIddict<object, object, object, object>();
// Assert
builder.Verify(mock => mock.Use(It.IsAny<Func<RequestDelegate, RequestDelegate>>()), Times.Once());
}
[Theory]
[InlineData(true)]
[InlineData(false)]
public void IsAuthorizationCodeFlowEnabled_ReturnsAppropriateResult(bool enabled) {
// Arrange
var options = new OpenIddictOptions();
if (enabled) {
options.GrantTypes.Add(OpenIdConnectConstants.GrantTypes.AuthorizationCode);
}
// Act and assert
Assert.Equal(enabled, options.IsAuthorizationCodeFlowEnabled());
}
[Theory]
[InlineData(true)]
[InlineData(false)]
public void IsClientCredentialsFlowEnabled_ReturnsAppropriateResult(bool enabled) {
// Arrange
var options = new OpenIddictOptions();
if (enabled) {
options.GrantTypes.Add(OpenIdConnectConstants.GrantTypes.ClientCredentials);
}
// Act and assert
Assert.Equal(enabled, options.IsClientCredentialsFlowEnabled());
}
[Theory]
[InlineData(true)]
[InlineData(false)]
public void IsImplicitFlowEnabled_ReturnsAppropriateResult(bool enabled) {
// Arrange
var options = new OpenIddictOptions();
if (enabled) {
options.GrantTypes.Add(OpenIdConnectConstants.GrantTypes.Implicit);
}
// Act and assert
Assert.Equal(enabled, options.IsImplicitFlowEnabled());
}
[Theory]
[InlineData(true)]
[InlineData(false)]
public void IsPasswordFlowEnabled_ReturnsAppropriateResult(bool enabled) {
// Arrange
var options = new OpenIddictOptions();
if (enabled) {
options.GrantTypes.Add(OpenIdConnectConstants.GrantTypes.Password);
}
// Act and assert
Assert.Equal(enabled, options.IsPasswordFlowEnabled());
}
[Theory]
[InlineData(true)]
[InlineData(false)]
public void IsRefreshTokenFlowEnabled_ReturnsAppropriateResult(bool enabled) {
// Arrange
var options = new OpenIddictOptions();
if (enabled) {
options.GrantTypes.Add(OpenIdConnectConstants.GrantTypes.RefreshToken);
}
// Act and assert
Assert.Equal(enabled, options.IsRefreshTokenFlowEnabled());
Assert.Contains(services, service => service.ServiceType == type);
}
}
}

14
test/OpenIddict.Core.Tests/project.json

@ -1,19 +1,11 @@
{
"buildOptions": {
"warningsAsErrors": true,
"embed": {
"include": [ "Certificate.pfx" ]
}
"warningsAsErrors": true
},
"dependencies": {
"AspNet.Security.OpenIdConnect.Client": "1.0.0-beta7-final",
"dotnet-test-xunit": "2.2.0-preview2-build1029",
"Microsoft.AspNetCore.Diagnostics": "1.0.0",
"Microsoft.AspNetCore.TestHost": "1.0.0",
"Microsoft.Extensions.Caching.Memory": "1.0.0",
"Microsoft.Extensions.Logging.Debug": "1.0.0",
"Moq": "4.6.38-alpha",
"OpenIddict.Core": { "target": "project" },
"xunit": "2.2.0-beta2-build3300"
@ -21,10 +13,6 @@
"frameworks": {
"netcoreapp1.0": {
"buildOptions": {
"define": [ "SUPPORTS_ECDSA" ]
},
"dependencies": {
"Microsoft.NETCore.App": { "type": "platform", "version": "1.0.0" }
},

43
test/OpenIddict.EntityFramework.Tests/OpenIddictExtensionsTests.cs

@ -1,51 +1,40 @@
using System;
using Microsoft.AspNetCore.Builder;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
using OpenIddict.Models;
using Xunit;
namespace OpenIddict.EntityFramework.Tests {
public class OpenIddictExtensionsTests {
[Theory]
[InlineData(typeof(OpenIddictApplicationStore<OpenIddictApplication<Guid>, OpenIddictToken<Guid>, OpenIddictDbContext, Guid>))]
[InlineData(typeof(OpenIddictAuthorizationStore<OpenIddictAuthorization<Guid>, OpenIddictToken<Guid>, OpenIddictDbContext, Guid>))]
[InlineData(typeof(OpenIddictScopeStore<OpenIddictScope<Guid>, OpenIddictDbContext, Guid>))]
[InlineData(typeof(OpenIddictTokenStore<OpenIddictToken<Guid>, OpenIddictAuthorization<Guid>, OpenIddictDbContext, Guid>))]
public void AddEntityFramework_RegistersEntityFrameworkStores(Type type) {
[InlineData(typeof(OpenIddictApplicationStore<OpenIddictApplication<Guid, OpenIddictToken<Guid>>, OpenIddictToken<Guid>, DbContext, Guid>))]
[InlineData(typeof(OpenIddictAuthorizationStore<OpenIddictAuthorization<Guid, OpenIddictToken<Guid>>, OpenIddictToken<Guid>, DbContext, Guid>))]
[InlineData(typeof(OpenIddictScopeStore<OpenIddictScope<Guid>, DbContext, Guid>))]
[InlineData(typeof(OpenIddictTokenStore<OpenIddictToken<Guid>, OpenIddictAuthorization<Guid, OpenIddictToken<Guid>>, DbContext, Guid>))]
public void AddEntityFrameworkStores_RegistersEntityFrameworkStores(Type type) {
// Arrange
var services = new ServiceCollection();
var builder = new OpenIddictBuilder(services) {
ApplicationType = typeof(OpenIddictApplication<Guid>),
AuthorizationType = typeof(OpenIddictAuthorization<Guid>),
ScopeType = typeof(OpenIddictScope<Guid>),
TokenType = typeof(OpenIddictToken<Guid>)
};
// Act
builder.AddEntityFramework<OpenIddictDbContext, Guid>();
services.AddOpenIddict<Guid>()
.AddEntityFrameworkStores<DbContext, Guid>();
// Assert
Assert.Contains(services, service => service.ImplementationType == type);
}
[Theory]
[InlineData(typeof(OpenIddictApplicationStore<OpenIddictApplication, OpenIddictToken, OpenIddictDbContext, string>))]
[InlineData(typeof(OpenIddictAuthorizationStore<OpenIddictAuthorization, OpenIddictToken, OpenIddictDbContext, string>))]
[InlineData(typeof(OpenIddictScopeStore<OpenIddictScope, OpenIddictDbContext, string>))]
[InlineData(typeof(OpenIddictTokenStore<OpenIddictToken, OpenIddictAuthorization, OpenIddictDbContext, string>))]
public void AddEntityFramework_KeyTypeDefaultsToString(Type type) {
[InlineData(typeof(OpenIddictApplicationStore<OpenIddictApplication, OpenIddictToken, DbContext, string>))]
[InlineData(typeof(OpenIddictAuthorizationStore<OpenIddictAuthorization, OpenIddictToken, DbContext, string>))]
[InlineData(typeof(OpenIddictScopeStore<OpenIddictScope, DbContext, string>))]
[InlineData(typeof(OpenIddictTokenStore<OpenIddictToken, OpenIddictAuthorization, DbContext, string>))]
public void AddEntityFrameworkStores_KeyTypeDefaultsToString(Type type) {
// Arrange
var services = new ServiceCollection();
var builder = new OpenIddictBuilder(services) {
ApplicationType = typeof(OpenIddictApplication),
AuthorizationType = typeof(OpenIddictAuthorization),
ScopeType = typeof(OpenIddictScope),
TokenType = typeof(OpenIddictToken)
};
// Act
builder.AddEntityFramework<OpenIddictDbContext>();
services.AddOpenIddict()
.AddEntityFrameworkStores<DbContext>();
// Assert
Assert.Contains(services, service => service.ImplementationType == type);

2
test/OpenIddict.EntityFramework.Tests/project.json

@ -6,8 +6,6 @@
"dependencies": {
"dotnet-test-xunit": "2.2.0-preview2-build1029",
"Microsoft.AspNetCore.TestHost": "1.0.0",
"Microsoft.Extensions.Caching.Memory": "1.0.0",
"Microsoft.Extensions.Logging.Debug": "1.0.0",
"Moq": "4.6.38-alpha",
"OpenIddict.EntityFramework": { "target": "project" },
"xunit": "2.2.0-beta2-build3300"

3
test/OpenIddict.Mvc.Tests/OpenIddictExtensionsTests.cs

@ -1,5 +1,4 @@
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;
using Xunit;

2
test/OpenIddict.Mvc.Tests/project.json

@ -6,8 +6,6 @@
"dependencies": {
"dotnet-test-xunit": "2.2.0-preview2-build1029",
"Microsoft.AspNetCore.TestHost": "1.0.0",
"Microsoft.Extensions.Caching.Memory": "1.0.0",
"Microsoft.Extensions.Logging.Debug": "1.0.0",
"Moq": "4.6.38-alpha",
"OpenIddict.Mvc": { "target": "project" },
"xunit": "2.2.0-beta2-build3300"

0
test/OpenIddict.Core.Tests/Certificate.pfx → test/OpenIddict.Tests/Certificate.pfx

526
test/OpenIddict.Tests/OpenIddictBuilderTests.cs

@ -0,0 +1,526 @@
using System;
using System.IdentityModel.Tokens.Jwt;
using System.Reflection;
using AspNet.Security.OpenIdConnect.Primitives;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.DataProtection;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;
using Microsoft.IdentityModel.Tokens;
using Moq;
using Xunit;
namespace OpenIddict.Tests {
public class OpenIddictBuilderTests {
[Fact]
public void Configure_OptionsAreCorrectlyAmended() {
// Arrange
var services = new ServiceCollection();
services.AddOptions();
var builder = new OpenIddictBuilder(services);
// Act
builder.Configure(configuration => configuration.Description.DisplayName = "OpenIddict");
var provider = services.BuildServiceProvider();
var options = provider.GetRequiredService<IOptions<OpenIddictOptions>>();
// Assert
Assert.Equal("OpenIddict", options.Value.Description.DisplayName);
}
[Fact]
public void AddEphemeralSigningKey_SigningKeyIsCorrectlyAdded() {
// Arrange
var services = new ServiceCollection();
services.AddOptions();
var builder = new OpenIddictBuilder(services);
// Act
builder.AddEphemeralSigningKey();
var provider = services.BuildServiceProvider();
var options = provider.GetRequiredService<IOptions<OpenIddictOptions>>();
// Assert
Assert.Equal(1, options.Value.SigningCredentials.Count);
}
[Theory]
[InlineData(SecurityAlgorithms.RsaSha256Signature)]
[InlineData(SecurityAlgorithms.RsaSha384Signature)]
[InlineData(SecurityAlgorithms.RsaSha512Signature)]
#if SUPPORTS_ECDSA
[InlineData(SecurityAlgorithms.EcdsaSha256Signature)]
[InlineData(SecurityAlgorithms.EcdsaSha384Signature)]
[InlineData(SecurityAlgorithms.EcdsaSha512Signature)]
#endif
public void AddEphemeralSigningKey_SigningCredentialsUseSpecifiedAlgorithm(string algorithm) {
// Arrange
var services = new ServiceCollection();
services.AddOptions();
var builder = new OpenIddictBuilder(services);
// Act
builder.AddEphemeralSigningKey(algorithm);
var provider = services.BuildServiceProvider();
var options = provider.GetRequiredService<IOptions<OpenIddictOptions>>();
var credentials = options.Value.SigningCredentials[0];
// Assert
Assert.Equal(algorithm, credentials.Algorithm);
}
[Theory]
[InlineData(SecurityAlgorithms.HmacSha256Signature)]
[InlineData(SecurityAlgorithms.RsaSha256Signature)]
#if SUPPORTS_ECDSA
[InlineData(SecurityAlgorithms.EcdsaSha256Signature)]
[InlineData(SecurityAlgorithms.EcdsaSha384Signature)]
[InlineData(SecurityAlgorithms.EcdsaSha512Signature)]
#endif
public void AddSigningKey_SigningKeyIsCorrectlyAdded(string algorithm) {
// Arrange
var services = new ServiceCollection();
services.AddOptions();
var builder = new OpenIddictBuilder(services);
var factory = Mock.Of<CryptoProviderFactory>(mock =>
mock.IsSupportedAlgorithm(algorithm, It.IsAny<SecurityKey>()));
var key = Mock.Of<SecurityKey>(mock => mock.CryptoProviderFactory == factory);
// Act
builder.AddSigningKey(key);
var provider = services.BuildServiceProvider();
var options = provider.GetRequiredService<IOptions<OpenIddictOptions>>();
// Assert
Assert.Same(key, options.Value.SigningCredentials[0].Key);
}
[Fact]
public void AddSigningCertificate_SigningKeyIsCorrectlyAdded() {
// Arrange
var services = new ServiceCollection();
services.AddOptions();
var builder = new OpenIddictBuilder(services);
// Act
builder.AddSigningCertificate(
assembly: typeof(OpenIddictBuilderTests).GetTypeInfo().Assembly,
resource: "OpenIddict.Tests.Certificate.pfx",
password: "OpenIddict");
var provider = services.BuildServiceProvider();
var options = provider.GetRequiredService<IOptions<OpenIddictOptions>>();
// Assert
Assert.IsType(typeof(X509SecurityKey), options.Value.SigningCredentials[0].Key);
}
[Fact]
public void AllowAuthorizationCodeFlow_CodeFlowIsAddedToGrantTypes() {
// Arrange
var services = new ServiceCollection();
services.AddOptions();
var builder = new OpenIddictBuilder(services);
// Act
builder.AllowAuthorizationCodeFlow();
var provider = services.BuildServiceProvider();
var options = provider.GetRequiredService<IOptions<OpenIddictOptions>>();
// Assert
Assert.Contains(OpenIdConnectConstants.GrantTypes.AuthorizationCode, options.Value.GrantTypes);
}
[Fact]
public void AllowClientCredentialsFlow_ClientCredentialsFlowIsAddedToGrantTypes() {
// Arrange
var services = new ServiceCollection();
services.AddOptions();
var builder = new OpenIddictBuilder(services);
// Act
builder.AllowClientCredentialsFlow();
var provider = services.BuildServiceProvider();
var options = provider.GetRequiredService<IOptions<OpenIddictOptions>>();
// Assert
Assert.Contains(OpenIdConnectConstants.GrantTypes.ClientCredentials, options.Value.GrantTypes);
}
[Fact]
public void AllowCustomFlow_CustomFlowIsAddedToGrantTypes() {
// Arrange
var services = new ServiceCollection();
services.AddOptions();
var builder = new OpenIddictBuilder(services);
// Act
builder.AllowCustomFlow("urn:ietf:params:oauth:grant-type:custom_grant");
var provider = services.BuildServiceProvider();
var options = provider.GetRequiredService<IOptions<OpenIddictOptions>>();
// Assert
Assert.Contains("urn:ietf:params:oauth:grant-type:custom_grant", options.Value.GrantTypes);
}
[Fact]
public void AllowImplicitFlow_ImplicitFlowIsAddedToGrantTypes() {
// Arrange
var services = new ServiceCollection();
services.AddOptions();
var builder = new OpenIddictBuilder(services);
// Act
builder.AllowImplicitFlow();
var provider = services.BuildServiceProvider();
var options = provider.GetRequiredService<IOptions<OpenIddictOptions>>();
// Assert
Assert.Contains(OpenIdConnectConstants.GrantTypes.Implicit, options.Value.GrantTypes);
}
[Fact]
public void AllowPasswordFlow_PasswordFlowIsAddedToGrantTypes() {
// Arrange
var services = new ServiceCollection();
services.AddOptions();
var builder = new OpenIddictBuilder(services);
// Act
builder.AllowPasswordFlow();
var provider = services.BuildServiceProvider();
var options = provider.GetRequiredService<IOptions<OpenIddictOptions>>();
// Assert
Assert.Contains(OpenIdConnectConstants.GrantTypes.Password, options.Value.GrantTypes);
}
[Fact]
public void AllowRefreshTokenFlow_RefreshTokenFlowIsAddedToGrantTypes() {
// Arrange
var services = new ServiceCollection();
services.AddOptions();
var builder = new OpenIddictBuilder(services);
// Act
builder.AllowRefreshTokenFlow();
var provider = services.BuildServiceProvider();
var options = provider.GetRequiredService<IOptions<OpenIddictOptions>>();
// Assert
Assert.Contains(OpenIdConnectConstants.GrantTypes.RefreshToken, options.Value.GrantTypes);
}
[Fact]
public void DisableConfigurationEndpoint_ConfigurationEndpointIsDisabled() {
// Arrange
var services = new ServiceCollection();
services.AddOptions();
var builder = new OpenIddictBuilder(services);
// Act
builder.DisableConfigurationEndpoint();
var provider = services.BuildServiceProvider();
var options = provider.GetRequiredService<IOptions<OpenIddictOptions>>();
// Assert
Assert.Equal(PathString.Empty, options.Value.ConfigurationEndpointPath);
}
[Fact]
public void DisableCryptographyEndpoint_CryptographyEndpointIsDisabled() {
// Arrange
var services = new ServiceCollection();
services.AddOptions();
var builder = new OpenIddictBuilder(services);
// Act
builder.DisableCryptographyEndpoint();
var provider = services.BuildServiceProvider();
var options = provider.GetRequiredService<IOptions<OpenIddictOptions>>();
// Assert
Assert.Equal(PathString.Empty, options.Value.CryptographyEndpointPath);
}
[Fact]
public void EnableAuthorizationEndpoint_AuthorizationEndpointIsEnabled() {
// Arrange
var services = new ServiceCollection();
services.AddOptions();
var builder = new OpenIddictBuilder(services);
// Act
builder.EnableAuthorizationEndpoint("/endpoint-path");
var provider = services.BuildServiceProvider();
var options = provider.GetRequiredService<IOptions<OpenIddictOptions>>();
// Assert
Assert.Equal("/endpoint-path", options.Value.AuthorizationEndpointPath);
}
[Fact]
public void EnableIntrospectionEndpoint_IntrospectionEndpointIsEnabled() {
// Arrange
var services = new ServiceCollection();
services.AddOptions();
var builder = new OpenIddictBuilder(services);
// Act
builder.EnableIntrospectionEndpoint("/endpoint-path");
var provider = services.BuildServiceProvider();
var options = provider.GetRequiredService<IOptions<OpenIddictOptions>>();
// Assert
Assert.Equal("/endpoint-path", options.Value.IntrospectionEndpointPath);
}
[Fact]
public void EnableLogoutEndpoint_LogoutEndpointIsEnabled() {
// Arrange
var services = new ServiceCollection();
services.AddOptions();
var builder = new OpenIddictBuilder(services);
// Act
builder.EnableLogoutEndpoint("/endpoint-path");
var provider = services.BuildServiceProvider();
var options = provider.GetRequiredService<IOptions<OpenIddictOptions>>();
// Assert
Assert.Equal("/endpoint-path", options.Value.LogoutEndpointPath);
}
[Fact]
public void EnableRequestCaching_RequestCachingIsEnabled() {
// Arrange
var services = new ServiceCollection();
services.AddOptions();
var builder = new OpenIddictBuilder(services);
// Act
builder.EnableRequestCaching();
var provider = services.BuildServiceProvider();
var options = provider.GetRequiredService<IOptions<OpenIddictOptions>>();
// Assert
Assert.True(options.Value.EnableRequestCaching);
}
[Fact]
public void EnableRevocationEndpoint_RevocationEndpointIsEnabled() {
// Arrange
var services = new ServiceCollection();
services.AddOptions();
var builder = new OpenIddictBuilder(services);
// Act
builder.EnableRevocationEndpoint("/endpoint-path");
var provider = services.BuildServiceProvider();
var options = provider.GetRequiredService<IOptions<OpenIddictOptions>>();
// Assert
Assert.Equal("/endpoint-path", options.Value.RevocationEndpointPath);
}
[Fact]
public void EnableTokenEndpoint_TokenEndpointIsEnabled() {
// Arrange
var services = new ServiceCollection();
services.AddOptions();
var builder = new OpenIddictBuilder(services);
// Act
builder.EnableTokenEndpoint("/endpoint-path");
var provider = services.BuildServiceProvider();
var options = provider.GetRequiredService<IOptions<OpenIddictOptions>>();
// Assert
Assert.Equal("/endpoint-path", options.Value.TokenEndpointPath);
}
[Fact]
public void EnableUserinfoEndpoint_UserinfoEndpointIsEnabled() {
// Arrange
var services = new ServiceCollection();
services.AddOptions();
var builder = new OpenIddictBuilder(services);
// Act
builder.EnableUserinfoEndpoint("/endpoint-path");
var provider = services.BuildServiceProvider();
var options = provider.GetRequiredService<IOptions<OpenIddictOptions>>();
// Assert
Assert.Equal("/endpoint-path", options.Value.UserinfoEndpointPath);
}
[Fact]
public void RequireClientIdentification_ClientIdentificationIsEnforced() {
// Arrange
var services = new ServiceCollection();
services.AddOptions();
var builder = new OpenIddictBuilder(services);
// Act
builder.RequireClientIdentification();
var provider = services.BuildServiceProvider();
var options = provider.GetRequiredService<IOptions<OpenIddictOptions>>();
// Assert
Assert.True(options.Value.RequireClientIdentification);
}
[Fact]
public void SetAccessTokenLifetime_DefaultAccessTokenLifetimeIsReplaced() {
// Arrange
var services = new ServiceCollection();
services.AddOptions();
var builder = new OpenIddictBuilder(services);
// Act
builder.SetAccessTokenLifetime(TimeSpan.FromMinutes(42));
var provider = services.BuildServiceProvider();
var options = provider.GetRequiredService<IOptions<OpenIddictOptions>>();
// Assert
Assert.Equal(TimeSpan.FromMinutes(42), options.Value.AccessTokenLifetime);
}
[Fact]
public void SetAuthorizationCodeLifetime_DefaultAuthorizationCodeLifetimeIsReplaced() {
// Arrange
var services = new ServiceCollection();
services.AddOptions();
var builder = new OpenIddictBuilder(services);
// Act
builder.SetAuthorizationCodeLifetime(TimeSpan.FromMinutes(42));
var provider = services.BuildServiceProvider();
var options = provider.GetRequiredService<IOptions<OpenIddictOptions>>();
// Assert
Assert.Equal(TimeSpan.FromMinutes(42), options.Value.AuthorizationCodeLifetime);
}
[Fact]
public void SetIdentityTokenLifetime_DefaultIdentityTokenLifetimeIsReplaced() {
// Arrange
var services = new ServiceCollection();
services.AddOptions();
var builder = new OpenIddictBuilder(services);
// Act
builder.SetIdentityTokenLifetime(TimeSpan.FromMinutes(42));
var provider = services.BuildServiceProvider();
var options = provider.GetRequiredService<IOptions<OpenIddictOptions>>();
// Assert
Assert.Equal(TimeSpan.FromMinutes(42), options.Value.IdentityTokenLifetime);
}
[Fact]
public void SetRefreshTokenLifetime_DefaultRefreshTokenLifetimeIsReplaced() {
// Arrange
var services = new ServiceCollection();
services.AddOptions();
var builder = new OpenIddictBuilder(services);
// Act
builder.SetRefreshTokenLifetime(TimeSpan.FromMinutes(42));
var provider = services.BuildServiceProvider();
var options = provider.GetRequiredService<IOptions<OpenIddictOptions>>();
// Assert
Assert.Equal(TimeSpan.FromMinutes(42), options.Value.RefreshTokenLifetime);
}
[Fact]
public void UseDataProtectionProvider_DefaultProviderIsReplaced() {
// Arrange
var services = new ServiceCollection();
services.AddOptions();
var builder = new OpenIddictBuilder(services);
// Act
builder.UseDataProtectionProvider(new EphemeralDataProtectionProvider());
var provider = services.BuildServiceProvider();
var options = provider.GetRequiredService<IOptions<OpenIddictOptions>>();
// Assert
Assert.IsType(typeof(EphemeralDataProtectionProvider), options.Value.DataProtectionProvider);
}
[Fact]
public void UseJsonWebTokens_AccessTokenHandlerIsCorrectlySet() {
// Arrange
var services = new ServiceCollection();
services.AddOptions();
var builder = new OpenIddictBuilder(services);
// Act
builder.UseJsonWebTokens();
var provider = services.BuildServiceProvider();
var options = provider.GetRequiredService<IOptions<OpenIddictOptions>>();
// Assert
Assert.IsType(typeof(JwtSecurityTokenHandler), options.Value.AccessTokenHandler);
}
}
}

721
test/OpenIddict.Tests/OpenIddictExtensionsTests.cs

@ -1,60 +1,731 @@
using System;
using System.IdentityModel.Tokens.Jwt;
using System.Reflection;
using AspNet.Security.OpenIdConnect.Primitives;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Builder.Internal;
using Microsoft.AspNetCore.DataProtection;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;
using Microsoft.IdentityModel.Tokens;
using Moq;
using Xunit;
namespace OpenIddict.Tests {
public class OpenIddictExtensionsTests {
[Fact]
public void UseOpenIddict_AnExceptionIsThrownWhenServicesAreNotRegistered() {
// Arrange
var services = new ServiceCollection();
var builder = new ApplicationBuilder(services.BuildServiceProvider());
// Act and assert
var exception = Assert.Throws<InvalidOperationException>(() => builder.UseOpenIddict());
Assert.Equal("The OpenIddict services cannot be resolved from the dependency injection container. " +
"Make sure 'services.AddOpenIddict()' is correctly called from 'ConfigureServices()'.", exception.Message);
}
[Fact]
public void UseOpenIddict_AnExceptionIsThrownWhenNoDistributedCacheIsRegisteredIfRequestCachingIsEnabled() {
// Arrange
var services = new ServiceCollection();
services.AddOpenIddict()
.EnableRequestCaching();
var builder = new ApplicationBuilder(services.BuildServiceProvider());
// Act and assert
var exception = Assert.Throws<InvalidOperationException>(() => builder.UseOpenIddict());
Assert.Equal("A distributed cache implementation must be registered in the OpenIddict options " +
"or in the dependency injection container when enabling request caching support.", exception.Message);
}
[Fact]
public void UseOpenIddict_AnExceptionIsThrownWhenNoSigningCredentialsIsRegistered() {
// Arrange
var services = new ServiceCollection();
services.AddOpenIddict();
var builder = new ApplicationBuilder(services.BuildServiceProvider());
// Act and assert
var exception = Assert.Throws<InvalidOperationException>(() => builder.UseOpenIddict());
Assert.Equal("At least one signing key must be registered. Consider registering a X.509 " +
"certificate using 'services.AddOpenIddict().AddSigningCertificate()' or call " +
"'services.AddOpenIddict().AddEphemeralSigningKey()' to use an ephemeral key.", exception.Message);
}
[Fact]
public void UseOpenIddict_AnExceptionIsThrownWhenNoFlowIsEnabled() {
// Arrange
var services = new ServiceCollection();
services.AddOpenIddict()
.AddEphemeralSigningKey();
var builder = new ApplicationBuilder(services.BuildServiceProvider());
// Act and assert
var exception = Assert.Throws<InvalidOperationException>(() => builder.UseOpenIddict());
Assert.Equal("At least one OAuth2/OpenID Connect flow must be enabled.", exception.Message);
}
[Theory]
[InlineData(typeof(OpenIddictApplicationManager<OpenIddictApplication<Guid>>))]
[InlineData(typeof(OpenIddictAuthorizationManager<OpenIddictAuthorization<Guid>>))]
[InlineData(typeof(OpenIddictScopeManager<OpenIddictScope<Guid>>))]
[InlineData(typeof(OpenIddictTokenManager<OpenIddictToken<Guid>>))]
public void AddOpenIddict_RegistersCoreManagers(Type type) {
[InlineData(OpenIdConnectConstants.GrantTypes.AuthorizationCode)]
[InlineData(OpenIdConnectConstants.GrantTypes.Implicit)]
public void UseOpenIddict_AnExceptionIsThrownWhenAuthorizationEndpointIsDisabled(string flow) {
// Arrange
var services = new ServiceCollection();
services.AddOpenIddict()
.AddEphemeralSigningKey()
.Configure(options => options.GrantTypes.Add(flow))
.Configure(options => options.AuthorizationEndpointPath = PathString.Empty);
var builder = new ApplicationBuilder(services.BuildServiceProvider());
// Act and assert
var exception = Assert.Throws<InvalidOperationException>(() => builder.UseOpenIddict());
Assert.Equal("The authorization endpoint must be enabled to use " +
"the authorization code and implicit flows.", exception.Message);
}
[Theory]
[InlineData(OpenIdConnectConstants.GrantTypes.AuthorizationCode)]
[InlineData(OpenIdConnectConstants.GrantTypes.ClientCredentials)]
[InlineData(OpenIdConnectConstants.GrantTypes.Password)]
[InlineData(OpenIdConnectConstants.GrantTypes.RefreshToken)]
public void UseOpenIddict_AnExceptionIsThrownWhenTokenEndpointIsDisabled(string flow) {
// Arrange
var services = new ServiceCollection();
services.AddOpenIddict()
.AddEphemeralSigningKey()
.EnableAuthorizationEndpoint("/connect/authorize")
.Configure(options => options.GrantTypes.Add(flow))
.Configure(options => options.TokenEndpointPath = PathString.Empty);
var builder = new ApplicationBuilder(services.BuildServiceProvider());
// Act and assert
var exception = Assert.Throws<InvalidOperationException>(() => builder.UseOpenIddict());
Assert.Equal("The token endpoint must be enabled to use the authorization code, " +
"client credentials, password and refresh token flows.", exception.Message);
}
[Fact]
public void Configure_OptionsAreCorrectlyAmended() {
// Arrange
var services = new ServiceCollection();
services.AddOptions();
var builder = new OpenIddictBuilder(services);
// Act
builder.Configure(configuration => configuration.Description.DisplayName = "OpenIddict");
var provider = services.BuildServiceProvider();
var options = provider.GetRequiredService<IOptions<OpenIddictOptions>>();
// Assert
Assert.Equal("OpenIddict", options.Value.Description.DisplayName);
}
[Fact]
public void UseOpenIddict_OpenIdConnectServerMiddlewareIsRegistered() {
// Arrange
var services = new ServiceCollection();
services.AddOpenIddict()
.AddEphemeralSigningKey()
.AllowImplicitFlow()
.EnableAuthorizationEndpoint("/connect/authorize");
var builder = new Mock<IApplicationBuilder>();
builder.SetupGet(mock => mock.ApplicationServices)
.Returns(services.BuildServiceProvider());
// Act
builder.Object.UseOpenIddict();
// Assert
builder.Verify(mock => mock.Use(It.IsAny<Func<RequestDelegate, RequestDelegate>>()), Times.Once());
}
[Fact]
public void AddEphemeralSigningKey_SigningKeyIsCorrectlyAdded() {
// Arrange
var services = new ServiceCollection();
services.AddOptions();
var builder = new OpenIddictBuilder(services);
// Act
services.AddOpenIddict<OpenIddictDbContext, Guid>();
builder.AddEphemeralSigningKey();
var provider = services.BuildServiceProvider();
var options = provider.GetRequiredService<IOptions<OpenIddictOptions>>();
// Assert
Assert.Contains(services, service => service.ImplementationType == type);
Assert.Equal(1, options.Value.SigningCredentials.Count);
}
[Theory]
[InlineData(typeof(OpenIddictApplicationStore<OpenIddictApplication<Guid>, OpenIddictToken<Guid>, OpenIddictDbContext, Guid>))]
[InlineData(typeof(OpenIddictAuthorizationStore<OpenIddictAuthorization<Guid>, OpenIddictToken<Guid>, OpenIddictDbContext, Guid>))]
[InlineData(typeof(OpenIddictScopeStore<OpenIddictScope<Guid>, OpenIddictDbContext, Guid>))]
[InlineData(typeof(OpenIddictTokenStore<OpenIddictToken<Guid>, OpenIddictAuthorization<Guid>, OpenIddictDbContext, Guid>))]
public void AddOpenIddict_RegistersEntityFrameworkStores(Type type) {
[InlineData(SecurityAlgorithms.RsaSha256Signature)]
[InlineData(SecurityAlgorithms.RsaSha384Signature)]
[InlineData(SecurityAlgorithms.RsaSha512Signature)]
#if SUPPORTS_ECDSA
[InlineData(SecurityAlgorithms.EcdsaSha256Signature)]
[InlineData(SecurityAlgorithms.EcdsaSha384Signature)]
[InlineData(SecurityAlgorithms.EcdsaSha512Signature)]
#endif
public void AddEphemeralSigningKey_SigningCredentialsUseSpecifiedAlgorithm(string algorithm) {
// Arrange
var services = new ServiceCollection();
services.AddOptions();
var builder = new OpenIddictBuilder(services);
// Act
services.AddOpenIddict<OpenIddictDbContext, Guid>();
builder.AddEphemeralSigningKey(algorithm);
var provider = services.BuildServiceProvider();
var options = provider.GetRequiredService<IOptions<OpenIddictOptions>>();
var credentials = options.Value.SigningCredentials[0];
// Assert
Assert.Contains(services, service => service.ImplementationType == type);
Assert.Equal(algorithm, credentials.Algorithm);
}
[Theory]
[InlineData(typeof(OpenIddictApplicationManager<OpenIddictApplication>))]
[InlineData(typeof(OpenIddictAuthorizationManager<OpenIddictAuthorization>))]
[InlineData(typeof(OpenIddictScopeManager<OpenIddictScope>))]
[InlineData(typeof(OpenIddictTokenManager<OpenIddictToken>))]
[InlineData(typeof(OpenIddictApplicationStore<OpenIddictApplication, OpenIddictToken, OpenIddictDbContext, string>))]
[InlineData(typeof(OpenIddictAuthorizationStore<OpenIddictAuthorization, OpenIddictToken, OpenIddictDbContext, string>))]
[InlineData(typeof(OpenIddictScopeStore<OpenIddictScope, OpenIddictDbContext, string>))]
[InlineData(typeof(OpenIddictTokenStore<OpenIddictToken, OpenIddictAuthorization, OpenIddictDbContext, string>))]
public void AddOpenIddict_KeyTypeDefaultsToString(Type type) {
[InlineData(SecurityAlgorithms.HmacSha256Signature)]
[InlineData(SecurityAlgorithms.RsaSha256Signature)]
#if SUPPORTS_ECDSA
[InlineData(SecurityAlgorithms.EcdsaSha256Signature)]
[InlineData(SecurityAlgorithms.EcdsaSha384Signature)]
[InlineData(SecurityAlgorithms.EcdsaSha512Signature)]
#endif
public void AddSigningKey_SigningKeyIsCorrectlyAdded(string algorithm) {
// Arrange
var services = new ServiceCollection();
services.AddOptions();
var builder = new OpenIddictBuilder(services);
var factory = Mock.Of<CryptoProviderFactory>(mock =>
mock.IsSupportedAlgorithm(algorithm, It.IsAny<SecurityKey>()));
var key = Mock.Of<SecurityKey>(mock => mock.CryptoProviderFactory == factory);
// Act
builder.AddSigningKey(key);
var provider = services.BuildServiceProvider();
var options = provider.GetRequiredService<IOptions<OpenIddictOptions>>();
// Assert
Assert.Same(key, options.Value.SigningCredentials[0].Key);
}
[Fact]
public void AddSigningCertificate_SigningKeyIsCorrectlyAdded() {
// Arrange
var services = new ServiceCollection();
services.AddOptions();
var builder = new OpenIddictBuilder(services);
// Act
builder.AddSigningCertificate(
assembly: typeof(OpenIddictBuilderTests).GetTypeInfo().Assembly,
resource: "OpenIddict.Tests.Certificate.pfx",
password: "OpenIddict");
var provider = services.BuildServiceProvider();
var options = provider.GetRequiredService<IOptions<OpenIddictOptions>>();
// Assert
Assert.IsType(typeof(X509SecurityKey), options.Value.SigningCredentials[0].Key);
}
[Fact]
public void AllowAuthorizationCodeFlow_CodeFlowIsAddedToGrantTypes() {
// Arrange
var services = new ServiceCollection();
services.AddOptions();
var builder = new OpenIddictBuilder(services);
// Act
builder.AllowAuthorizationCodeFlow();
var provider = services.BuildServiceProvider();
var options = provider.GetRequiredService<IOptions<OpenIddictOptions>>();
// Assert
Assert.Contains(OpenIdConnectConstants.GrantTypes.AuthorizationCode, options.Value.GrantTypes);
}
[Fact]
public void AllowClientCredentialsFlow_ClientCredentialsFlowIsAddedToGrantTypes() {
// Arrange
var services = new ServiceCollection();
services.AddOptions();
var builder = new OpenIddictBuilder(services);
// Act
builder.AllowClientCredentialsFlow();
var provider = services.BuildServiceProvider();
var options = provider.GetRequiredService<IOptions<OpenIddictOptions>>();
// Assert
Assert.Contains(OpenIdConnectConstants.GrantTypes.ClientCredentials, options.Value.GrantTypes);
}
[Fact]
public void AllowCustomFlow_CustomFlowIsAddedToGrantTypes() {
// Arrange
var services = new ServiceCollection();
services.AddOptions();
var builder = new OpenIddictBuilder(services);
// Act
builder.AllowCustomFlow("urn:ietf:params:oauth:grant-type:custom_grant");
var provider = services.BuildServiceProvider();
var options = provider.GetRequiredService<IOptions<OpenIddictOptions>>();
// Assert
Assert.Contains("urn:ietf:params:oauth:grant-type:custom_grant", options.Value.GrantTypes);
}
[Fact]
public void AllowImplicitFlow_ImplicitFlowIsAddedToGrantTypes() {
// Arrange
var services = new ServiceCollection();
services.AddOptions();
var builder = new OpenIddictBuilder(services);
// Act
builder.AllowImplicitFlow();
var provider = services.BuildServiceProvider();
var options = provider.GetRequiredService<IOptions<OpenIddictOptions>>();
// Assert
Assert.Contains(OpenIdConnectConstants.GrantTypes.Implicit, options.Value.GrantTypes);
}
[Fact]
public void AllowPasswordFlow_PasswordFlowIsAddedToGrantTypes() {
// Arrange
var services = new ServiceCollection();
services.AddOptions();
var builder = new OpenIddictBuilder(services);
// Act
builder.AllowPasswordFlow();
var provider = services.BuildServiceProvider();
var options = provider.GetRequiredService<IOptions<OpenIddictOptions>>();
// Assert
Assert.Contains(OpenIdConnectConstants.GrantTypes.Password, options.Value.GrantTypes);
}
[Fact]
public void AllowRefreshTokenFlow_RefreshTokenFlowIsAddedToGrantTypes() {
// Arrange
var services = new ServiceCollection();
services.AddOptions();
var builder = new OpenIddictBuilder(services);
// Act
builder.AllowRefreshTokenFlow();
var provider = services.BuildServiceProvider();
var options = provider.GetRequiredService<IOptions<OpenIddictOptions>>();
// Assert
Assert.Contains(OpenIdConnectConstants.GrantTypes.RefreshToken, options.Value.GrantTypes);
}
[Fact]
public void DisableConfigurationEndpoint_ConfigurationEndpointIsDisabled() {
// Arrange
var services = new ServiceCollection();
services.AddOptions();
var builder = new OpenIddictBuilder(services);
// Act
services.AddOpenIddict<OpenIddictDbContext>();
builder.DisableConfigurationEndpoint();
var provider = services.BuildServiceProvider();
var options = provider.GetRequiredService<IOptions<OpenIddictOptions>>();
// Assert
Assert.Contains(services, service => service.ImplementationType == type);
Assert.Equal(PathString.Empty, options.Value.ConfigurationEndpointPath);
}
[Fact]
public void DisableCryptographyEndpoint_CryptographyEndpointIsDisabled() {
// Arrange
var services = new ServiceCollection();
services.AddOptions();
var builder = new OpenIddictBuilder(services);
// Act
builder.DisableCryptographyEndpoint();
var provider = services.BuildServiceProvider();
var options = provider.GetRequiredService<IOptions<OpenIddictOptions>>();
// Assert
Assert.Equal(PathString.Empty, options.Value.CryptographyEndpointPath);
}
[Fact]
public void EnableAuthorizationEndpoint_AuthorizationEndpointIsEnabled() {
// Arrange
var services = new ServiceCollection();
services.AddOptions();
var builder = new OpenIddictBuilder(services);
// Act
builder.EnableAuthorizationEndpoint("/endpoint-path");
var provider = services.BuildServiceProvider();
var options = provider.GetRequiredService<IOptions<OpenIddictOptions>>();
// Assert
Assert.Equal("/endpoint-path", options.Value.AuthorizationEndpointPath);
}
[Fact]
public void EnableIntrospectionEndpoint_IntrospectionEndpointIsEnabled() {
// Arrange
var services = new ServiceCollection();
services.AddOptions();
var builder = new OpenIddictBuilder(services);
// Act
builder.EnableIntrospectionEndpoint("/endpoint-path");
var provider = services.BuildServiceProvider();
var options = provider.GetRequiredService<IOptions<OpenIddictOptions>>();
// Assert
Assert.Equal("/endpoint-path", options.Value.IntrospectionEndpointPath);
}
[Fact]
public void EnableLogoutEndpoint_LogoutEndpointIsEnabled() {
// Arrange
var services = new ServiceCollection();
services.AddOptions();
var builder = new OpenIddictBuilder(services);
// Act
builder.EnableLogoutEndpoint("/endpoint-path");
var provider = services.BuildServiceProvider();
var options = provider.GetRequiredService<IOptions<OpenIddictOptions>>();
// Assert
Assert.Equal("/endpoint-path", options.Value.LogoutEndpointPath);
}
[Fact]
public void EnableRequestCaching_RequestCachingIsEnabled() {
// Arrange
var services = new ServiceCollection();
services.AddOptions();
var builder = new OpenIddictBuilder(services);
// Act
builder.EnableRequestCaching();
var provider = services.BuildServiceProvider();
var options = provider.GetRequiredService<IOptions<OpenIddictOptions>>();
// Assert
Assert.True(options.Value.EnableRequestCaching);
}
[Fact]
public void EnableRevocationEndpoint_RevocationEndpointIsEnabled() {
// Arrange
var services = new ServiceCollection();
services.AddOptions();
var builder = new OpenIddictBuilder(services);
// Act
builder.EnableRevocationEndpoint("/endpoint-path");
var provider = services.BuildServiceProvider();
var options = provider.GetRequiredService<IOptions<OpenIddictOptions>>();
// Assert
Assert.Equal("/endpoint-path", options.Value.RevocationEndpointPath);
}
[Fact]
public void EnableTokenEndpoint_TokenEndpointIsEnabled() {
// Arrange
var services = new ServiceCollection();
services.AddOptions();
var builder = new OpenIddictBuilder(services);
// Act
builder.EnableTokenEndpoint("/endpoint-path");
var provider = services.BuildServiceProvider();
var options = provider.GetRequiredService<IOptions<OpenIddictOptions>>();
// Assert
Assert.Equal("/endpoint-path", options.Value.TokenEndpointPath);
}
[Fact]
public void EnableUserinfoEndpoint_UserinfoEndpointIsEnabled() {
// Arrange
var services = new ServiceCollection();
services.AddOptions();
var builder = new OpenIddictBuilder(services);
// Act
builder.EnableUserinfoEndpoint("/endpoint-path");
var provider = services.BuildServiceProvider();
var options = provider.GetRequiredService<IOptions<OpenIddictOptions>>();
// Assert
Assert.Equal("/endpoint-path", options.Value.UserinfoEndpointPath);
}
[Fact]
public void RequireClientIdentification_ClientIdentificationIsEnforced() {
// Arrange
var services = new ServiceCollection();
services.AddOptions();
var builder = new OpenIddictBuilder(services);
// Act
builder.RequireClientIdentification();
var provider = services.BuildServiceProvider();
var options = provider.GetRequiredService<IOptions<OpenIddictOptions>>();
// Assert
Assert.True(options.Value.RequireClientIdentification);
}
[Fact]
public void SetAccessTokenLifetime_DefaultAccessTokenLifetimeIsReplaced() {
// Arrange
var services = new ServiceCollection();
services.AddOptions();
var builder = new OpenIddictBuilder(services);
// Act
builder.SetAccessTokenLifetime(TimeSpan.FromMinutes(42));
var provider = services.BuildServiceProvider();
var options = provider.GetRequiredService<IOptions<OpenIddictOptions>>();
// Assert
Assert.Equal(TimeSpan.FromMinutes(42), options.Value.AccessTokenLifetime);
}
[Fact]
public void SetAuthorizationCodeLifetime_DefaultAuthorizationCodeLifetimeIsReplaced() {
// Arrange
var services = new ServiceCollection();
services.AddOptions();
var builder = new OpenIddictBuilder(services);
// Act
builder.SetAuthorizationCodeLifetime(TimeSpan.FromMinutes(42));
var provider = services.BuildServiceProvider();
var options = provider.GetRequiredService<IOptions<OpenIddictOptions>>();
// Assert
Assert.Equal(TimeSpan.FromMinutes(42), options.Value.AuthorizationCodeLifetime);
}
[Fact]
public void SetIdentityTokenLifetime_DefaultIdentityTokenLifetimeIsReplaced() {
// Arrange
var services = new ServiceCollection();
services.AddOptions();
var builder = new OpenIddictBuilder(services);
// Act
builder.SetIdentityTokenLifetime(TimeSpan.FromMinutes(42));
var provider = services.BuildServiceProvider();
var options = provider.GetRequiredService<IOptions<OpenIddictOptions>>();
// Assert
Assert.Equal(TimeSpan.FromMinutes(42), options.Value.IdentityTokenLifetime);
}
[Fact]
public void SetRefreshTokenLifetime_DefaultRefreshTokenLifetimeIsReplaced() {
// Arrange
var services = new ServiceCollection();
services.AddOptions();
var builder = new OpenIddictBuilder(services);
// Act
builder.SetRefreshTokenLifetime(TimeSpan.FromMinutes(42));
var provider = services.BuildServiceProvider();
var options = provider.GetRequiredService<IOptions<OpenIddictOptions>>();
// Assert
Assert.Equal(TimeSpan.FromMinutes(42), options.Value.RefreshTokenLifetime);
}
[Fact]
public void UseDataProtectionProvider_DefaultProviderIsReplaced() {
// Arrange
var services = new ServiceCollection();
services.AddOptions();
var builder = new OpenIddictBuilder(services);
// Act
builder.UseDataProtectionProvider(new EphemeralDataProtectionProvider());
var provider = services.BuildServiceProvider();
var options = provider.GetRequiredService<IOptions<OpenIddictOptions>>();
// Assert
Assert.IsType(typeof(EphemeralDataProtectionProvider), options.Value.DataProtectionProvider);
}
[Fact]
public void UseJsonWebTokens_AccessTokenHandlerIsCorrectlySet() {
// Arrange
var services = new ServiceCollection();
services.AddOptions();
var builder = new OpenIddictBuilder(services);
// Act
builder.UseJsonWebTokens();
var provider = services.BuildServiceProvider();
var options = provider.GetRequiredService<IOptions<OpenIddictOptions>>();
// Assert
Assert.IsType(typeof(JwtSecurityTokenHandler), options.Value.AccessTokenHandler);
}
[Theory]
[InlineData(true)]
[InlineData(false)]
public void IsAuthorizationCodeFlowEnabled_ReturnsAppropriateResult(bool enabled) {
// Arrange
var options = new OpenIddictOptions();
if (enabled) {
options.GrantTypes.Add(OpenIdConnectConstants.GrantTypes.AuthorizationCode);
}
// Act and assert
Assert.Equal(enabled, options.IsAuthorizationCodeFlowEnabled());
}
[Theory]
[InlineData(true)]
[InlineData(false)]
public void IsClientCredentialsFlowEnabled_ReturnsAppropriateResult(bool enabled) {
// Arrange
var options = new OpenIddictOptions();
if (enabled) {
options.GrantTypes.Add(OpenIdConnectConstants.GrantTypes.ClientCredentials);
}
// Act and assert
Assert.Equal(enabled, options.IsClientCredentialsFlowEnabled());
}
[Theory]
[InlineData(true)]
[InlineData(false)]
public void IsImplicitFlowEnabled_ReturnsAppropriateResult(bool enabled) {
// Arrange
var options = new OpenIddictOptions();
if (enabled) {
options.GrantTypes.Add(OpenIdConnectConstants.GrantTypes.Implicit);
}
// Act and assert
Assert.Equal(enabled, options.IsImplicitFlowEnabled());
}
[Theory]
[InlineData(true)]
[InlineData(false)]
public void IsPasswordFlowEnabled_ReturnsAppropriateResult(bool enabled) {
// Arrange
var options = new OpenIddictOptions();
if (enabled) {
options.GrantTypes.Add(OpenIdConnectConstants.GrantTypes.Password);
}
// Act and assert
Assert.Equal(enabled, options.IsPasswordFlowEnabled());
}
[Theory]
[InlineData(true)]
[InlineData(false)]
public void IsRefreshTokenFlowEnabled_ReturnsAppropriateResult(bool enabled) {
// Arrange
var options = new OpenIddictOptions();
if (enabled) {
options.GrantTypes.Add(OpenIdConnectConstants.GrantTypes.RefreshToken);
}
// Act and assert
Assert.Equal(enabled, options.IsRefreshTokenFlowEnabled());
}
}
}

68
test/OpenIddict.Core.Tests/Infrastructure/OpenIddictProviderTests.Authentication.cs → test/OpenIddict.Tests/OpenIddictProviderTests.Authentication.cs

@ -1,16 +1,20 @@
using System.IO;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using AspNet.Security.OpenIdConnect.Client;
using AspNet.Security.OpenIdConnect.Primitives;
using Microsoft.AspNetCore.Builder;
using Microsoft.Extensions.Caching.Distributed;
using Microsoft.Extensions.DependencyInjection;
using Moq;
using Newtonsoft.Json;
using Newtonsoft.Json.Bson;
using OpenIddict.Core;
using OpenIddict.Models;
using Xunit;
namespace OpenIddict.Core.Tests.Infrastructure {
namespace OpenIddict.Tests {
public partial class OpenIddictProviderTests {
[Fact]
public async Task ExtractAuthorizationRequest_UnsupportedRequestParameterIsRejected() {
@ -278,7 +282,7 @@ namespace OpenIddict.Core.Tests.Infrastructure {
public async Task ValidateAuthorizationRequest_RequestIsRejectedWhenClientCannotBeFound() {
// Arrange
var manager = CreateApplicationManager(instance => {
instance.Setup(mock => mock.FindByClientIdAsync("Fabrikam"))
instance.Setup(mock => mock.FindByClientIdAsync("Fabrikam", It.IsAny<CancellationToken>()))
.ReturnsAsync(null);
});
@ -299,19 +303,19 @@ namespace OpenIddict.Core.Tests.Infrastructure {
Assert.Equal(OpenIdConnectConstants.Errors.InvalidClient, response.Error);
Assert.Equal("Application not found in the database: ensure that your client_id is correct.", response.ErrorDescription);
Mock.Get(manager).Verify(mock => mock.FindByClientIdAsync("Fabrikam"), Times.Once());
Mock.Get(manager).Verify(mock => mock.FindByClientIdAsync("Fabrikam", It.IsAny<CancellationToken>()), Times.Once());
}
[Fact]
public async Task ValidateAuthorizationRequest_RequestIsRejectedWhenRedirectUriIsInvalid() {
// Arrange
var application = Mock.Of<object>();
var application = new OpenIddictApplication();
var manager = CreateApplicationManager(instance => {
instance.Setup(mock => mock.FindByClientIdAsync("Fabrikam"))
instance.Setup(mock => mock.FindByClientIdAsync("Fabrikam", It.IsAny<CancellationToken>()))
.ReturnsAsync(application);
instance.Setup(mock => mock.ValidateRedirectUriAsync(application, "http://www.fabrikam.com/path"))
instance.Setup(mock => mock.ValidateRedirectUriAsync(application, "http://www.fabrikam.com/path", It.IsAny<CancellationToken>()))
.ReturnsAsync(false);
});
@ -332,8 +336,8 @@ namespace OpenIddict.Core.Tests.Infrastructure {
Assert.Equal(OpenIdConnectConstants.Errors.InvalidClient, response.Error);
Assert.Equal("Invalid redirect_uri.", response.ErrorDescription);
Mock.Get(manager).Verify(mock => mock.FindByClientIdAsync("Fabrikam"), Times.Once());
Mock.Get(manager).Verify(mock => mock.ValidateRedirectUriAsync(application, "http://www.fabrikam.com/path"), Times.Once());
Mock.Get(manager).Verify(mock => mock.FindByClientIdAsync("Fabrikam", It.IsAny<CancellationToken>()), Times.Once());
Mock.Get(manager).Verify(mock => mock.ValidateRedirectUriAsync(application, "http://www.fabrikam.com/path", It.IsAny<CancellationToken>()), Times.Once());
}
[Theory]
@ -343,16 +347,16 @@ namespace OpenIddict.Core.Tests.Infrastructure {
[InlineData("token")]
public async Task ValidateAuthorizationRequest_ImplicitOrHybridRequestIsRejectedWhenClientIsConfidential(string type) {
// Arrange
var application = Mock.Of<object>();
var application = new OpenIddictApplication();
var manager = CreateApplicationManager(instance => {
instance.Setup(mock => mock.FindByClientIdAsync("Fabrikam"))
instance.Setup(mock => mock.FindByClientIdAsync("Fabrikam", It.IsAny<CancellationToken>()))
.ReturnsAsync(application);
instance.Setup(mock => mock.ValidateRedirectUriAsync(application, "http://www.fabrikam.com/path"))
instance.Setup(mock => mock.ValidateRedirectUriAsync(application, "http://www.fabrikam.com/path", It.IsAny<CancellationToken>()))
.ReturnsAsync(true);
instance.Setup(mock => mock.GetClientTypeAsync(application))
instance.Setup(mock => mock.GetClientTypeAsync(application, It.IsAny<CancellationToken>()))
.ReturnsAsync(OpenIddictConstants.ClientTypes.Confidential);
});
@ -376,9 +380,9 @@ namespace OpenIddict.Core.Tests.Infrastructure {
Assert.Equal("Confidential clients are not allowed to retrieve " +
"an access token from the authorization endpoint.", response.ErrorDescription);
Mock.Get(manager).Verify(mock => mock.FindByClientIdAsync("Fabrikam"), Times.Once());
Mock.Get(manager).Verify(mock => mock.ValidateRedirectUriAsync(application, "http://www.fabrikam.com/path"), Times.Once());
Mock.Get(manager).Verify(mock => mock.GetClientTypeAsync(application), Times.Once());
Mock.Get(manager).Verify(mock => mock.FindByClientIdAsync("Fabrikam", 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());
}
[Fact]
@ -388,15 +392,15 @@ namespace OpenIddict.Core.Tests.Infrastructure {
var server = CreateAuthorizationServer(builder => {
builder.Services.AddSingleton(CreateApplicationManager(instance => {
var application = Mock.Of<object>();
var application = new OpenIddictApplication();
instance.Setup(mock => mock.FindByClientIdAsync("Fabrikam"))
instance.Setup(mock => mock.FindByClientIdAsync("Fabrikam", It.IsAny<CancellationToken>()))
.ReturnsAsync(application);
instance.Setup(mock => mock.ValidateRedirectUriAsync(application, "http://www.fabrikam.com/path"))
instance.Setup(mock => mock.ValidateRedirectUriAsync(application, "http://www.fabrikam.com/path", It.IsAny<CancellationToken>()))
.ReturnsAsync(true);
instance.Setup(mock => mock.GetClientTypeAsync(application))
instance.Setup(mock => mock.GetClientTypeAsync(application, It.IsAny<CancellationToken>()))
.ReturnsAsync(OpenIddictConstants.ClientTypes.Public);
}));
@ -438,20 +442,20 @@ namespace OpenIddict.Core.Tests.Infrastructure {
// Arrange
var server = CreateAuthorizationServer(builder => {
builder.Services.AddSingleton(CreateApplicationManager(instance => {
var application = Mock.Of<object>();
var application = new OpenIddictApplication();
instance.Setup(mock => mock.FindByClientIdAsync("Fabrikam"))
instance.Setup(mock => mock.FindByClientIdAsync("Fabrikam", It.IsAny<CancellationToken>()))
.ReturnsAsync(application);
instance.Setup(mock => mock.ValidateRedirectUriAsync(application, "http://www.fabrikam.com/path"))
instance.Setup(mock => mock.ValidateRedirectUriAsync(application, "http://www.fabrikam.com/path", It.IsAny<CancellationToken>()))
.ReturnsAsync(true);
instance.Setup(mock => mock.GetClientTypeAsync(application))
instance.Setup(mock => mock.GetClientTypeAsync(application, It.IsAny<CancellationToken>()))
.ReturnsAsync(OpenIddictConstants.ClientTypes.Public);
}));
builder.Services.AddSingleton(CreateTokenManager(instance => {
instance.Setup(mock => mock.CreateAsync(OpenIdConnectConstants.TokenTypeHints.AuthorizationCode))
instance.Setup(mock => mock.CreateAsync(OpenIdConnectConstants.TokenTypeHints.AuthorizationCode, It.IsAny<CancellationToken>()))
.ReturnsAsync("3E228451-1555-46F7-A471-951EFBA23A56");
}));
});
@ -498,15 +502,15 @@ namespace OpenIddict.Core.Tests.Infrastructure {
var server = CreateAuthorizationServer(builder => {
builder.Services.AddSingleton(CreateApplicationManager(instance => {
var application = Mock.Of<object>();
var application = new OpenIddictApplication();
instance.Setup(mock => mock.FindByClientIdAsync("Fabrikam"))
instance.Setup(mock => mock.FindByClientIdAsync("Fabrikam", It.IsAny<CancellationToken>()))
.ReturnsAsync(application);
instance.Setup(mock => mock.ValidateRedirectUriAsync(application, "http://www.fabrikam.com/path"))
instance.Setup(mock => mock.ValidateRedirectUriAsync(application, "http://www.fabrikam.com/path", It.IsAny<CancellationToken>()))
.ReturnsAsync(true);
instance.Setup(mock => mock.GetClientTypeAsync(application))
instance.Setup(mock => mock.GetClientTypeAsync(application, It.IsAny<CancellationToken>()))
.ReturnsAsync(OpenIddictConstants.ClientTypes.Public);
}));
@ -535,15 +539,15 @@ namespace OpenIddict.Core.Tests.Infrastructure {
// Arrange
var server = CreateAuthorizationServer(builder => {
builder.Services.AddSingleton(CreateApplicationManager(instance => {
var application = Mock.Of<object>();
var application = new OpenIddictApplication();
instance.Setup(mock => mock.FindByClientIdAsync("Fabrikam"))
instance.Setup(mock => mock.FindByClientIdAsync("Fabrikam", It.IsAny<CancellationToken>()))
.ReturnsAsync(application);
instance.Setup(mock => mock.ValidateRedirectUriAsync(application, "http://www.fabrikam.com/path"))
instance.Setup(mock => mock.ValidateRedirectUriAsync(application, "http://www.fabrikam.com/path", It.IsAny<CancellationToken>()))
.ReturnsAsync(true);
instance.Setup(mock => mock.GetClientTypeAsync(application))
instance.Setup(mock => mock.GetClientTypeAsync(application, It.IsAny<CancellationToken>()))
.ReturnsAsync(OpenIddictConstants.ClientTypes.Public);
}));

4
test/OpenIddict.Core.Tests/Infrastructure/OpenIddictProviderTests.Discovery.cs → test/OpenIddict.Tests/OpenIddictProviderTests.Discovery.cs

@ -2,9 +2,11 @@
using System.Threading.Tasks;
using AspNet.Security.OpenIdConnect.Client;
using AspNet.Security.OpenIdConnect.Primitives;
using Microsoft.AspNetCore.Builder;
using OpenIddict.Core;
using Xunit;
namespace OpenIddict.Core.Tests.Infrastructure {
namespace OpenIddict.Tests {
public partial class OpenIddictProviderTests {
[Fact]
public async Task HandleConfigurationRequest_PlainCodeChallengeMethodIsNotReturned() {

114
test/OpenIddict.Core.Tests/Infrastructure/OpenIddictProviderTests.Exchange.cs → test/OpenIddict.Tests/OpenIddictProviderTests.Exchange.cs

@ -1,16 +1,20 @@
using System.Security.Claims;
using System.Threading;
using System.Threading.Tasks;
using AspNet.Security.OpenIdConnect.Client;
using AspNet.Security.OpenIdConnect.Extensions;
using AspNet.Security.OpenIdConnect.Primitives;
using AspNet.Security.OpenIdConnect.Server;
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Http.Authentication;
using Microsoft.Extensions.DependencyInjection;
using Moq;
using OpenIddict.Core;
using OpenIddict.Models;
using Xunit;
namespace OpenIddict.Core.Tests.Infrastructure {
namespace OpenIddict.Tests {
public partial class OpenIddictProviderTests {
[Theory]
[InlineData(OpenIdConnectConstants.GrantTypes.AuthorizationCode)]
@ -124,7 +128,7 @@ namespace OpenIddict.Core.Tests.Infrastructure {
public async Task ValidateTokenRequest_RequestIsRejectedWhenClientCannotBeFound() {
// Arrange
var manager = CreateApplicationManager(instance => {
instance.Setup(mock => mock.FindByClientIdAsync("Fabrikam"))
instance.Setup(mock => mock.FindByClientIdAsync("Fabrikam", It.IsAny<CancellationToken>()))
.ReturnsAsync(null);
});
@ -146,19 +150,19 @@ namespace OpenIddict.Core.Tests.Infrastructure {
Assert.Equal(OpenIdConnectConstants.Errors.InvalidClient, response.Error);
Assert.Equal("Application not found in the database: ensure that your client_id is correct.", response.ErrorDescription);
Mock.Get(manager).Verify(mock => mock.FindByClientIdAsync("Fabrikam"), Times.Once());
Mock.Get(manager).Verify(mock => mock.FindByClientIdAsync("Fabrikam", It.IsAny<CancellationToken>()), Times.Once());
}
[Fact]
public async Task ValidateTokenRequest_ClientCredentialsRequestFromPublicClientIsRejected() {
// Arrange
var application = Mock.Of<object>();
var application = new OpenIddictApplication();
var manager = CreateApplicationManager(instance => {
instance.Setup(mock => mock.FindByClientIdAsync("Fabrikam"))
instance.Setup(mock => mock.FindByClientIdAsync("Fabrikam", It.IsAny<CancellationToken>()))
.ReturnsAsync(application);
instance.Setup(mock => mock.GetClientTypeAsync(application))
instance.Setup(mock => mock.GetClientTypeAsync(application, It.IsAny<CancellationToken>()))
.ReturnsAsync(OpenIddictConstants.ClientTypes.Public);
});
@ -179,20 +183,20 @@ namespace OpenIddict.Core.Tests.Infrastructure {
Assert.Equal(OpenIdConnectConstants.Errors.UnauthorizedClient, response.Error);
Assert.Equal("Public clients are not allowed to use the client credentials grant.", response.ErrorDescription);
Mock.Get(manager).Verify(mock => mock.FindByClientIdAsync("Fabrikam"), Times.Once());
Mock.Get(manager).Verify(mock => mock.GetClientTypeAsync(application), Times.Once());
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());
}
[Fact]
public async Task ValidateTokenRequest_ClientSecretCannotBeUsedByPublicClients() {
// Arrange
var application = Mock.Of<object>();
var application = new OpenIddictApplication();
var manager = CreateApplicationManager(instance => {
instance.Setup(mock => mock.FindByClientIdAsync("Fabrikam"))
instance.Setup(mock => mock.FindByClientIdAsync("Fabrikam", It.IsAny<CancellationToken>()))
.ReturnsAsync(application);
instance.Setup(mock => mock.GetClientTypeAsync(application))
instance.Setup(mock => mock.GetClientTypeAsync(application, It.IsAny<CancellationToken>()))
.ReturnsAsync(OpenIddictConstants.ClientTypes.Public);
});
@ -215,20 +219,20 @@ namespace OpenIddict.Core.Tests.Infrastructure {
Assert.Equal(OpenIdConnectConstants.Errors.InvalidRequest, response.Error);
Assert.Equal("Public clients are not allowed to send a client_secret.", response.ErrorDescription);
Mock.Get(manager).Verify(mock => mock.FindByClientIdAsync("Fabrikam"), Times.Once());
Mock.Get(manager).Verify(mock => mock.GetClientTypeAsync(application), Times.Once());
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());
}
[Fact]
public async Task ValidateTokenRequest_ClientSecretIsRequiredForConfidentialClients() {
// Arrange
var application = Mock.Of<object>();
var application = new OpenIddictApplication();
var manager = CreateApplicationManager(instance => {
instance.Setup(mock => mock.FindByClientIdAsync("Fabrikam"))
instance.Setup(mock => mock.FindByClientIdAsync("Fabrikam", It.IsAny<CancellationToken>()))
.ReturnsAsync(application);
instance.Setup(mock => mock.GetClientTypeAsync(application))
instance.Setup(mock => mock.GetClientTypeAsync(application, It.IsAny<CancellationToken>()))
.ReturnsAsync(OpenIddictConstants.ClientTypes.Confidential);
});
@ -251,23 +255,23 @@ namespace OpenIddict.Core.Tests.Infrastructure {
Assert.Equal(OpenIdConnectConstants.Errors.InvalidClient, response.Error);
Assert.Equal("Missing credentials: ensure that you specified a client_secret.", response.ErrorDescription);
Mock.Get(manager).Verify(mock => mock.FindByClientIdAsync("Fabrikam"), Times.Once());
Mock.Get(manager).Verify(mock => mock.GetClientTypeAsync(application), Times.Once());
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());
}
[Fact]
public async Task ValidateTokenRequest_RequestIsRejectedWhenClientCredentialsAreInvalid() {
// Arrange
var application = Mock.Of<object>();
var application = new OpenIddictApplication();
var manager = CreateApplicationManager(instance => {
instance.Setup(mock => mock.FindByClientIdAsync("Fabrikam"))
instance.Setup(mock => mock.FindByClientIdAsync("Fabrikam", It.IsAny<CancellationToken>()))
.ReturnsAsync(application);
instance.Setup(mock => mock.GetClientTypeAsync(application))
instance.Setup(mock => mock.GetClientTypeAsync(application, It.IsAny<CancellationToken>()))
.ReturnsAsync(OpenIddictConstants.ClientTypes.Confidential);
instance.Setup(mock => mock.ValidateSecretAsync(application, "7Fjfp0ZBr1KtDRbnfVdmIw"))
instance.Setup(mock => mock.ValidateSecretAsync(application, "7Fjfp0ZBr1KtDRbnfVdmIw", It.IsAny<CancellationToken>()))
.ReturnsAsync(false);
});
@ -290,9 +294,9 @@ namespace OpenIddict.Core.Tests.Infrastructure {
Assert.Equal(OpenIdConnectConstants.Errors.InvalidClient, response.Error);
Assert.Equal("Invalid credentials: ensure that you specified a correct client_secret.", response.ErrorDescription);
Mock.Get(manager).Verify(mock => mock.FindByClientIdAsync("Fabrikam"), Times.Once());
Mock.Get(manager).Verify(mock => mock.GetClientTypeAsync(application), Times.Once());
Mock.Get(manager).Verify(mock => mock.ValidateSecretAsync(application, "7Fjfp0ZBr1KtDRbnfVdmIw"), Times.Once());
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());
}
[Fact]
@ -313,18 +317,18 @@ namespace OpenIddict.Core.Tests.Infrastructure {
.Returns(ticket);
var manager = CreateTokenManager(instance => {
instance.Setup(mock => mock.FindByIdAsync("3E228451-1555-46F7-A471-951EFBA23A56"))
instance.Setup(mock => mock.FindByIdAsync("3E228451-1555-46F7-A471-951EFBA23A56", It.IsAny<CancellationToken>()))
.ReturnsAsync(null);
});
var server = CreateAuthorizationServer(builder => {
builder.Services.AddSingleton(CreateApplicationManager(instance => {
var application = Mock.Of<object>();
var application = new OpenIddictApplication();
instance.Setup(mock => mock.FindByClientIdAsync("Fabrikam"))
instance.Setup(mock => mock.FindByClientIdAsync("Fabrikam", It.IsAny<CancellationToken>()))
.ReturnsAsync(application);
instance.Setup(mock => mock.GetClientTypeAsync(application))
instance.Setup(mock => mock.GetClientTypeAsync(application, It.IsAny<CancellationToken>()))
.ReturnsAsync(OpenIddictConstants.ClientTypes.Public);
}));
@ -346,7 +350,7 @@ namespace OpenIddict.Core.Tests.Infrastructure {
Assert.Equal(OpenIdConnectConstants.Errors.InvalidGrant, response.Error);
Assert.Equal("The authorization code is no longer valid.", response.ErrorDescription);
Mock.Get(manager).Verify(mock => mock.FindByIdAsync("3E228451-1555-46F7-A471-951EFBA23A56"), Times.Once());
Mock.Get(manager).Verify(mock => mock.FindByIdAsync("3E228451-1555-46F7-A471-951EFBA23A56", It.IsAny<CancellationToken>()), Times.Once());
}
[Fact]
@ -366,18 +370,18 @@ namespace OpenIddict.Core.Tests.Infrastructure {
.Returns(ticket);
var manager = CreateTokenManager(instance => {
instance.Setup(mock => mock.FindByIdAsync("60FFF7EA-F98E-437B-937E-5073CC313103"))
instance.Setup(mock => mock.FindByIdAsync("60FFF7EA-F98E-437B-937E-5073CC313103", It.IsAny<CancellationToken>()))
.ReturnsAsync(null);
});
var server = CreateAuthorizationServer(builder => {
builder.Services.AddSingleton(CreateApplicationManager(instance => {
var application = Mock.Of<object>();
var application = new OpenIddictApplication();
instance.Setup(mock => mock.FindByClientIdAsync("Fabrikam"))
instance.Setup(mock => mock.FindByClientIdAsync("Fabrikam", It.IsAny<CancellationToken>()))
.ReturnsAsync(application);
instance.Setup(mock => mock.GetClientTypeAsync(application))
instance.Setup(mock => mock.GetClientTypeAsync(application, It.IsAny<CancellationToken>()))
.ReturnsAsync(OpenIddictConstants.ClientTypes.Public);
}));
@ -398,7 +402,7 @@ namespace OpenIddict.Core.Tests.Infrastructure {
Assert.Equal(OpenIdConnectConstants.Errors.InvalidGrant, response.Error);
Assert.Equal("The refresh token is no longer valid.", response.ErrorDescription);
Mock.Get(manager).Verify(mock => mock.FindByIdAsync("60FFF7EA-F98E-437B-937E-5073CC313103"), Times.Once());
Mock.Get(manager).Verify(mock => mock.FindByIdAsync("60FFF7EA-F98E-437B-937E-5073CC313103", It.IsAny<CancellationToken>()), Times.Once());
}
[Fact]
@ -421,21 +425,21 @@ namespace OpenIddict.Core.Tests.Infrastructure {
format.Setup(mock => mock.Unprotect("SplxlOBeZQQYbYS6WxSbIA"))
.Returns(ticket);
var token = Mock.Of<object>();
var token = new OpenIddictToken();
var manager = CreateTokenManager(instance => {
instance.Setup(mock => mock.FindByIdAsync("3E228451-1555-46F7-A471-951EFBA23A56"))
instance.Setup(mock => mock.FindByIdAsync("3E228451-1555-46F7-A471-951EFBA23A56", It.IsAny<CancellationToken>()))
.ReturnsAsync(token);
});
var server = CreateAuthorizationServer(builder => {
builder.Services.AddSingleton(CreateApplicationManager(instance => {
var application = Mock.Of<object>();
var application = new OpenIddictApplication();
instance.Setup(mock => mock.FindByClientIdAsync("Fabrikam"))
instance.Setup(mock => mock.FindByClientIdAsync("Fabrikam", It.IsAny<CancellationToken>()))
.ReturnsAsync(application);
instance.Setup(mock => mock.GetClientTypeAsync(application))
instance.Setup(mock => mock.GetClientTypeAsync(application, It.IsAny<CancellationToken>()))
.ReturnsAsync(OpenIddictConstants.ClientTypes.Public);
}));
@ -454,8 +458,8 @@ namespace OpenIddict.Core.Tests.Infrastructure {
});
// Assert
Mock.Get(manager).Verify(mock => mock.FindByIdAsync("3E228451-1555-46F7-A471-951EFBA23A56"), Times.Once());
Mock.Get(manager).Verify(mock => mock.RevokeAsync(token), Times.Once());
Mock.Get(manager).Verify(mock => mock.FindByIdAsync("3E228451-1555-46F7-A471-951EFBA23A56", It.IsAny<CancellationToken>()), Times.Once());
Mock.Get(manager).Verify(mock => mock.RevokeAsync(token, It.IsAny<CancellationToken>()), Times.Once());
}
[Fact]
@ -477,21 +481,21 @@ namespace OpenIddict.Core.Tests.Infrastructure {
format.Setup(mock => mock.Unprotect("8xLOxBtZp8"))
.Returns(ticket);
var token = Mock.Of<object>();
var token = new OpenIddictToken();
var manager = CreateTokenManager(instance => {
instance.Setup(mock => mock.FindByIdAsync("60FFF7EA-F98E-437B-937E-5073CC313103"))
instance.Setup(mock => mock.FindByIdAsync("60FFF7EA-F98E-437B-937E-5073CC313103", It.IsAny<CancellationToken>()))
.ReturnsAsync(token);
});
var server = CreateAuthorizationServer(builder => {
builder.Services.AddSingleton(CreateApplicationManager(instance => {
var application = Mock.Of<object>();
var application = new OpenIddictApplication();
instance.Setup(mock => mock.FindByClientIdAsync("Fabrikam"))
instance.Setup(mock => mock.FindByClientIdAsync("Fabrikam", It.IsAny<CancellationToken>()))
.ReturnsAsync(application);
instance.Setup(mock => mock.GetClientTypeAsync(application))
instance.Setup(mock => mock.GetClientTypeAsync(application, It.IsAny<CancellationToken>()))
.ReturnsAsync(OpenIddictConstants.ClientTypes.Public);
}));
@ -509,8 +513,8 @@ namespace OpenIddict.Core.Tests.Infrastructure {
});
// Assert
Mock.Get(manager).Verify(mock => mock.FindByIdAsync("60FFF7EA-F98E-437B-937E-5073CC313103"), Times.Once());
Mock.Get(manager).Verify(mock => mock.RevokeAsync(token), Times.Once());
Mock.Get(manager).Verify(mock => mock.FindByIdAsync("60FFF7EA-F98E-437B-937E-5073CC313103", It.IsAny<CancellationToken>()), Times.Once());
Mock.Get(manager).Verify(mock => mock.RevokeAsync(token, It.IsAny<CancellationToken>()), Times.Once());
}
[Theory]
@ -536,24 +540,24 @@ namespace OpenIddict.Core.Tests.Infrastructure {
format.Setup(mock => mock.Unprotect("8xLOxBtZp8"))
.Returns(ticket);
var token = Mock.Of<object>();
var token = new OpenIddictToken();
var manager = CreateTokenManager(instance => {
instance.Setup(mock => mock.FindByIdAsync("60FFF7EA-F98E-437B-937E-5073CC313103"))
instance.Setup(mock => mock.FindByIdAsync("60FFF7EA-F98E-437B-937E-5073CC313103", It.IsAny<CancellationToken>()))
.ReturnsAsync(token);
});
var server = CreateAuthorizationServer(builder => {
builder.Services.AddSingleton(CreateApplicationManager(instance => {
var application = Mock.Of<object>();
var application = new OpenIddictApplication();
instance.Setup(mock => mock.FindByClientIdAsync("Fabrikam"))
instance.Setup(mock => mock.FindByClientIdAsync("Fabrikam", It.IsAny<CancellationToken>()))
.ReturnsAsync(application);
instance.Setup(mock => mock.GetClientTypeAsync(application))
instance.Setup(mock => mock.GetClientTypeAsync(application, It.IsAny<CancellationToken>()))
.ReturnsAsync(OpenIddictConstants.ClientTypes.Confidential);
instance.Setup(mock => mock.ValidateSecretAsync(application, "7Fjfp0ZBr1KtDRbnfVdmIw"))
instance.Setup(mock => mock.ValidateSecretAsync(application, "7Fjfp0ZBr1KtDRbnfVdmIw", It.IsAny<CancellationToken>()))
.ReturnsAsync(true);
}));

66
test/OpenIddict.Core.Tests/Infrastructure/OpenIddictProviderTests.Introspection.cs → test/OpenIddict.Tests/OpenIddictProviderTests.Introspection.cs

@ -1,17 +1,21 @@
using System.Linq;
using System.Security.Claims;
using System.Threading;
using System.Threading.Tasks;
using AspNet.Security.OpenIdConnect.Client;
using AspNet.Security.OpenIdConnect.Extensions;
using AspNet.Security.OpenIdConnect.Primitives;
using AspNet.Security.OpenIdConnect.Server;
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Http.Authentication;
using Microsoft.Extensions.DependencyInjection;
using Moq;
using OpenIddict.Core;
using OpenIddict.Models;
using Xunit;
namespace OpenIddict.Core.Tests.Infrastructure {
namespace OpenIddict.Tests {
public partial class OpenIddictProviderTests {
[Fact]
public async Task ExtractIntrospectionRequest_GetRequestsAreRejected() {
@ -55,7 +59,7 @@ namespace OpenIddict.Core.Tests.Infrastructure {
public async Task ValidateIntrospectionRequest_RequestIsRejectedWhenClientCannotBeFound() {
// Arrange
var manager = CreateApplicationManager(instance => {
instance.Setup(mock => mock.FindByClientIdAsync("Fabrikam"))
instance.Setup(mock => mock.FindByClientIdAsync("Fabrikam", It.IsAny<CancellationToken>()))
.ReturnsAsync(null);
});
@ -76,19 +80,19 @@ namespace OpenIddict.Core.Tests.Infrastructure {
Assert.Equal(OpenIdConnectConstants.Errors.InvalidClient, response.Error);
Assert.Equal("Application not found in the database: ensure that your client_id is correct.", response.ErrorDescription);
Mock.Get(manager).Verify(mock => mock.FindByClientIdAsync("Fabrikam"), Times.Once());
Mock.Get(manager).Verify(mock => mock.FindByClientIdAsync("Fabrikam", It.IsAny<CancellationToken>()), Times.Once());
}
[Fact]
public async Task ValidateIntrospectionRequest_RequestsSentByPublicClientsAreRejected() {
// Arrange
var application = Mock.Of<object>();
var application = new OpenIddictApplication();
var manager = CreateApplicationManager(instance => {
instance.Setup(mock => mock.FindByClientIdAsync("Fabrikam"))
instance.Setup(mock => mock.FindByClientIdAsync("Fabrikam", It.IsAny<CancellationToken>()))
.ReturnsAsync(application);
instance.Setup(mock => mock.GetClientTypeAsync(application))
instance.Setup(mock => mock.GetClientTypeAsync(application, It.IsAny<CancellationToken>()))
.ReturnsAsync(OpenIddictConstants.ClientTypes.Public);
});
@ -109,23 +113,23 @@ namespace OpenIddict.Core.Tests.Infrastructure {
Assert.Equal(OpenIdConnectConstants.Errors.InvalidClient, response.Error);
Assert.Equal("Public applications are not allowed to use the introspection endpoint.", response.ErrorDescription);
Mock.Get(manager).Verify(mock => mock.FindByClientIdAsync("Fabrikam"), Times.Once());
Mock.Get(manager).Verify(mock => mock.GetClientTypeAsync(application), Times.Once());
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());
}
[Fact]
public async Task ValidateIntrospectionRequest_RequestIsRejectedWhenClientCredentialsAreInvalid() {
// Arrange
var application = Mock.Of<object>();
var application = new OpenIddictApplication();
var manager = CreateApplicationManager(instance => {
instance.Setup(mock => mock.FindByClientIdAsync("Fabrikam"))
instance.Setup(mock => mock.FindByClientIdAsync("Fabrikam", It.IsAny<CancellationToken>()))
.ReturnsAsync(application);
instance.Setup(mock => mock.GetClientTypeAsync(application))
instance.Setup(mock => mock.GetClientTypeAsync(application, It.IsAny<CancellationToken>()))
.ReturnsAsync(OpenIddictConstants.ClientTypes.Confidential);
instance.Setup(mock => mock.ValidateSecretAsync(application, "7Fjfp0ZBr1KtDRbnfVdmIw"))
instance.Setup(mock => mock.ValidateSecretAsync(application, "7Fjfp0ZBr1KtDRbnfVdmIw", It.IsAny<CancellationToken>()))
.ReturnsAsync(false);
});
@ -146,9 +150,9 @@ namespace OpenIddict.Core.Tests.Infrastructure {
Assert.Equal(OpenIdConnectConstants.Errors.InvalidClient, response.Error);
Assert.Equal("Invalid credentials: ensure that you specified a correct client_secret.", response.ErrorDescription);
Mock.Get(manager).Verify(mock => mock.FindByClientIdAsync("Fabrikam"), Times.Once());
Mock.Get(manager).Verify(mock => mock.GetClientTypeAsync(application), Times.Once());
Mock.Get(manager).Verify(mock => mock.ValidateSecretAsync(application, "7Fjfp0ZBr1KtDRbnfVdmIw"), Times.Once());
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());
}
[Fact]
@ -173,15 +177,15 @@ namespace OpenIddict.Core.Tests.Infrastructure {
var server = CreateAuthorizationServer(builder => {
builder.Services.AddSingleton(CreateApplicationManager(instance => {
var application = Mock.Of<object>();
var application = new OpenIddictApplication();
instance.Setup(mock => mock.FindByClientIdAsync("Fabrikam"))
instance.Setup(mock => mock.FindByClientIdAsync("Fabrikam", It.IsAny<CancellationToken>()))
.ReturnsAsync(application);
instance.Setup(mock => mock.GetClientTypeAsync(application))
instance.Setup(mock => mock.GetClientTypeAsync(application, It.IsAny<CancellationToken>()))
.ReturnsAsync(OpenIddictConstants.ClientTypes.Confidential);
instance.Setup(mock => mock.ValidateSecretAsync(application, "7Fjfp0ZBr1KtDRbnfVdmIw"))
instance.Setup(mock => mock.ValidateSecretAsync(application, "7Fjfp0ZBr1KtDRbnfVdmIw", It.IsAny<CancellationToken>()))
.ReturnsAsync(true);
}));
@ -222,21 +226,21 @@ namespace OpenIddict.Core.Tests.Infrastructure {
.Returns(ticket);
var manager = CreateTokenManager(instance => {
instance.Setup(mock => mock.FindByIdAsync("3E228451-1555-46F7-A471-951EFBA23A56"))
instance.Setup(mock => mock.FindByIdAsync("3E228451-1555-46F7-A471-951EFBA23A56", It.IsAny<CancellationToken>()))
.ReturnsAsync(null);
});
var server = CreateAuthorizationServer(builder => {
builder.Services.AddSingleton(CreateApplicationManager(instance => {
var application = Mock.Of<object>();
var application = new OpenIddictApplication();
instance.Setup(mock => mock.FindByClientIdAsync("Fabrikam"))
instance.Setup(mock => mock.FindByClientIdAsync("Fabrikam", It.IsAny<CancellationToken>()))
.ReturnsAsync(application);
instance.Setup(mock => mock.GetClientTypeAsync(application))
instance.Setup(mock => mock.GetClientTypeAsync(application, It.IsAny<CancellationToken>()))
.ReturnsAsync(OpenIddictConstants.ClientTypes.Confidential);
instance.Setup(mock => mock.ValidateSecretAsync(application, "7Fjfp0ZBr1KtDRbnfVdmIw"))
instance.Setup(mock => mock.ValidateSecretAsync(application, "7Fjfp0ZBr1KtDRbnfVdmIw", It.IsAny<CancellationToken>()))
.ReturnsAsync(true);
}));
@ -258,7 +262,7 @@ namespace OpenIddict.Core.Tests.Infrastructure {
Assert.Equal(1, response.GetParameters().Count());
Assert.False((bool) response[OpenIdConnectConstants.Claims.Active]);
Mock.Get(manager).Verify(mock => mock.FindByIdAsync("3E228451-1555-46F7-A471-951EFBA23A56"), Times.Once());
Mock.Get(manager).Verify(mock => mock.FindByIdAsync("3E228451-1555-46F7-A471-951EFBA23A56", It.IsAny<CancellationToken>()), Times.Once());
}
[Fact]
@ -281,21 +285,21 @@ namespace OpenIddict.Core.Tests.Infrastructure {
.Returns(ticket);
var manager = CreateTokenManager(instance => {
instance.Setup(mock => mock.FindByIdAsync("3E228451-1555-46F7-A471-951EFBA23A56"))
instance.Setup(mock => mock.FindByIdAsync("3E228451-1555-46F7-A471-951EFBA23A56", It.IsAny<CancellationToken>()))
.ReturnsAsync(null);
});
var server = CreateAuthorizationServer(builder => {
builder.Services.AddSingleton(CreateApplicationManager(instance => {
var application = Mock.Of<object>();
var application = new OpenIddictApplication();
instance.Setup(mock => mock.FindByClientIdAsync("Fabrikam"))
instance.Setup(mock => mock.FindByClientIdAsync("Fabrikam", It.IsAny<CancellationToken>()))
.ReturnsAsync(application);
instance.Setup(mock => mock.GetClientTypeAsync(application))
instance.Setup(mock => mock.GetClientTypeAsync(application, It.IsAny<CancellationToken>()))
.ReturnsAsync(OpenIddictConstants.ClientTypes.Confidential);
instance.Setup(mock => mock.ValidateSecretAsync(application, "7Fjfp0ZBr1KtDRbnfVdmIw"))
instance.Setup(mock => mock.ValidateSecretAsync(application, "7Fjfp0ZBr1KtDRbnfVdmIw", It.IsAny<CancellationToken>()))
.ReturnsAsync(true);
}));
@ -317,7 +321,7 @@ namespace OpenIddict.Core.Tests.Infrastructure {
Assert.Equal(1, response.GetParameters().Count());
Assert.False((bool) response[OpenIdConnectConstants.Claims.Active]);
Mock.Get(manager).Verify(mock => mock.FindByIdAsync("3E228451-1555-46F7-A471-951EFBA23A56"), Times.Once());
Mock.Get(manager).Verify(mock => mock.FindByIdAsync("3E228451-1555-46F7-A471-951EFBA23A56", It.IsAny<CancellationToken>()), Times.Once());
}
}
}

58
test/OpenIddict.Core.Tests/Infrastructure/OpenIddictProviderTests.Revocation.cs → test/OpenIddict.Tests/OpenIddictProviderTests.Revocation.cs

@ -2,19 +2,23 @@
using System.IdentityModel.Tokens.Jwt;
using System.Linq;
using System.Security.Claims;
using System.Threading;
using System.Threading.Tasks;
using AspNet.Security.OpenIdConnect.Client;
using AspNet.Security.OpenIdConnect.Extensions;
using AspNet.Security.OpenIdConnect.Primitives;
using AspNet.Security.OpenIdConnect.Server;
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Http.Authentication;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.IdentityModel.Tokens;
using Moq;
using OpenIddict.Core;
using OpenIddict.Models;
using Xunit;
namespace OpenIddict.Core.Tests.Infrastructure {
namespace OpenIddict.Tests {
public partial class OpenIddictProviderTests {
[Theory]
[InlineData(OpenIdConnectConstants.TokenTypeHints.AccessToken)]
@ -59,7 +63,7 @@ namespace OpenIddict.Core.Tests.Infrastructure {
public async Task ValidateRevocationRequest_RequestIsRejectedWhenClientCannotBeFound() {
// Arrange
var manager = CreateApplicationManager(instance => {
instance.Setup(mock => mock.FindByClientIdAsync("Fabrikam"))
instance.Setup(mock => mock.FindByClientIdAsync("Fabrikam", It.IsAny<CancellationToken>()))
.ReturnsAsync(null);
});
@ -80,19 +84,19 @@ namespace OpenIddict.Core.Tests.Infrastructure {
Assert.Equal(OpenIdConnectConstants.Errors.InvalidClient, response.Error);
Assert.Equal("Application not found in the database: ensure that your client_id is correct.", response.ErrorDescription);
Mock.Get(manager).Verify(mock => mock.FindByClientIdAsync("Fabrikam"), Times.Once());
Mock.Get(manager).Verify(mock => mock.FindByClientIdAsync("Fabrikam", It.IsAny<CancellationToken>()), Times.Once());
}
[Fact]
public async Task ValidateRevocationRequest_ClientSecretCannotBeUsedByPublicClients() {
// Arrange
var application = Mock.Of<object>();
var application = new OpenIddictApplication();
var manager = CreateApplicationManager(instance => {
instance.Setup(mock => mock.FindByClientIdAsync("Fabrikam"))
instance.Setup(mock => mock.FindByClientIdAsync("Fabrikam", It.IsAny<CancellationToken>()))
.ReturnsAsync(application);
instance.Setup(mock => mock.GetClientTypeAsync(application))
instance.Setup(mock => mock.GetClientTypeAsync(application, It.IsAny<CancellationToken>()))
.ReturnsAsync(OpenIddictConstants.ClientTypes.Public);
});
@ -114,20 +118,20 @@ namespace OpenIddict.Core.Tests.Infrastructure {
Assert.Equal(OpenIdConnectConstants.Errors.InvalidRequest, response.Error);
Assert.Equal("Public clients are not allowed to send a client_secret.", response.ErrorDescription);
Mock.Get(manager).Verify(mock => mock.FindByClientIdAsync("Fabrikam"), Times.Once());
Mock.Get(manager).Verify(mock => mock.GetClientTypeAsync(application), Times.Once());
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());
}
[Fact]
public async Task ValidateRevocationRequest_ClientSecretIsRequiredForConfidentialClients() {
// Arrange
var application = Mock.Of<object>();
var application = new OpenIddictApplication();
var manager = CreateApplicationManager(instance => {
instance.Setup(mock => mock.FindByClientIdAsync("Fabrikam"))
instance.Setup(mock => mock.FindByClientIdAsync("Fabrikam", It.IsAny<CancellationToken>()))
.ReturnsAsync(application);
instance.Setup(mock => mock.GetClientTypeAsync(application))
instance.Setup(mock => mock.GetClientTypeAsync(application, It.IsAny<CancellationToken>()))
.ReturnsAsync(OpenIddictConstants.ClientTypes.Confidential);
});
@ -149,23 +153,23 @@ namespace OpenIddict.Core.Tests.Infrastructure {
Assert.Equal(OpenIdConnectConstants.Errors.InvalidClient, response.Error);
Assert.Equal("Missing credentials: ensure that you specified a client_secret.", response.ErrorDescription);
Mock.Get(manager).Verify(mock => mock.FindByClientIdAsync("Fabrikam"), Times.Once());
Mock.Get(manager).Verify(mock => mock.GetClientTypeAsync(application), Times.Once());
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());
}
[Fact]
public async Task ValidateRevocationRequest_RequestIsRejectedWhenClientCredentialsAreInvalid() {
// Arrange
var application = Mock.Of<object>();
var application = new OpenIddictApplication();
var manager = CreateApplicationManager(instance => {
instance.Setup(mock => mock.FindByClientIdAsync("Fabrikam"))
instance.Setup(mock => mock.FindByClientIdAsync("Fabrikam", It.IsAny<CancellationToken>()))
.ReturnsAsync(application);
instance.Setup(mock => mock.GetClientTypeAsync(application))
instance.Setup(mock => mock.GetClientTypeAsync(application, It.IsAny<CancellationToken>()))
.ReturnsAsync(OpenIddictConstants.ClientTypes.Confidential);
instance.Setup(mock => mock.ValidateSecretAsync(application, "7Fjfp0ZBr1KtDRbnfVdmIw"))
instance.Setup(mock => mock.ValidateSecretAsync(application, "7Fjfp0ZBr1KtDRbnfVdmIw", It.IsAny<CancellationToken>()))
.ReturnsAsync(false);
});
@ -187,9 +191,9 @@ namespace OpenIddict.Core.Tests.Infrastructure {
Assert.Equal(OpenIdConnectConstants.Errors.InvalidClient, response.Error);
Assert.Equal("Invalid credentials: ensure that you specified a correct client_secret.", response.ErrorDescription);
Mock.Get(manager).Verify(mock => mock.FindByClientIdAsync("Fabrikam"), Times.Once());
Mock.Get(manager).Verify(mock => mock.GetClientTypeAsync(application), Times.Once());
Mock.Get(manager).Verify(mock => mock.ValidateSecretAsync(application, "7Fjfp0ZBr1KtDRbnfVdmIw"), Times.Once());
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());
}
[Fact]
@ -284,7 +288,7 @@ namespace OpenIddict.Core.Tests.Infrastructure {
.Returns(ticket);
var manager = CreateTokenManager(instance => {
instance.Setup(mock => mock.FindByIdAsync("3E228451-1555-46F7-A471-951EFBA23A56"))
instance.Setup(mock => mock.FindByIdAsync("3E228451-1555-46F7-A471-951EFBA23A56", It.IsAny<CancellationToken>()))
.ReturnsAsync(null);
});
@ -304,8 +308,8 @@ namespace OpenIddict.Core.Tests.Infrastructure {
// Assert
Assert.Equal(0, response.GetParameters().Count());
Mock.Get(manager).Verify(mock => mock.FindByIdAsync("3E228451-1555-46F7-A471-951EFBA23A56"), Times.Once());
Mock.Get(manager).Verify(mock => mock.RevokeAsync(It.IsAny<object>()), Times.Never());
Mock.Get(manager).Verify(mock => mock.FindByIdAsync("3E228451-1555-46F7-A471-951EFBA23A56", It.IsAny<CancellationToken>()), Times.Once());
Mock.Get(manager).Verify(mock => mock.RevokeAsync(It.IsAny<OpenIddictToken>(), It.IsAny<CancellationToken>()), Times.Never());
}
[Fact]
@ -324,10 +328,10 @@ namespace OpenIddict.Core.Tests.Infrastructure {
format.Setup(mock => mock.Unprotect("SlAV32hkKG"))
.Returns(ticket);
var token = Mock.Of<object>();
var token = new OpenIddictToken();
var manager = CreateTokenManager(instance => {
instance.Setup(mock => mock.FindByIdAsync("3E228451-1555-46F7-A471-951EFBA23A56"))
instance.Setup(mock => mock.FindByIdAsync("3E228451-1555-46F7-A471-951EFBA23A56", It.IsAny<CancellationToken>()))
.ReturnsAsync(token);
});
@ -347,8 +351,8 @@ namespace OpenIddict.Core.Tests.Infrastructure {
// Assert
Assert.Equal(0, response.GetParameters().Count());
Mock.Get(manager).Verify(mock => mock.FindByIdAsync("3E228451-1555-46F7-A471-951EFBA23A56"), Times.Once());
Mock.Get(manager).Verify(mock => mock.RevokeAsync(token), Times.Once());
Mock.Get(manager).Verify(mock => mock.FindByIdAsync("3E228451-1555-46F7-A471-951EFBA23A56", It.IsAny<CancellationToken>()), Times.Once());
Mock.Get(manager).Verify(mock => mock.RevokeAsync(token, It.IsAny<CancellationToken>()), Times.Once());
}
}
}

23
test/OpenIddict.Core.Tests/Infrastructure/OpenIddictProviderTests.Serialization.cs → test/OpenIddict.Tests/OpenIddictProviderTests.Serialization.cs

@ -1,31 +1,34 @@
using System.Threading.Tasks;
using System.Threading;
using System.Threading.Tasks;
using AspNet.Security.OpenIdConnect.Client;
using AspNet.Security.OpenIdConnect.Primitives;
using Microsoft.Extensions.DependencyInjection;
using Moq;
using OpenIddict.Core;
using OpenIddict.Models;
using Xunit;
namespace OpenIddict.Core.Tests.Infrastructure {
namespace OpenIddict.Tests {
public partial class OpenIddictProviderTests {
[Fact]
public async Task SerializeAuthorizationCode_AuthorizationCodeIsAutomaticallyPersisted() {
// Arrange
var manager = CreateTokenManager(instance => {
instance.Setup(mock => mock.CreateAsync(OpenIdConnectConstants.TokenTypeHints.AuthorizationCode))
instance.Setup(mock => mock.CreateAsync(OpenIdConnectConstants.TokenTypeHints.AuthorizationCode, It.IsAny<CancellationToken>()))
.ReturnsAsync("3E228451-1555-46F7-A471-951EFBA23A56");
});
var server = CreateAuthorizationServer(builder => {
builder.Services.AddSingleton(CreateApplicationManager(instance => {
var application = Mock.Of<object>();
var application = new OpenIddictApplication();
instance.Setup(mock => mock.FindByClientIdAsync("Fabrikam"))
instance.Setup(mock => mock.FindByClientIdAsync("Fabrikam", It.IsAny<CancellationToken>()))
.ReturnsAsync(application);
instance.Setup(mock => mock.ValidateRedirectUriAsync(application, "http://www.fabrikam.com/path"))
instance.Setup(mock => mock.ValidateRedirectUriAsync(application, "http://www.fabrikam.com/path", It.IsAny<CancellationToken>()))
.ReturnsAsync(true);
instance.Setup(mock => mock.GetClientTypeAsync(application))
instance.Setup(mock => mock.GetClientTypeAsync(application, It.IsAny<CancellationToken>()))
.ReturnsAsync(OpenIddictConstants.ClientTypes.Public);
}));
@ -44,14 +47,14 @@ namespace OpenIddict.Core.Tests.Infrastructure {
// Assert
Assert.NotNull(response.Code);
Mock.Get(manager).Verify(mock => mock.CreateAsync(OpenIdConnectConstants.TokenTypeHints.AuthorizationCode), Times.Once());
Mock.Get(manager).Verify(mock => mock.CreateAsync(OpenIdConnectConstants.TokenTypeHints.AuthorizationCode, It.IsAny<CancellationToken>()), Times.Once());
}
[Fact]
public async Task SerializeRefreshToken_RefreshTokenIsAutomaticallyPersisted() {
// Arrange
var manager = CreateTokenManager(instance => {
instance.Setup(mock => mock.CreateAsync(OpenIdConnectConstants.TokenTypeHints.RefreshToken))
instance.Setup(mock => mock.CreateAsync(OpenIdConnectConstants.TokenTypeHints.RefreshToken, It.IsAny<CancellationToken>()))
.ReturnsAsync("3E228451-1555-46F7-A471-951EFBA23A56");
});
@ -72,7 +75,7 @@ namespace OpenIddict.Core.Tests.Infrastructure {
// Assert
Assert.NotNull(response.RefreshToken);
Mock.Get(manager).Verify(mock => mock.CreateAsync(OpenIdConnectConstants.TokenTypeHints.RefreshToken), Times.Once());
Mock.Get(manager).Verify(mock => mock.CreateAsync(OpenIdConnectConstants.TokenTypeHints.RefreshToken, It.IsAny<CancellationToken>()), Times.Once());
}
}
}

20
test/OpenIddict.Core.Tests/Infrastructure/OpenIddictProviderTests.Session.cs → test/OpenIddict.Tests/OpenIddictProviderTests.Session.cs

@ -1,13 +1,17 @@
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using AspNet.Security.OpenIdConnect.Client;
using AspNet.Security.OpenIdConnect.Primitives;
using Microsoft.AspNetCore.Builder;
using Microsoft.Extensions.Caching.Distributed;
using Microsoft.Extensions.DependencyInjection;
using Moq;
using OpenIddict.Core;
using OpenIddict.Models;
using Xunit;
namespace OpenIddict.Core.Tests.Infrastructure {
namespace OpenIddict.Tests {
public partial class OpenIddictProviderTests {
[Fact]
public async Task ExtractLogoutRequest_RequestIdParameterIsRejectedWhenRequestCachingIsDisabled() {
@ -51,7 +55,7 @@ namespace OpenIddict.Core.Tests.Infrastructure {
public async Task ValidateLogoutRequest_RequestIsRejectedWhenRedirectUriIsInvalid() {
// Arrange
var manager = CreateApplicationManager(instance => {
instance.Setup(mock => mock.FindByLogoutRedirectUri("http://www.fabrikam.com/path"))
instance.Setup(mock => mock.FindByLogoutRedirectUri("http://www.fabrikam.com/path", It.IsAny<CancellationToken>()))
.ReturnsAsync(null);
});
@ -70,7 +74,7 @@ namespace OpenIddict.Core.Tests.Infrastructure {
Assert.Equal(OpenIdConnectConstants.Errors.InvalidClient, response.Error);
Assert.Equal("Invalid post_logout_redirect_uri.", response.ErrorDescription);
Mock.Get(manager).Verify(mock => mock.FindByLogoutRedirectUri("http://www.fabrikam.com/path"), Times.Once());
Mock.Get(manager).Verify(mock => mock.FindByLogoutRedirectUri("http://www.fabrikam.com/path", It.IsAny<CancellationToken>()), Times.Once());
}
[Fact]
@ -80,9 +84,9 @@ namespace OpenIddict.Core.Tests.Infrastructure {
var server = CreateAuthorizationServer(builder => {
builder.Services.AddSingleton(CreateApplicationManager(instance => {
var application = Mock.Of<object>();
var application = new OpenIddictApplication();
instance.Setup(mock => mock.FindByLogoutRedirectUri("http://www.fabrikam.com/path"))
instance.Setup(mock => mock.FindByLogoutRedirectUri("http://www.fabrikam.com/path", It.IsAny<CancellationToken>()))
.ReturnsAsync(application);
}));
@ -115,9 +119,9 @@ namespace OpenIddict.Core.Tests.Infrastructure {
// Arrange
var server = CreateAuthorizationServer(builder => {
builder.Services.AddSingleton(CreateApplicationManager(instance => {
var application = Mock.Of<object>();
var application = new OpenIddictApplication();
instance.Setup(mock => mock.FindByLogoutRedirectUri("http://www.fabrikam.com/path"))
instance.Setup(mock => mock.FindByLogoutRedirectUri("http://www.fabrikam.com/path", It.IsAny<CancellationToken>()))
.ReturnsAsync(application);
}));
});
@ -139,7 +143,7 @@ namespace OpenIddict.Core.Tests.Infrastructure {
// Arrange
var server = CreateAuthorizationServer(builder => {
builder.Services.AddSingleton(CreateApplicationManager(instance => {
instance.Setup(mock => mock.FindByLogoutRedirectUri("http://www.fabrikam.com/path"))
instance.Setup(mock => mock.FindByLogoutRedirectUri("http://www.fabrikam.com/path", It.IsAny<CancellationToken>()))
.ReturnsAsync(null);
}));

3
test/OpenIddict.Core.Tests/Infrastructure/OpenIddictProviderTests.Userinfo.cs → test/OpenIddict.Tests/OpenIddictProviderTests.Userinfo.cs

@ -5,11 +5,12 @@ using AspNet.Security.OpenIdConnect.Extensions;
using AspNet.Security.OpenIdConnect.Primitives;
using AspNet.Security.OpenIdConnect.Server;
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Http.Authentication;
using Moq;
using Xunit;
namespace OpenIddict.Core.Tests.Infrastructure {
namespace OpenIddict.Tests {
public partial class OpenIddictProviderTests {
[Fact]
public async Task HandleUserinfoRequest_RequestIsHandledByUserCode() {

33
test/OpenIddict.Core.Tests/Infrastructure/OpenIddictProviderTests.cs → test/OpenIddict.Tests/OpenIddictProviderTests.cs

@ -18,8 +18,10 @@ using Microsoft.Extensions.Logging;
using Microsoft.Net.Http.Headers;
using Moq;
using Newtonsoft.Json;
using OpenIddict.Core;
using OpenIddict.Models;
namespace OpenIddict.Core.Tests.Infrastructure {
namespace OpenIddict.Tests {
public partial class OpenIddictProviderTests {
public const string AuthorizationEndpoint = "/connect/authorize";
public const string ConfigurationEndpoint = "/.well-known/openid-configuration";
@ -37,7 +39,10 @@ namespace OpenIddict.Core.Tests.Infrastructure {
builder.ConfigureLogging(options => options.AddDebug());
builder.ConfigureServices(services => {
var instance = services.AddOpenIddict<object, object, object, object>()
services.AddAuthentication();
services.AddOptions();
var instance = services.AddOpenIddict()
// Disable the transport security requirement during testing.
.DisableHttpsRequirement()
@ -59,7 +64,7 @@ namespace OpenIddict.Core.Tests.Infrastructure {
// Register the X.509 certificate used to sign the identity tokens.
.AddSigningCertificate(
assembly: typeof(OpenIddictProviderTests).GetTypeInfo().Assembly,
resource: "OpenIddict.Core.Tests.Certificate.pfx",
resource: "OpenIddict.Tests.Certificate.pfx",
password: "OpenIddict")
// Note: overriding the default data protection provider is not necessary for the tests to pass,
@ -67,6 +72,10 @@ namespace OpenIddict.Core.Tests.Infrastructure {
// helps make the unit tests run faster, as no registry or disk access is required in this case.
.UseDataProtectionProvider(new EphemeralDataProtectionProvider());
// Replace the default application/token managers.
services.AddSingleton(CreateApplicationManager());
services.AddSingleton(CreateTokenManager());
// Run the configuration delegate
// registered by the unit tests.
configuration?.Invoke(instance);
@ -129,22 +138,20 @@ namespace OpenIddict.Core.Tests.Infrastructure {
return new TestServer(builder);
}
private static OpenIddictApplicationManager<object> CreateApplicationManager(Action<Mock<OpenIddictApplicationManager<object>>> setup = null) {
var manager = new Mock<OpenIddictApplicationManager<object>>(
Mock.Of<IServiceProvider>(),
Mock.Of<IOpenIddictApplicationStore<object>>(),
Mock.Of<ILogger<OpenIddictApplicationManager<object>>>());
private static OpenIddictApplicationManager<OpenIddictApplication> CreateApplicationManager(Action<Mock<OpenIddictApplicationManager<OpenIddictApplication>>> setup = null) {
var manager = new Mock<OpenIddictApplicationManager<OpenIddictApplication>>(
Mock.Of<IOpenIddictApplicationStore<OpenIddictApplication>>(),
Mock.Of<ILogger<OpenIddictApplicationManager<OpenIddictApplication>>>());
setup?.Invoke(manager);
return manager.Object;
}
private static OpenIddictTokenManager<object> CreateTokenManager(Action<Mock<OpenIddictTokenManager<object>>> setup = null) {
var manager = new Mock<OpenIddictTokenManager<object>>(
Mock.Of<IServiceProvider>(),
Mock.Of<IOpenIddictTokenStore<object>>(),
Mock.Of<ILogger<OpenIddictTokenManager<object>>>());
private static OpenIddictTokenManager<OpenIddictToken> CreateTokenManager(Action<Mock<OpenIddictTokenManager<OpenIddictToken>>> setup = null) {
var manager = new Mock<OpenIddictTokenManager<OpenIddictToken>>(
Mock.Of<IOpenIddictTokenStore<OpenIddictToken>>(),
Mock.Of<ILogger<OpenIddictTokenManager<OpenIddictToken>>>());
setup?.Invoke(manager);

12
test/OpenIddict.Tests/project.json

@ -1,10 +1,16 @@
{
"buildOptions": {
"warningsAsErrors": true
"warningsAsErrors": true,
"embed": {
"include": [ "Certificate.pfx" ]
}
},
"dependencies": {
"AspNet.Security.OpenIdConnect.Client": "1.0.0-beta7-final",
"dotnet-test-xunit": "2.2.0-preview2-build1029",
"Microsoft.AspNetCore.Diagnostics": "1.0.0",
"Microsoft.AspNetCore.TestHost": "1.0.0",
"Microsoft.Extensions.Caching.Memory": "1.0.0",
"Microsoft.Extensions.Logging.Debug": "1.0.0",
@ -15,6 +21,10 @@
"frameworks": {
"netcoreapp1.0": {
"buildOptions": {
"define": [ "SUPPORTS_ECDSA" ]
},
"dependencies": {
"Microsoft.NETCore.App": { "type": "platform", "version": "1.0.0" }
},

Loading…
Cancel
Save