From bb1d2e07be96b35afc9e4af9bcb08e198548078e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A9vin=20Chalet?= Date: Tue, 29 Nov 2016 13:57:43 +0100 Subject: [PATCH] Remove OpenIddictDbContext and revamp OpenIddict/OpenIddict.Core --- OpenIddict.sln | 7 + .../Controllers/AccountController.cs | 2 +- .../Controllers/AuthorizationController.cs | 5 +- .../Mvc.Server/Models/ApplicationDbContext.cs | 12 +- samples/Mvc.Server/Startup.cs | 34 +- samples/Mvc.Server/project.json | 2 + .../Infrastructure/OpenIddictServices.cs | 57 -- .../Managers/OpenIddictApplicationManager.cs | 86 +- .../OpenIddictAuthorizationManager.cs | 18 +- .../Managers/OpenIddictScopeManager.cs | 18 +- .../Managers/OpenIddictTokenManager.cs | 31 +- src/OpenIddict.Core/OpenIddictBuilder.cs | 463 +--------- src/OpenIddict.Core/OpenIddictConstants.cs | 2 +- src/OpenIddict.Core/OpenIddictExtensions.cs | 184 +--- .../Stores/IOpenIddictApplicationStore.cs | 2 +- .../Stores/IOpenIddictAuthorizationStore.cs | 2 +- .../Stores/IOpenIddictScopeStore.cs | 2 +- .../Stores/IOpenIddictTokenStore.cs | 2 +- src/OpenIddict.Core/project.json | 17 +- .../OpenIddict.EntityFramework.xproj | 2 +- .../OpenIddictCustomizer.cs | 40 + .../OpenIddictDbContext.cs | 199 ----- .../OpenIddictExtension.cs | 27 + .../OpenIddictExtensions.cs | 151 +++- .../Stores/OpenIddictApplicationStore.cs | 11 +- .../Stores/OpenIddictAuthorizationStore.cs | 11 +- .../Stores/OpenIddictScopeStore.cs | 11 +- .../Stores/OpenIddictTokenStore.cs | 11 +- src/OpenIddict.EntityFramework/project.json | 8 +- src/OpenIddict.Models/OpenIddict.Models.xproj | 21 + .../OpenIddictApplication.cs | 4 +- .../OpenIddictAuthorization.cs | 2 +- .../OpenIddictScope.cs | 2 +- .../OpenIddictToken.cs | 2 +- src/OpenIddict.Models/project.json | 43 + src/OpenIddict.Mvc/OpenIddictExtensions.cs | 11 +- src/OpenIddict.Mvc/project.json | 3 +- src/OpenIddict/OpenIddictExtensions.cs | 811 ++++++++++++++++-- .../OpenIddictOptions.cs | 2 + .../OpenIddictProvider.Authentication.cs | 109 +-- .../OpenIddictProvider.Discovery.cs | 10 +- .../OpenIddictProvider.Exchange.cs | 73 +- .../OpenIddictProvider.Introspection.cs | 39 +- .../OpenIddictProvider.Revocation.cs | 41 +- .../OpenIddictProvider.Serialization.cs | 11 +- .../OpenIddictProvider.Session.cs | 50 +- .../OpenIddictProvider.Userinfo.cs | 2 +- .../OpenIddictProvider.cs | 4 +- src/OpenIddict/project.json | 7 +- .../OpenIddictBuilderTests.cs | 522 ----------- .../OpenIddictExtensionsTests.cs | 228 +---- test/OpenIddict.Core.Tests/project.json | 14 +- .../OpenIddictExtensionsTests.cs | 43 +- .../project.json | 2 - .../OpenIddictExtensionsTests.cs | 3 +- test/OpenIddict.Mvc.Tests/project.json | 2 - .../Certificate.pfx | Bin .../OpenIddictBuilderTests.cs | 526 ++++++++++++ .../OpenIddictExtensionsTests.cs | 721 +++++++++++++++- .../OpenIddictProviderTests.Authentication.cs | 68 +- .../OpenIddictProviderTests.Discovery.cs | 4 +- .../OpenIddictProviderTests.Exchange.cs | 114 +-- .../OpenIddictProviderTests.Introspection.cs | 66 +- .../OpenIddictProviderTests.Revocation.cs | 58 +- .../OpenIddictProviderTests.Serialization.cs | 23 +- .../OpenIddictProviderTests.Session.cs | 20 +- .../OpenIddictProviderTests.Userinfo.cs | 3 +- .../OpenIddictProviderTests.cs | 33 +- test/OpenIddict.Tests/project.json | 12 +- 69 files changed, 2936 insertions(+), 2190 deletions(-) delete mode 100644 src/OpenIddict.Core/Infrastructure/OpenIddictServices.cs create mode 100644 src/OpenIddict.EntityFramework/OpenIddictCustomizer.cs delete mode 100644 src/OpenIddict.EntityFramework/OpenIddictDbContext.cs create mode 100644 src/OpenIddict.EntityFramework/OpenIddictExtension.cs create mode 100644 src/OpenIddict.Models/OpenIddict.Models.xproj rename src/{OpenIddict.EntityFramework/Models => OpenIddict.Models}/OpenIddictApplication.cs (95%) rename src/{OpenIddict.EntityFramework/Models => OpenIddict.Models}/OpenIddictAuthorization.cs (98%) rename src/{OpenIddict.EntityFramework/Models => OpenIddict.Models}/OpenIddictScope.cs (97%) rename src/{OpenIddict.EntityFramework/Models => OpenIddict.Models}/OpenIddictToken.cs (97%) create mode 100644 src/OpenIddict.Models/project.json rename src/{OpenIddict.Core => OpenIddict}/OpenIddictOptions.cs (98%) rename src/{OpenIddict.Core/Infrastructure => OpenIddict}/OpenIddictProvider.Authentication.cs (75%) rename src/{OpenIddict.Core/Infrastructure => OpenIddict}/OpenIddictProvider.Discovery.cs (88%) rename src/{OpenIddict.Core/Infrastructure => OpenIddict}/OpenIddictProvider.Exchange.cs (71%) rename src/{OpenIddict.Core/Infrastructure => OpenIddict}/OpenIddictProvider.Introspection.cs (69%) rename src/{OpenIddict.Core/Infrastructure => OpenIddict}/OpenIddictProvider.Revocation.cs (72%) rename src/{OpenIddict.Core/Infrastructure => OpenIddict}/OpenIddictProvider.Serialization.cs (76%) rename src/{OpenIddict.Core/Infrastructure => OpenIddict}/OpenIddictProvider.Session.cs (73%) rename src/{OpenIddict.Core/Infrastructure => OpenIddict}/OpenIddictProvider.Userinfo.cs (95%) rename src/{OpenIddict.Core/Infrastructure => OpenIddict}/OpenIddictProvider.cs (85%) rename test/{OpenIddict.Core.Tests => OpenIddict.Tests}/Certificate.pfx (100%) create mode 100644 test/OpenIddict.Tests/OpenIddictBuilderTests.cs rename test/{OpenIddict.Core.Tests/Infrastructure => OpenIddict.Tests}/OpenIddictProviderTests.Authentication.cs (91%) rename test/{OpenIddict.Core.Tests/Infrastructure => OpenIddict.Tests}/OpenIddictProviderTests.Discovery.cs (97%) rename test/{OpenIddict.Core.Tests/Infrastructure => OpenIddict.Tests}/OpenIddictProviderTests.Exchange.cs (87%) rename test/{OpenIddict.Core.Tests/Infrastructure => OpenIddict.Tests}/OpenIddictProviderTests.Introspection.cs (86%) rename test/{OpenIddict.Core.Tests/Infrastructure => OpenIddict.Tests}/OpenIddictProviderTests.Revocation.cs (89%) rename test/{OpenIddict.Core.Tests/Infrastructure => OpenIddict.Tests}/OpenIddictProviderTests.Serialization.cs (78%) rename test/{OpenIddict.Core.Tests/Infrastructure => OpenIddict.Tests}/OpenIddictProviderTests.Session.cs (90%) rename test/{OpenIddict.Core.Tests/Infrastructure => OpenIddict.Tests}/OpenIddictProviderTests.Userinfo.cs (96%) rename test/{OpenIddict.Core.Tests/Infrastructure => OpenIddict.Tests}/OpenIddictProviderTests.cs (83%) diff --git a/OpenIddict.sln b/OpenIddict.sln index b5b18322..a359bb5f 100644 --- a/OpenIddict.sln +++ b/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 diff --git a/samples/Mvc.Server/Controllers/AccountController.cs b/samples/Mvc.Server/Controllers/AccountController.cs index 37ab7477..54fe77f7 100644 --- a/samples/Mvc.Server/Controllers/AccountController.cs +++ b/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] diff --git a/samples/Mvc.Server/Controllers/AuthorizationController.cs b/samples/Mvc.Server/Controllers/AuthorizationController.cs index 35533e8a..e86f0da6 100644 --- a/samples/Mvc.Server/Controllers/AuthorizationController.cs +++ b/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 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, diff --git a/samples/Mvc.Server/Models/ApplicationDbContext.cs b/samples/Mvc.Server/Models/ApplicationDbContext.cs index 3aca6de3..5de8fdcb 100644 --- a/samples/Mvc.Server/Models/ApplicationDbContext.cs +++ b/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 { + public class ApplicationDbContext : IdentityDbContext { 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. diff --git a/samples/Mvc.Server/Startup.cs b/samples/Mvc.Server/Startup.cs index a0eb8754..a1bd3ce1 100644 --- a/samples/Mvc.Server/Startup.cs +++ b/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() .AddDefaultTokenProviders(); - // Register the OpenIddict services, including the default Entity Framework stores. - services.AddOpenIddict() + // Register the OpenIddict services. + services.AddOpenIddict() + // Register the Entity Framework stores. + .AddEntityFrameworkStores() + // 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() + // + // 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() + // + // 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>())) { context.Database.EnsureCreated(); + var applications = context.Set(); + // 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", diff --git a/samples/Mvc.Server/project.json b/samples/Mvc.Server/project.json index 97b6e026..7a59874b 100644 --- a/samples/Mvc.Server/project.json +++ b/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" } }, diff --git a/src/OpenIddict.Core/Infrastructure/OpenIddictServices.cs b/src/OpenIddict.Core/Infrastructure/OpenIddictServices.cs deleted file mode 100644 index f683dd08..00000000 --- a/src/OpenIddict.Core/Infrastructure/OpenIddictServices.cs +++ /dev/null @@ -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 { - /// - /// Exposes the common services used by OpenIddict. - /// - public class OpenIddictServices - where TApplication : class where TAuthorization : class - where TScope : class where TToken : class { - public OpenIddictServices([NotNull] IServiceProvider services) { - Services = services; - } - - /// - /// Gets the . - /// - public virtual OpenIddictApplicationManager Applications => - Services.GetRequiredService>(); - - /// - /// Gets the optional . - /// - public virtual HttpContext Context => Services.GetService()?.HttpContext; - - /// - /// Gets the . - /// - public virtual ILogger Logger => - Services.GetRequiredService>>(); - - /// - /// Gets the . - /// - public virtual OpenIddictOptions Options => Services.GetRequiredService>().Value; - - /// - /// Gets the used to resolve services. - /// - public virtual IServiceProvider Services { get; } - - /// - /// Gets the . - /// - public virtual OpenIddictTokenManager Tokens => Services.GetRequiredService>(); - } -} \ No newline at end of file diff --git a/src/OpenIddict.Core/Managers/OpenIddictApplicationManager.cs b/src/OpenIddict.Core/Managers/OpenIddictApplicationManager.cs index 16c9f430..93c37c2f 100644 --- a/src/OpenIddict.Core/Managers/OpenIddictApplicationManager.cs +++ b/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 { /// /// Provides methods allowing to manage the applications stored in the store. /// /// The type of the Application entity. public class OpenIddictApplicationManager where TApplication : class { public OpenIddictApplicationManager( - [NotNull] IServiceProvider services, [NotNull] IOpenIddictApplicationStore store, [NotNull] ILogger> logger) { - Context = services?.GetService()?.HttpContext; Store = store; Logger = logger; } - /// - /// Gets the HTTP context associated with the current manager. - /// - protected HttpContext Context { get; } - - /// - /// Gets the cancellation token used to abort async operations. - /// - protected CancellationToken CancellationToken => Context?.RequestAborted ?? CancellationToken.None; - /// /// Gets the logger associated with the current manager. /// @@ -53,68 +39,73 @@ namespace OpenIddict { /// Creates a new application. /// /// The application to create. + /// The that can be used to abort the operation. /// /// A that can be used to monitor the asynchronous operation, /// whose result returns the unique identifier associated with the application. /// - public virtual Task CreateAsync(TApplication application) { + public virtual Task CreateAsync(TApplication application, CancellationToken cancellationToken) { if (application == null) { throw new ArgumentNullException(nameof(application)); } - return Store.CreateAsync(application, CancellationToken); + return Store.CreateAsync(application, cancellationToken); } /// /// Retrieves an application using its unique identifier. /// /// The unique identifier associated with the application. + /// The that can be used to abort the operation. /// /// A that can be used to monitor the asynchronous operation, /// whose result returns the client application corresponding to the identifier. /// - public virtual Task FindByIdAsync(string identifier) { - return Store.FindByIdAsync(identifier, CancellationToken); + public virtual Task FindByIdAsync(string identifier, CancellationToken cancellationToken) { + return Store.FindByIdAsync(identifier, cancellationToken); } /// /// Retrieves an application using its client identifier. /// /// The client identifier associated with the application. + /// The that can be used to abort the operation. /// /// A that can be used to monitor the asynchronous operation, /// whose result returns the client application corresponding to the identifier. /// - public virtual Task FindByClientIdAsync(string identifier) { - return Store.FindByClientIdAsync(identifier, CancellationToken); + public virtual Task FindByClientIdAsync(string identifier, CancellationToken cancellationToken) { + return Store.FindByClientIdAsync(identifier, cancellationToken); } /// /// Retrieves an application using its post_logout_redirect_uri. /// /// The post_logout_redirect_uri associated with the application. + /// The that can be used to abort the operation. /// /// A that can be used to monitor the asynchronous operation, whose result /// returns the client application corresponding to the post_logout_redirect_uri. /// - public virtual Task FindByLogoutRedirectUri(string url) { - return Store.FindByLogoutRedirectUri(url, CancellationToken); + public virtual Task FindByLogoutRedirectUri(string url, CancellationToken cancellationToken) { + return Store.FindByLogoutRedirectUri(url, cancellationToken); } /// /// Retrieves the client type associated with an application. /// /// The application. + /// The that can be used to abort the operation. /// /// A that can be used to monitor the asynchronous operation, /// whose result returns the client type of the application (by default, "public"). /// - public virtual async Task GetClientTypeAsync(TApplication application) { + public virtual async Task 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. /// /// The application. + /// The that can be used to abort the operation. /// /// A that can be used to monitor the asynchronous operation, /// whose result returns the display name associated with the application. /// - public virtual Task GetDisplayNameAsync(TApplication application) { + public virtual Task GetDisplayNameAsync(TApplication application, CancellationToken cancellationToken) { if (application == null) { throw new ArgumentNullException(nameof(application)); } - return Store.GetDisplayNameAsync(application, CancellationToken); + return Store.GetDisplayNameAsync(application, cancellationToken); } /// /// Retrieves the token identifiers associated with an application. /// /// The application. + /// The that can be used to abort the operation. /// /// A that can be used to monitor the asynchronous operation, /// whose result returns the tokens associated with the application. /// - public virtual Task> GetTokensAsync(TApplication application) { + public virtual Task> GetTokensAsync(TApplication application, CancellationToken cancellationToken) { if (application == null) { throw new ArgumentNullException(nameof(application)); } - return Store.GetTokensAsync(application, CancellationToken); + return Store.GetTokensAsync(application, cancellationToken); } /// /// Determines whether an application is a confidential client. /// /// The application. + /// The that can be used to abort the operation. /// true if the application is a confidential client, false otherwise. - public async Task IsConfidentialAsync([NotNull] TApplication application) { + public async Task 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. /// /// The application. + /// The that can be used to abort the operation. /// true if the application is a public client, false otherwise. - public async Task IsPublicAsync(TApplication application) { + public async Task 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 { /// /// The application. /// The address that should be compared to the redirect_uri stored in the database. + /// The that can be used to abort the operation. /// /// A that can be used to monitor the asynchronous operation, /// whose result returns a boolean indicating whether the redirect_uri was valid. /// - public virtual async Task ValidateRedirectUriAsync(TApplication application, string address) { + public virtual async Task 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 { /// /// The application. /// The secret that should be compared to the client_secret stored in the database. + /// The that can be used to abort the operation. /// A that can be used to monitor the asynchronous operation. /// /// A that can be used to monitor the asynchronous operation, /// whose result returns a boolean indicating whether the client secret was valid. /// - public virtual async Task ValidateSecretAsync(TApplication application, string secret) { + public virtual async Task 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; } diff --git a/src/OpenIddict.Core/Managers/OpenIddictAuthorizationManager.cs b/src/OpenIddict.Core/Managers/OpenIddictAuthorizationManager.cs index a6abced6..d0a70380 100644 --- a/src/OpenIddict.Core/Managers/OpenIddictAuthorizationManager.cs +++ b/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 { /// /// Provides methods allowing to manage the authorizations stored in the store. /// /// The type of the Authorization entity. public class OpenIddictAuthorizationManager where TAuthorization : class { public OpenIddictAuthorizationManager( - [NotNull] IServiceProvider services, [NotNull] IOpenIddictAuthorizationStore store, [NotNull] ILogger> logger) { - Context = services?.GetService()?.HttpContext; Logger = logger; Store = store; } - /// - /// Gets the HTTP context associated with the current manager. - /// - protected HttpContext Context { get; } - - /// - /// Gets the cancellation token used to abort async operations. - /// - protected CancellationToken CancellationToken => Context?.RequestAborted ?? CancellationToken.None; - /// /// Gets the logger associated with the current manager. /// diff --git a/src/OpenIddict.Core/Managers/OpenIddictScopeManager.cs b/src/OpenIddict.Core/Managers/OpenIddictScopeManager.cs index 1d82e02a..87bfb1fe 100644 --- a/src/OpenIddict.Core/Managers/OpenIddictScopeManager.cs +++ b/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 { /// /// Provides methods allowing to manage the scopes stored in the store. /// /// The type of the Scope entity. public class OpenIddictScopeManager where TScope : class { public OpenIddictScopeManager( - [NotNull] IServiceProvider services, [NotNull] IOpenIddictScopeStore store, [NotNull] ILogger> logger) { - Context = services?.GetService()?.HttpContext; Logger = logger; Store = store; } - /// - /// Gets the HTTP context associated with the current manager. - /// - protected HttpContext Context { get; } - - /// - /// Gets the cancellation token used to abort async operations. - /// - protected CancellationToken CancellationToken => Context?.RequestAborted ?? CancellationToken.None; - /// /// Gets the logger associated with the current manager. /// diff --git a/src/OpenIddict.Core/Managers/OpenIddictTokenManager.cs b/src/OpenIddict.Core/Managers/OpenIddictTokenManager.cs index 3099f983..d7736a1e 100644 --- a/src/OpenIddict.Core/Managers/OpenIddictTokenManager.cs +++ b/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 { /// /// Provides methods allowing to manage the tokens stored in the store. /// /// The type of the Token entity. public class OpenIddictTokenManager where TToken : class { public OpenIddictTokenManager( - [NotNull] IServiceProvider services, [NotNull] IOpenIddictTokenStore store, [NotNull] ILogger> logger) { - Context = services?.GetService()?.HttpContext; Logger = logger; Store = store; } - /// - /// Gets the cancellation token used to abort async operations. - /// - protected CancellationToken CancellationToken => Context?.RequestAborted ?? CancellationToken.None; - - /// - /// Gets the HTTP context associated with the current manager. - /// - protected HttpContext Context { get; } - /// /// Gets the logger associated with the current manager. /// @@ -51,41 +37,44 @@ namespace OpenIddict { /// Creates a new token, which is not associated with a particular user or client. /// /// The token type. + /// The that can be used to abort the operation. /// /// A that can be used to monitor the asynchronous operation, /// whose result returns the unique identifier associated with the token. /// - public virtual Task CreateAsync(string type) { + public virtual Task 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); } /// /// Retrieves a token using its unique identifier. /// /// The unique identifier associated with the token. + /// The that can be used to abort the operation. /// /// A that can be used to monitor the asynchronous operation, /// whose result returns the token corresponding to the unique identifier. /// - public virtual Task FindByIdAsync(string identifier) { - return Store.FindByIdAsync(identifier, CancellationToken); + public virtual Task FindByIdAsync(string identifier, CancellationToken cancellationToken) { + return Store.FindByIdAsync(identifier, cancellationToken); } /// /// Revokes a token. /// /// The token to revoke. + /// The that can be used to abort the operation. /// A that can be used to monitor the asynchronous operation. - 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); } } } \ No newline at end of file diff --git a/src/OpenIddict.Core/OpenIddictBuilder.cs b/src/OpenIddict.Core/OpenIddictBuilder.cs index d2874707..965402ff 100644 --- a/src/OpenIddict.Core/OpenIddictBuilder.cs +++ b/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 { /// /// Exposes the necessary methods required to configure OpenIddict. /// @@ -27,7 +22,11 @@ namespace Microsoft.AspNetCore.Builder { /// Initializes a new instance of . /// /// The services collection. - 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; } - /// - /// Amends the default OpenIddict configuration. - /// - /// The delegate used to configure the OpenIddict options. - /// This extension can be safely called multiple times. - /// The . - public virtual OpenIddictBuilder Configure([NotNull] Action configuration) { - if (configuration == null) { - throw new ArgumentNullException(nameof(configuration)); - } - - Services.Configure(configuration); - - return this; - } - /// /// Adds a custom application manager. /// @@ -300,429 +283,5 @@ namespace Microsoft.AspNetCore.Builder { return this; } - - /// - /// 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. - /// - /// The . - public virtual OpenIddictBuilder AddEphemeralSigningKey() { - return Configure(options => options.SigningCredentials.AddEphemeralKey()); - } - - /// - /// 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. - /// - /// The algorithm associated with the signing key. - /// The . - 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)); - } - - /// - /// Registers a that is used to sign the tokens issued by OpenIddict. - /// - /// The certificate used to sign the security tokens issued by the server. - /// The . - 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)); - } - - /// - /// Registers a retrieved from an - /// embedded resource and used to sign the tokens issued by OpenIddict. - /// - /// The assembly containing the certificate. - /// The name of the embedded resource. - /// The password used to open the certificate. - /// The . - 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)); - } - - /// - /// Registers a extracted from a - /// stream and used to sign the tokens issued by OpenIddict. - /// - /// The stream containing the certificate. - /// The password used to open the certificate. - /// The . - 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)); - } - - /// - /// Registers a extracted from a - /// stream and used to sign the tokens issued by OpenIddict. - /// - /// The stream containing the certificate. - /// The password used to open the certificate. - /// - /// An enumeration of flags indicating how and where - /// to store the private key of the certificate. - /// - /// The . - 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)); - } - - /// - /// Registers a retrieved from the X.509 - /// machine store and used to sign the tokens issued by OpenIddict. - /// - /// The thumbprint of the certificate used to identify it in the X.509 store. - /// The . - public virtual OpenIddictBuilder AddSigningCertificate([NotNull] string thumbprint) { - if (string.IsNullOrEmpty(thumbprint)) { - throw new ArgumentNullException(nameof(thumbprint)); - } - - return Configure(options => options.SigningCredentials.AddCertificate(thumbprint)); - } - - /// - /// Registers a retrieved from the given - /// X.509 store and used to sign the tokens issued by OpenIddict. - /// - /// The thumbprint of the certificate used to identify it in the X.509 store. - /// The name of the X.509 store. - /// The location of the X.509 store. - /// The . - 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)); - } - - /// - /// Registers a used to sign the tokens issued by OpenIddict. - /// Note: using asymmetric keys is recommended on production. - /// - /// The security key. - /// The . - public virtual OpenIddictBuilder AddSigningKey([NotNull] SecurityKey key) { - if (key == null) { - throw new ArgumentNullException(nameof(key)); - } - - return Configure(options => options.SigningCredentials.AddKey(key)); - } - - /// - /// 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. - /// - /// The . - public virtual OpenIddictBuilder AllowAuthorizationCodeFlow() { - return Configure(options => options.GrantTypes.Add( - OpenIdConnectConstants.GrantTypes.AuthorizationCode)); - } - - /// - /// Enables client credentials flow support. For more information about this - /// specific OAuth2 flow, visit https://tools.ietf.org/html/rfc6749#section-4.4. - /// - /// The . - public virtual OpenIddictBuilder AllowClientCredentialsFlow() { - return Configure(options => options.GrantTypes.Add( - OpenIdConnectConstants.GrantTypes.ClientCredentials)); - } - - /// - /// Enables custom grant type support. - /// - /// The grant type associated with the flow. - /// The . - 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)); - } - - /// - /// 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. - /// - /// The . - public virtual OpenIddictBuilder AllowImplicitFlow() { - return Configure(options => options.GrantTypes.Add( - OpenIdConnectConstants.GrantTypes.Implicit)); - } - - /// - /// Enables password flow support. For more information about this specific - /// OAuth2 flow, visit https://tools.ietf.org/html/rfc6749#section-4.3. - /// - /// The . - public virtual OpenIddictBuilder AllowPasswordFlow() { - return Configure(options => options.GrantTypes.Add( - OpenIdConnectConstants.GrantTypes.Password)); - } - - /// - /// Enables refresh token flow support. For more information about this - /// specific OAuth2 flow, visit https://tools.ietf.org/html/rfc6749#section-6. - /// - /// The . - public virtual OpenIddictBuilder AllowRefreshTokenFlow() { - return Configure(options => options.GrantTypes.Add( - OpenIdConnectConstants.GrantTypes.RefreshToken)); - } - - /// - /// Disables the configuration endpoint. - /// - /// The . - public virtual OpenIddictBuilder DisableConfigurationEndpoint() { - return Configure(options => options.ConfigurationEndpointPath = PathString.Empty); - } - - /// - /// Disables the cryptography endpoint. - /// - /// The . - public virtual OpenIddictBuilder DisableCryptographyEndpoint() { - return Configure(options => options.CryptographyEndpointPath = PathString.Empty); - } - - /// - /// Disables the HTTPS requirement during development. - /// - /// The . - public virtual OpenIddictBuilder DisableHttpsRequirement() { - return Configure(options => options.AllowInsecureHttp = true); - } - - /// - /// Disables sliding expiration, which prevents OpenIddict from issuing a new - /// refresh token when receiving a grant_type=refresh_token token request. - /// - /// The . - public virtual OpenIddictBuilder DisableSlidingExpiration() { - return Configure(options => options.UseSlidingExpiration = false); - } - - /// - /// Enables the authorization endpoint. - /// - /// The relative path of the authorization endpoint. - /// The . - 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); - } - - /// - /// Enables the introspection endpoint. - /// - /// The relative path of the logout endpoint. - /// The . - 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); - } - - /// - /// Enables the logout endpoint. - /// - /// The relative path of the logout endpoint. - /// The . - 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); - } - - /// - /// 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. - /// - /// The . - public virtual OpenIddictBuilder EnableRequestCaching() { - return Configure(options => options.EnableRequestCaching = true); - } - - /// - /// Enables the revocation endpoint. - /// - /// The relative path of the revocation endpoint. - /// The . - 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); - } - - /// - /// Enables the token endpoint. - /// - /// The relative path of the token endpoint. - /// The . - 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); - } - - /// - /// Enables the userinfo endpoint. - /// - /// The relative path of the userinfo endpoint. - /// The . - 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); - } - - /// - /// 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. - /// - public virtual OpenIddictBuilder RequireClientIdentification() { - return Configure(options => options.RequireClientIdentification = true); - } - - /// - /// 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. - /// - /// The access token lifetime. - /// The . - public virtual OpenIddictBuilder SetAccessTokenLifetime(TimeSpan lifetime) { - return Configure(options => options.AccessTokenLifetime = lifetime); - } - - /// - /// 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. - /// - /// The authorization code lifetime. - /// The . - public virtual OpenIddictBuilder SetAuthorizationCodeLifetime(TimeSpan lifetime) { - return Configure(options => options.AuthorizationCodeLifetime = lifetime); - } - - /// - /// Sets the identity token lifetime, after which client - /// applications should refuse processing identity tokens. - /// - /// The identity token lifetime. - /// The . - public virtual OpenIddictBuilder SetIdentityTokenLifetime(TimeSpan lifetime) { - return Configure(options => options.IdentityTokenLifetime = lifetime); - } - - /// - /// 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. - /// - /// The refresh token lifetime. - /// The . - public virtual OpenIddictBuilder SetRefreshTokenLifetime(TimeSpan lifetime) { - return Configure(options => options.RefreshTokenLifetime = lifetime); - } - - /// - /// Configures OpenIddict to use a specific data protection provider - /// instead of relying on the default instance provided by the DI container. - /// - /// The data protection provider used to create token protectors. - /// The . - public virtual OpenIddictBuilder UseDataProtectionProvider(IDataProtectionProvider provider) { - if (provider == null) { - throw new ArgumentNullException(nameof(provider)); - } - - return Configure(options => options.DataProtectionProvider = provider); - } - - /// - /// Sets JWT as the default token format for access tokens. - /// - /// The . - public virtual OpenIddictBuilder UseJsonWebTokens() { - return Configure(options => options.AccessTokenHandler = new JwtSecurityTokenHandler()); - } } } \ No newline at end of file diff --git a/src/OpenIddict.Core/OpenIddictConstants.cs b/src/OpenIddict.Core/OpenIddictConstants.cs index f63314bc..2c340b1f 100644 --- a/src/OpenIddict.Core/OpenIddictConstants.cs +++ b/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"; diff --git a/src/OpenIddict.Core/OpenIddictExtensions.cs b/src/OpenIddict.Core/OpenIddictExtensions.cs index 8c7f2816..c6015012 100644 --- a/src/OpenIddict.Core/OpenIddictExtensions.cs +++ b/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 { /// - /// 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. + /// + /// The services collection. + /// The . + public static OpenIddictBuilder AddOpenIddict([NotNull] this IServiceCollection services) { + if (services == null) { + throw new ArgumentNullException(nameof(services)); + } + + return services.AddOpenIddict(); + } + + /// + /// Registers the default OpenIddict services in the DI container, + /// using the default entities and the specified entity key type. + /// + /// The type of the entity primary keys. + /// The services collection. + /// The . + public static OpenIddictBuilder AddOpenIddict([NotNull] this IServiceCollection services) + where TKey : IEquatable { + if (services == null) { + throw new ArgumentNullException(nameof(services)); + } + + return services.AddOpenIddict>, + OpenIddictAuthorization>, + OpenIddictScope, + OpenIddictToken>(); + } + + /// + /// Registers the default OpenIddict services in the DI container, using the specified entities. /// /// The type of the Application entity. /// The type of the Authorization entity. @@ -26,8 +54,7 @@ namespace Microsoft.AspNetCore.Builder { /// The type of the Token entity. /// The services collection. /// The . - public static OpenIddictBuilder AddOpenIddict( - [NotNull] this IServiceCollection services) + public static OpenIddictBuilder AddOpenIddict([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(); - }); - // Register the OpenIddict core services in the DI container. + builder.Services.TryAddSingleton(builder); builder.Services.TryAddScoped>(); builder.Services.TryAddScoped>(); builder.Services.TryAddScoped>(); builder.Services.TryAddScoped>(); - builder.Services.TryAddScoped>(); return builder; } - - /// - /// Registers OpenIddict in the ASP.NET Core pipeline. - /// - /// The application builder used to register middleware instances. - /// The . - 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>().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(); - - 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); - } - - /// - /// Determines whether the authorization code flow has been enabled. - /// - /// The OpenIddict options. - /// true if the authorization code flow has been enabled, false otherwise. - public static bool IsAuthorizationCodeFlowEnabled([NotNull] this OpenIddictOptions options) { - if (options == null) { - throw new ArgumentNullException(nameof(options)); - } - - return options.GrantTypes.Contains(OpenIdConnectConstants.GrantTypes.AuthorizationCode); - } - - /// - /// Determines whether the client credentials flow has been enabled. - /// - /// The OpenIddict options. - /// true if the client credentials flow has been enabled, false otherwise. - public static bool IsClientCredentialsFlowEnabled([NotNull] this OpenIddictOptions options) { - if (options == null) { - throw new ArgumentNullException(nameof(options)); - } - - return options.GrantTypes.Contains(OpenIdConnectConstants.GrantTypes.ClientCredentials); - } - - /// - /// Determines whether the implicit flow has been enabled. - /// - /// The OpenIddict options. - /// true if the implicit flow has been enabled, false otherwise. - public static bool IsImplicitFlowEnabled([NotNull] this OpenIddictOptions options) { - if (options == null) { - throw new ArgumentNullException(nameof(options)); - } - - return options.GrantTypes.Contains(OpenIdConnectConstants.GrantTypes.Implicit); - } - - /// - /// Determines whether the password flow has been enabled. - /// - /// The OpenIddict options. - /// true if the password flow has been enabled, false otherwise. - public static bool IsPasswordFlowEnabled([NotNull] this OpenIddictOptions options) { - if (options == null) { - throw new ArgumentNullException(nameof(options)); - } - - return options.GrantTypes.Contains(OpenIdConnectConstants.GrantTypes.Password); - } - - /// - /// Determines whether the refresh token flow has been enabled. - /// - /// The OpenIddict options. - /// true if the refresh token flow has been enabled, false otherwise. - public static bool IsRefreshTokenFlowEnabled([NotNull] this OpenIddictOptions options) { - if (options == null) { - throw new ArgumentNullException(nameof(options)); - } - - return options.GrantTypes.Contains(OpenIdConnectConstants.GrantTypes.RefreshToken); - } } } \ No newline at end of file diff --git a/src/OpenIddict.Core/Stores/IOpenIddictApplicationStore.cs b/src/OpenIddict.Core/Stores/IOpenIddictApplicationStore.cs index 474cb743..6c2cf396 100644 --- a/src/OpenIddict.Core/Stores/IOpenIddictApplicationStore.cs +++ b/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 { /// /// Provides methods allowing to manage the applications stored in a database. /// diff --git a/src/OpenIddict.Core/Stores/IOpenIddictAuthorizationStore.cs b/src/OpenIddict.Core/Stores/IOpenIddictAuthorizationStore.cs index f5be030c..9ba7a7dd 100644 --- a/src/OpenIddict.Core/Stores/IOpenIddictAuthorizationStore.cs +++ b/src/OpenIddict.Core/Stores/IOpenIddictAuthorizationStore.cs @@ -4,7 +4,7 @@ * the license and the contributors participating to this project. */ -namespace OpenIddict { +namespace OpenIddict.Core { /// /// Provides methods allowing to manage the authorizations stored in a database. /// diff --git a/src/OpenIddict.Core/Stores/IOpenIddictScopeStore.cs b/src/OpenIddict.Core/Stores/IOpenIddictScopeStore.cs index dbe4ba9c..ccb06a79 100644 --- a/src/OpenIddict.Core/Stores/IOpenIddictScopeStore.cs +++ b/src/OpenIddict.Core/Stores/IOpenIddictScopeStore.cs @@ -4,7 +4,7 @@ * the license and the contributors participating to this project. */ -namespace OpenIddict { +namespace OpenIddict.Core { /// /// Provides methods allowing to manage the scopes stored in a database. /// diff --git a/src/OpenIddict.Core/Stores/IOpenIddictTokenStore.cs b/src/OpenIddict.Core/Stores/IOpenIddictTokenStore.cs index f7f5868d..1cd5088d 100644 --- a/src/OpenIddict.Core/Stores/IOpenIddictTokenStore.cs +++ b/src/OpenIddict.Core/Stores/IOpenIddictTokenStore.cs @@ -7,7 +7,7 @@ using System.Threading; using System.Threading.Tasks; -namespace OpenIddict { +namespace OpenIddict.Core { /// /// Provides methods allowing to manage the tokens stored in a database. /// diff --git a/src/OpenIddict.Core/project.json b/src/OpenIddict.Core/project.json index 3c6b5819..0a482671 100644 --- a/src/OpenIddict.Core/project.json +++ b/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" ] } diff --git a/src/OpenIddict.EntityFramework/OpenIddict.EntityFramework.xproj b/src/OpenIddict.EntityFramework/OpenIddict.EntityFramework.xproj index 0871f0d1..260c0d74 100644 --- a/src/OpenIddict.EntityFramework/OpenIddict.EntityFramework.xproj +++ b/src/OpenIddict.EntityFramework/OpenIddict.EntityFramework.xproj @@ -7,7 +7,7 @@ d2450929-ed0e-420d-b475-327924f9701c - OpenIddict.EF + OpenIddict.EntityFramework .\obj .\bin\ diff --git a/src/OpenIddict.EntityFramework/OpenIddictCustomizer.cs b/src/OpenIddict.EntityFramework/OpenIddictCustomizer.cs new file mode 100644 index 00000000..52cf3a91 --- /dev/null +++ b/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 { + /// + /// Represents a model customizer able to register the entity sets + /// required by the OpenIddict stack in an Entity Framework context. + /// + public class OpenIddictCustomizer : ModelCustomizer + where TApplication : OpenIddictApplication + where TAuthorization : OpenIddictAuthorization + where TScope : OpenIddictScope + where TToken : OpenIddictToken + where TKey : IEquatable { + 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(); + + base.Customize(builder, context); + } + } +} \ No newline at end of file diff --git a/src/OpenIddict.EntityFramework/OpenIddictDbContext.cs b/src/OpenIddict.EntityFramework/OpenIddictDbContext.cs deleted file mode 100644 index 70be59fb..00000000 --- a/src/OpenIddict.EntityFramework/OpenIddictDbContext.cs +++ /dev/null @@ -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 { - /// - /// Represents an OpenIddict-powered Entity Framework context. - /// - public class OpenIddictDbContext : OpenIddictDbContext { - /// - /// Initializes a new OpenIddict context without configuring the Entity Framework options. - /// - protected OpenIddictDbContext() { } - - /// - /// Initializes a new OpenIddict context. - /// - /// The options used to configure the Entity Framework context. - public OpenIddictDbContext(DbContextOptions options) : base(options) { } - } - - /// - /// Represents an OpenIddict-powered Entity Framework context. - /// - /// The type of the User entity. - public class OpenIddictDbContext : OpenIddictDbContext - where TUser : IdentityUser { - /// - /// Initializes a new OpenIddict context without configuring the Entity Framework options. - /// - protected OpenIddictDbContext() { } - - /// - /// Initializes a new OpenIddict context. - /// - /// The options used to configure the Entity Framework context. - public OpenIddictDbContext(DbContextOptions options) : base(options) { } - } - - /// - /// Represents an OpenIddict-powered Entity Framework context. - /// - /// The type of the User entity. - /// The type of the Role entity. - public class OpenIddictDbContext : OpenIddictDbContext - where TUser : IdentityUser - where TRole : IdentityRole { - /// - /// Initializes a new OpenIddict context without configuring the Entity Framework options. - /// - protected OpenIddictDbContext() { } - - /// - /// Initializes a new OpenIddict context. - /// - /// The options used to configure the Entity Framework context. - public OpenIddictDbContext(DbContextOptions options) : base(options) { } - } - - /// - /// Represents an OpenIddict-powered Entity Framework context. - /// - /// The type of the User entity. - /// The type of the Role entity. - /// The type of the primary key used by the Identity/OpenIddict entities. - public class OpenIddictDbContext : OpenIddictDbContext, - OpenIddictAuthorization, - OpenIddictScope, - OpenIddictToken, TKey> - where TUser : IdentityUser - where TRole : IdentityRole - where TKey : IEquatable { - /// - /// Initializes a new OpenIddict context without configuring the Entity Framework options. - /// - protected OpenIddictDbContext() { } - - /// - /// Initializes a new OpenIddict context. - /// - /// The options used to configure the Entity Framework context. - public OpenIddictDbContext(DbContextOptions options) : base(options) { } - } - - /// - /// Represents an OpenIddict-powered Entity Framework context. - /// - /// The type of the User entity. - /// The type of the Role entity. - /// The type of the Application entity. - /// The type of the Authorization entity. - /// The type of the Scope entity. - /// The type of the Token entity. - /// The type of the primary key used by the Identity/OpenIddict entities. - public class OpenIddictDbContext : IdentityDbContext - where TUser : IdentityUser - where TRole : IdentityRole - where TApplication : OpenIddictApplication - where TAuthorization : OpenIddictAuthorization - where TScope : OpenIddictScope - where TToken : OpenIddictToken - where TKey : IEquatable { - /// - /// Initializes a new OpenIddict context without configuring the Entity Framework options. - /// - protected OpenIddictDbContext() { } - - /// - /// Initializes a new OpenIddict context. - /// - /// The options used to configure the Entity Framework context. - public OpenIddictDbContext(DbContextOptions options) : base(options) { } - - /// - /// Gets or sets the database set containing the applications. - /// - public DbSet Applications { get; set; } - - /// - /// Gets or sets the database set containing the authorizations. - /// - public DbSet Authorizations { get; set; } - - /// - /// Gets or sets the database set containing the scopes. - /// - public DbSet Scopes { get; set; } - - /// - /// Gets or sets the database set containing the tokens. - /// - public DbSet Tokens { get; set; } - - /// - /// Registers the OpenIddict entities in the Entity Framework context. - /// - /// The model builder used by Entity Framework. - 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(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(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(entity => { - entity.HasKey(scope => scope.Id); - - entity.ToTable("OpenIddictScopes"); - }); - - // Configure the TToken entity. - builder.Entity(entity => { - entity.HasKey(token => token.Id); - - entity.ToTable("OpenIddictTokens"); - }); - } - } -} \ No newline at end of file diff --git a/src/OpenIddict.EntityFramework/OpenIddictExtension.cs b/src/OpenIddict.EntityFramework/OpenIddictExtension.cs new file mode 100644 index 00000000..60a74cd4 --- /dev/null +++ b/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 : IDbContextOptionsExtension + where TApplication : OpenIddictApplication + where TAuthorization : OpenIddictAuthorization + where TScope : OpenIddictScope + where TToken : OpenIddictToken + where TKey : IEquatable { + public void ApplyServices(IServiceCollection services) { + if (services == null) { + throw new ArgumentNullException(nameof(services)); + } + + services.AddSingleton>(); + } + } +} diff --git a/src/OpenIddict.EntityFramework/OpenIddictExtensions.cs b/src/OpenIddict.EntityFramework/OpenIddictExtensions.cs index 4af8efb2..074a55e2 100644 --- a/src/OpenIddict.EntityFramework/OpenIddictExtensions.cs +++ b/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 { /// - /// 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. /// /// The services builder used by OpenIddict to register new services. /// The . - public static OpenIddictBuilder AddEntityFramework([NotNull] this OpenIddictBuilder builder) + public static OpenIddictBuilder AddEntityFrameworkStores([NotNull] this OpenIddictBuilder builder) where TContext : DbContext { - return builder.AddEntityFramework(); + return builder.AddEntityFrameworkStores(); } /// - /// 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. /// /// The services builder used by OpenIddict to register new services. /// The . - public static OpenIddictBuilder AddEntityFramework([NotNull] this OpenIddictBuilder builder) + public static OpenIddictBuilder AddEntityFrameworkStores([NotNull] this OpenIddictBuilder builder) where TContext : DbContext where TKey : IEquatable { if (builder == null) { @@ -77,5 +82,137 @@ namespace Microsoft.AspNetCore.Builder { return builder; } + + /// + /// Registers the OpenIddict entity sets in the Entity Framework context + /// using the default OpenIddict models and the default key type (string). + /// + /// The builder used to configure the Entity Framework context. + /// The Entity Framework context builder. + public static DbContextOptionsBuilder UseOpenIddict([NotNull] this DbContextOptionsBuilder builder) { + return builder.UseOpenIddict(); + } + + /// + /// Registers the OpenIddict entity sets in the Entity Framework context + /// using the default OpenIddict models and the specified key type. + /// + /// The builder used to configure the Entity Framework context. + /// The Entity Framework context builder. + public static DbContextOptionsBuilder UseOpenIddict([NotNull] this DbContextOptionsBuilder builder) where TKey : IEquatable { + return builder.UseOpenIddict>, + OpenIddictAuthorization>, + OpenIddictScope, + OpenIddictToken, TKey>(); + } + + /// + /// Registers the OpenIddict entity sets in the Entity Framework context + /// using the specified entities and the specified key type. + /// + /// The builder used to configure the Entity Framework context. + /// The Entity Framework context builder. + public static DbContextOptionsBuilder UseOpenIddict([NotNull] this DbContextOptionsBuilder builder) + where TApplication : OpenIddictApplication + where TAuthorization : OpenIddictAuthorization + where TScope : OpenIddictScope + where TToken : OpenIddictToken + where TKey : IEquatable { + if (builder == null) { + throw new ArgumentNullException(nameof(builder)); + } + + var extension = new OpenIddictExtension(); + ((IDbContextOptionsBuilderInfrastructure) builder).AddOrUpdateExtension(extension); + + return builder; + } + + /// + /// Registers the OpenIddict entity sets in the Entity Framework context + /// using the default OpenIddict models and the default key type (string). + /// + /// The builder used to configure the Entity Framework context. + /// The Entity Framework context builder. + public static ModelBuilder UseOpenIddict([NotNull] this ModelBuilder builder) { + return builder.UseOpenIddict(); + } + + /// + /// Registers the OpenIddict entity sets in the Entity Framework context + /// using the default OpenIddict models and the specified key type. + /// + /// The builder used to configure the Entity Framework context. + /// The Entity Framework context builder. + public static ModelBuilder UseOpenIddict([NotNull] this ModelBuilder builder) where TKey : IEquatable { + return builder.UseOpenIddict>, + OpenIddictAuthorization>, + OpenIddictScope, + OpenIddictToken, TKey>(); + } + + /// + /// Registers the OpenIddict entity sets in the Entity Framework context + /// using the specified entities and the specified key type. + /// + /// The builder used to configure the Entity Framework context. + /// The Entity Framework context builder. + public static ModelBuilder UseOpenIddict([NotNull] this ModelBuilder builder) + where TApplication : OpenIddictApplication + where TAuthorization : OpenIddictAuthorization + where TScope : OpenIddictScope + where TToken : OpenIddictToken + where TKey : IEquatable { + 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(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(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(entity => { + entity.HasKey(scope => scope.Id); + + entity.ToTable("OpenIddictScopes"); + }); + + // Configure the TToken entity. + builder.Entity(entity => { + entity.HasKey(token => token.Id); + + entity.ToTable("OpenIddictTokens"); + }); + + return builder; + } } } \ No newline at end of file diff --git a/src/OpenIddict.EntityFramework/Stores/OpenIddictApplicationStore.cs b/src/OpenIddict.EntityFramework/Stores/OpenIddictApplicationStore.cs index ffefd10a..49baa194 100644 --- a/src/OpenIddict.EntityFramework/Stores/OpenIddictApplicationStore.cs +++ b/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 { /// /// Provides methods allowing to manage the applications stored in a database. /// @@ -25,7 +28,11 @@ namespace OpenIddict { where TToken : OpenIddictToken, new() where TContext : DbContext where TKey : IEquatable { - public OpenIddictApplicationStore(TContext context) { + public OpenIddictApplicationStore([NotNull] TContext context) { + if (context == null) { + throw new ArgumentNullException(nameof(context)); + } + Context = context; } diff --git a/src/OpenIddict.EntityFramework/Stores/OpenIddictAuthorizationStore.cs b/src/OpenIddict.EntityFramework/Stores/OpenIddictAuthorizationStore.cs index 1b3eeef8..08c17fc5 100644 --- a/src/OpenIddict.EntityFramework/Stores/OpenIddictAuthorizationStore.cs +++ b/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 { /// /// Provides methods allowing to manage the authorizations stored in a database. /// @@ -20,7 +23,11 @@ namespace OpenIddict { where TToken : OpenIddictToken where TContext : DbContext where TKey : IEquatable { - public OpenIddictAuthorizationStore(TContext context) { + public OpenIddictAuthorizationStore([NotNull] TContext context) { + if (context == null) { + throw new ArgumentNullException(nameof(context)); + } + Context = context; } diff --git a/src/OpenIddict.EntityFramework/Stores/OpenIddictScopeStore.cs b/src/OpenIddict.EntityFramework/Stores/OpenIddictScopeStore.cs index 16586d11..f9c42c9d 100644 --- a/src/OpenIddict.EntityFramework/Stores/OpenIddictScopeStore.cs +++ b/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 { /// /// Provides methods allowing to manage the scopes stored in a database. /// @@ -18,7 +21,11 @@ namespace OpenIddict { where TScope : OpenIddictScope where TContext : DbContext where TKey : IEquatable { - public OpenIddictScopeStore(TContext context) { + public OpenIddictScopeStore([NotNull] TContext context) { + if (context == null) { + throw new ArgumentNullException(nameof(context)); + } + Context = context; } diff --git a/src/OpenIddict.EntityFramework/Stores/OpenIddictTokenStore.cs b/src/OpenIddict.EntityFramework/Stores/OpenIddictTokenStore.cs index e38aa8b5..7f1f2cf6 100644 --- a/src/OpenIddict.EntityFramework/Stores/OpenIddictTokenStore.cs +++ b/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 { /// /// Provides methods allowing to manage the tokens stored in a database. /// @@ -23,7 +26,11 @@ namespace OpenIddict { where TAuthorization : OpenIddictAuthorization where TContext : DbContext where TKey : IEquatable { - public OpenIddictTokenStore(TContext context) { + public OpenIddictTokenStore([NotNull] TContext context) { + if (context == null) { + throw new ArgumentNullException(nameof(context)); + } + Context = context; } diff --git a/src/OpenIddict.EntityFramework/project.json b/src/OpenIddict.EntityFramework/project.json index 8548da3c..0e0a7fc5 100644 --- a/src/OpenIddict.EntityFramework/project.json +++ b/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" diff --git a/src/OpenIddict.Models/OpenIddict.Models.xproj b/src/OpenIddict.Models/OpenIddict.Models.xproj new file mode 100644 index 00000000..6ac7b33f --- /dev/null +++ b/src/OpenIddict.Models/OpenIddict.Models.xproj @@ -0,0 +1,21 @@ + + + + 14.0 + $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) + + + + + 0102a6cc-41a6-4b34-b49e-65afe95882bb + OpenIddict.Models + .\obj + .\bin\ + v4.5.1 + + + + 2.0 + + + diff --git a/src/OpenIddict.EntityFramework/Models/OpenIddictApplication.cs b/src/OpenIddict.Models/OpenIddictApplication.cs similarity index 95% rename from src/OpenIddict.EntityFramework/Models/OpenIddictApplication.cs rename to src/OpenIddict.Models/OpenIddictApplication.cs index 5b22ba58..5eee5694 100644 --- a/src/OpenIddict.EntityFramework/Models/OpenIddictApplication.cs +++ b/src/OpenIddict.Models/OpenIddictApplication.cs @@ -7,7 +7,7 @@ using System; using System.Collections.Generic; -namespace OpenIddict { +namespace OpenIddict.Models { /// /// Represents an OpenIddict application. /// @@ -73,6 +73,6 @@ namespace OpenIddict { /// Gets or sets the application type /// associated with the current application. /// - public virtual string Type { get; set; } = OpenIddictConstants.ClientTypes.Public; + public virtual string Type { get; set; } = "public"; } } \ No newline at end of file diff --git a/src/OpenIddict.EntityFramework/Models/OpenIddictAuthorization.cs b/src/OpenIddict.Models/OpenIddictAuthorization.cs similarity index 98% rename from src/OpenIddict.EntityFramework/Models/OpenIddictAuthorization.cs rename to src/OpenIddict.Models/OpenIddictAuthorization.cs index bf111603..9837b05a 100644 --- a/src/OpenIddict.EntityFramework/Models/OpenIddictAuthorization.cs +++ b/src/OpenIddict.Models/OpenIddictAuthorization.cs @@ -7,7 +7,7 @@ using System; using System.Collections.Generic; -namespace OpenIddict { +namespace OpenIddict.Models { /// /// Represents an OpenIddict authorization. /// diff --git a/src/OpenIddict.EntityFramework/Models/OpenIddictScope.cs b/src/OpenIddict.Models/OpenIddictScope.cs similarity index 97% rename from src/OpenIddict.EntityFramework/Models/OpenIddictScope.cs rename to src/OpenIddict.Models/OpenIddictScope.cs index c56f54e9..dfa46cd2 100644 --- a/src/OpenIddict.EntityFramework/Models/OpenIddictScope.cs +++ b/src/OpenIddict.Models/OpenIddictScope.cs @@ -6,7 +6,7 @@ using System; -namespace OpenIddict { +namespace OpenIddict.Models { /// /// Represents an OpenIddict scope. /// diff --git a/src/OpenIddict.EntityFramework/Models/OpenIddictToken.cs b/src/OpenIddict.Models/OpenIddictToken.cs similarity index 97% rename from src/OpenIddict.EntityFramework/Models/OpenIddictToken.cs rename to src/OpenIddict.Models/OpenIddictToken.cs index 6b14f73e..e8a1976c 100644 --- a/src/OpenIddict.EntityFramework/Models/OpenIddictToken.cs +++ b/src/OpenIddict.Models/OpenIddictToken.cs @@ -6,7 +6,7 @@ using System; -namespace OpenIddict { +namespace OpenIddict.Models { /// /// Represents an OpenIddict token. /// diff --git a/src/OpenIddict.Models/project.json b/src/OpenIddict.Models/project.json new file mode 100644 index 00000000..fac1675b --- /dev/null +++ b/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": { } + } +} diff --git a/src/OpenIddict.Mvc/OpenIddictExtensions.cs b/src/OpenIddict.Mvc/OpenIddictExtensions.cs index c2cb4ff8..7787d437 100644 --- a/src/OpenIddict.Mvc/OpenIddictExtensions.cs +++ b/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 { /// /// Registers the ASP.NET Core MVC model binders used by OpenIddict. @@ -23,6 +22,14 @@ namespace Microsoft.AspNetCore.Builder { } builder.Services.Configure(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()); }); diff --git a/src/OpenIddict.Mvc/project.json b/src/OpenIddict.Mvc/project.json index 0ca37aef..4778f21b 100644 --- a/src/OpenIddict.Mvc/project.json +++ b/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" } diff --git a/src/OpenIddict/OpenIddictExtensions.cs b/src/OpenIddict/OpenIddictExtensions.cs index 17949eeb..937f82ff 100644 --- a/src/OpenIddict/OpenIddictExtensions.cs +++ b/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 { /// - /// 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. /// - /// The type of the Entity Framework database context. - /// The services collection. + /// The application builder used to register middleware instances. + /// The . + 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(); + 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>().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(); + + 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); + } + + /// + /// Amends the default OpenIddict configuration. + /// + /// The services builder used by OpenIddict to register new services. + /// The delegate used to configure the OpenIddict options. + /// This extension can be safely called multiple times. + /// The . + public static OpenIddictBuilder Configure( + [NotNull] this OpenIddictBuilder builder, + [NotNull] Action configuration) { + if (builder == null) { + throw new ArgumentNullException(nameof(builder)); + } + + if (configuration == null) { + throw new ArgumentNullException(nameof(configuration)); + } + + builder.Services.Configure(configuration); + + return builder; + } + + /// + /// 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. + /// + /// The services builder used by OpenIddict to register new services. + /// The . + public static OpenIddictBuilder AddEphemeralSigningKey([NotNull] this OpenIddictBuilder builder) { + if (builder == null) { + throw new ArgumentNullException(nameof(builder)); + } + + return builder.Configure(options => options.SigningCredentials.AddEphemeralKey()); + } + + /// + /// 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. + /// + /// The services builder used by OpenIddict to register new services. + /// The algorithm associated with the signing key. + /// The . + 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)); + } + + /// + /// Registers a that is used to sign the tokens issued by OpenIddict. + /// + /// The services builder used by OpenIddict to register new services. + /// The certificate used to sign the security tokens issued by the server. + /// The . + 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)); + } + + /// + /// Registers a retrieved from an + /// embedded resource and used to sign the tokens issued by OpenIddict. + /// + /// The services builder used by OpenIddict to register new services. + /// The assembly containing the certificate. + /// The name of the embedded resource. + /// The password used to open the certificate. + /// The . + 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)); + } + + /// + /// Registers a extracted from a + /// stream and used to sign the tokens issued by OpenIddict. + /// + /// The services builder used by OpenIddict to register new services. + /// The stream containing the certificate. + /// The password used to open the certificate. + /// The . + 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)); + } + + /// + /// Registers a extracted from a + /// stream and used to sign the tokens issued by OpenIddict. + /// + /// The services builder used by OpenIddict to register new services. + /// The stream containing the certificate. + /// The password used to open the certificate. + /// + /// An enumeration of flags indicating how and where + /// to store the private key of the certificate. + /// + /// The . + 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)); + } + + /// + /// Registers a retrieved from the X.509 + /// machine store and used to sign the tokens issued by OpenIddict. + /// + /// The services builder used by OpenIddict to register new services. + /// The thumbprint of the certificate used to identify it in the X.509 store. + /// The . + 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)); + } + + /// + /// Registers a retrieved from the given + /// X.509 store and used to sign the tokens issued by OpenIddict. + /// + /// The services builder used by OpenIddict to register new services. + /// The thumbprint of the certificate used to identify it in the X.509 store. + /// The name of the X.509 store. + /// The location of the X.509 store. + /// The . + 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)); + } + + /// + /// Registers a used to sign the tokens issued by OpenIddict. + /// Note: using asymmetric keys is recommended on production. + /// + /// The services builder used by OpenIddict to register new services. + /// The security key. + /// The . + 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)); + } + + /// + /// 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. + /// + /// The services builder used by OpenIddict to register new services. + /// The . + 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)); + } + + /// + /// Enables client credentials flow support. For more information about this + /// specific OAuth2 flow, visit https://tools.ietf.org/html/rfc6749#section-4.4. + /// + /// The services builder used by OpenIddict to register new services. + /// The . + 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)); + } + + /// + /// Enables custom grant type support. + /// + /// The services builder used by OpenIddict to register new services. + /// The grant type associated with the flow. + /// The . + 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)); + } + + /// + /// 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. + /// + /// The services builder used by OpenIddict to register new services. + /// The . + 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)); + } + + /// + /// Enables password flow support. For more information about this specific + /// OAuth2 flow, visit https://tools.ietf.org/html/rfc6749#section-4.3. + /// + /// The services builder used by OpenIddict to register new services. + /// The . + 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)); + } + + /// + /// Enables refresh token flow support. For more information about this + /// specific OAuth2 flow, visit https://tools.ietf.org/html/rfc6749#section-6. + /// + /// The services builder used by OpenIddict to register new services. + /// The . + 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)); + } + + /// + /// Disables the configuration endpoint. + /// + /// The services builder used by OpenIddict to register new services. + /// The . + public static OpenIddictBuilder DisableConfigurationEndpoint([NotNull] this OpenIddictBuilder builder) { + if (builder == null) { + throw new ArgumentNullException(nameof(builder)); + } + + return builder.Configure(options => options.ConfigurationEndpointPath = PathString.Empty); + } + + /// + /// Disables the cryptography endpoint. + /// + /// The services builder used by OpenIddict to register new services. + /// The . + public static OpenIddictBuilder DisableCryptographyEndpoint([NotNull] this OpenIddictBuilder builder) { + if (builder == null) { + throw new ArgumentNullException(nameof(builder)); + } + + return builder.Configure(options => options.CryptographyEndpointPath = PathString.Empty); + } + + /// + /// Disables the HTTPS requirement during development. + /// + /// The services builder used by OpenIddict to register new services. + /// The . + public static OpenIddictBuilder DisableHttpsRequirement([NotNull] this OpenIddictBuilder builder) { + if (builder == null) { + throw new ArgumentNullException(nameof(builder)); + } + + return builder.Configure(options => options.AllowInsecureHttp = true); + } + + /// + /// Disables sliding expiration, which prevents OpenIddict from issuing a new + /// refresh token when receiving a grant_type=refresh_token token request. + /// + /// The services builder used by OpenIddict to register new services. + /// The . + public static OpenIddictBuilder DisableSlidingExpiration([NotNull] this OpenIddictBuilder builder) { + if (builder == null) { + throw new ArgumentNullException(nameof(builder)); + } + + return builder.Configure(options => options.UseSlidingExpiration = false); + } + + /// + /// Enables the authorization endpoint. + /// + /// The services builder used by OpenIddict to register new services. + /// The relative path of the authorization endpoint. /// The . - public static OpenIddictBuilder AddOpenIddict([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(); + return builder.Configure(options => options.AuthorizationEndpointPath = path); } /// - /// Registers the default OpenIddict services in the DI container, - /// including the Entity Framework stores and the specified entities. + /// Enables the introspection endpoint. /// - /// The type of the Entity Framework database context. - /// The type of the entity primary keys. - /// The services collection. + /// The services builder used by OpenIddict to register new services. + /// The relative path of the logout endpoint. /// The . - public static OpenIddictBuilder AddOpenIddict([NotNull] this IServiceCollection services) - where TContext : DbContext - where TKey : IEquatable { - 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); + } + + /// + /// Enables the logout endpoint. + /// + /// The services builder used by OpenIddict to register new services. + /// The relative path of the logout endpoint. + /// The . + 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); + } + + /// + /// 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. + /// + /// The services builder used by OpenIddict to register new services. + /// The . + public static OpenIddictBuilder EnableRequestCaching([NotNull] this OpenIddictBuilder builder) { + if (builder == null) { + throw new ArgumentNullException(nameof(builder)); + } + + return builder.Configure(options => options.EnableRequestCaching = true); + } + + /// + /// Enables the revocation endpoint. + /// + /// The services builder used by OpenIddict to register new services. + /// The relative path of the revocation endpoint. + /// The . + 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); + } + + /// + /// Enables the token endpoint. + /// + /// The services builder used by OpenIddict to register new services. + /// The relative path of the token endpoint. + /// The . + 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); + } + + /// + /// Enables the userinfo endpoint. + /// + /// The services builder used by OpenIddict to register new services. + /// The relative path of the userinfo endpoint. + /// The . + 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); + } + + /// + /// 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. + /// + /// The services builder used by OpenIddict to register new services. + public static OpenIddictBuilder RequireClientIdentification([NotNull] this OpenIddictBuilder builder) { + if (builder == null) { + throw new ArgumentNullException(nameof(builder)); + } + + return builder.Configure(options => options.RequireClientIdentification = true); + } + + /// + /// 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. + /// + /// The services builder used by OpenIddict to register new services. + /// The access token lifetime. + /// The . + 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); + } + + /// + /// 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. + /// + /// The services builder used by OpenIddict to register new services. + /// The authorization code lifetime. + /// The . + 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); + } + + /// + /// Sets the identity token lifetime, after which client + /// applications should refuse processing identity tokens. + /// + /// The services builder used by OpenIddict to register new services. + /// The identity token lifetime. + /// The . + 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); + } + + /// + /// 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. + /// + /// The services builder used by OpenIddict to register new services. + /// The refresh token lifetime. + /// The . + 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); + } + + /// + /// Configures OpenIddict to use a specific data protection provider + /// instead of relying on the default instance provided by the DI container. + /// + /// The services builder used by OpenIddict to register new services. + /// The data protection provider used to create token protectors. + /// The . + 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); + } + + /// + /// Sets JWT as the default token format for access tokens. + /// + /// The services builder used by OpenIddict to register new services. + /// The . + public static OpenIddictBuilder UseJsonWebTokens([NotNull] this OpenIddictBuilder builder) { + if (builder == null) { + throw new ArgumentNullException(nameof(builder)); + } + + return builder.Configure(options => options.AccessTokenHandler = new JwtSecurityTokenHandler()); + } + + /// + /// Determines whether the authorization code flow has been enabled. + /// + /// The OpenIddict options. + /// true if the authorization code flow has been enabled, false otherwise. + public static bool IsAuthorizationCodeFlowEnabled([NotNull] this OpenIddictOptions options) { + if (options == null) { + throw new ArgumentNullException(nameof(options)); + } + + return options.GrantTypes.Contains(OpenIdConnectConstants.GrantTypes.AuthorizationCode); + } + + /// + /// Determines whether the client credentials flow has been enabled. + /// + /// The OpenIddict options. + /// true if the client credentials flow has been enabled, false otherwise. + public static bool IsClientCredentialsFlowEnabled([NotNull] this OpenIddictOptions options) { + if (options == null) { + throw new ArgumentNullException(nameof(options)); + } + + return options.GrantTypes.Contains(OpenIdConnectConstants.GrantTypes.ClientCredentials); + } + + /// + /// Determines whether the implicit flow has been enabled. + /// + /// The OpenIddict options. + /// true if the implicit flow has been enabled, false otherwise. + public static bool IsImplicitFlowEnabled([NotNull] this OpenIddictOptions options) { + if (options == null) { + throw new ArgumentNullException(nameof(options)); + } + + return options.GrantTypes.Contains(OpenIdConnectConstants.GrantTypes.Implicit); + } + + /// + /// Determines whether the password flow has been enabled. + /// + /// The OpenIddict options. + /// true if the password flow has been enabled, false otherwise. + public static bool IsPasswordFlowEnabled([NotNull] this OpenIddictOptions options) { + if (options == null) { + throw new ArgumentNullException(nameof(options)); } - return services.AddOpenIddict, - OpenIddictAuthorization, - OpenIddictScope, - OpenIddictToken, TContext, TKey>(); + return options.GrantTypes.Contains(OpenIdConnectConstants.GrantTypes.Password); } /// - /// 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. /// - /// The type of the Application entity. - /// The type of the Authorization entity. - /// The type of the Scope entity. - /// The type of the Token entity. - /// The type of the Entity Framework database context. - /// The type of the entity primary keys. - /// The services collection. - /// The . - public static OpenIddictBuilder AddOpenIddict( - [NotNull] this IServiceCollection services) - where TApplication : OpenIddictApplication - where TAuthorization : OpenIddictAuthorization - where TScope : OpenIddictScope - where TToken : OpenIddictToken - where TContext : DbContext - where TKey : IEquatable { - if (services == null) { - throw new ArgumentNullException(nameof(services)); + /// The OpenIddict options. + /// true if the refresh token flow has been enabled, false otherwise. + 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() - .AddEntityFramework(); + return options.GrantTypes.Contains(OpenIdConnectConstants.GrantTypes.RefreshToken); } } } \ No newline at end of file diff --git a/src/OpenIddict.Core/OpenIddictOptions.cs b/src/OpenIddict/OpenIddictOptions.cs similarity index 98% rename from src/OpenIddict.Core/OpenIddictOptions.cs rename to src/OpenIddict/OpenIddictOptions.cs index 8d570bea..59a7c218 100644 --- a/src/OpenIddict.Core/OpenIddictOptions.cs +++ b/src/OpenIddict/OpenIddictOptions.cs @@ -15,6 +15,8 @@ namespace OpenIddict { /// 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); diff --git a/src/OpenIddict.Core/Infrastructure/OpenIddictProvider.Authentication.cs b/src/OpenIddict/OpenIddictProvider.Authentication.cs similarity index 75% rename from src/OpenIddict.Core/Infrastructure/OpenIddictProvider.Authentication.cs rename to src/OpenIddict/OpenIddictProvider.Authentication.cs index 5bd2ac44..71473c30 100644 --- a/src/OpenIddict.Core/Infrastructure/OpenIddictProvider.Authentication.cs +++ b/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 : 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>(); + var logger = context.HttpContext.RequestServices.GetRequiredService>>(); + var options = context.HttpContext.RequestServices.GetRequiredService>(); // 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>(); + var applications = context.HttpContext.RequestServices.GetRequiredService>(); + var logger = context.HttpContext.RequestServices.GetRequiredService>>(); + var options = context.HttpContext.RequestServices.GetRequiredService>(); // 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>(); + var options = context.HttpContext.RequestServices.GetRequiredService>(); // 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>(); + var options = context.HttpContext.RequestServices.GetRequiredService>(); // 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. diff --git a/src/OpenIddict.Core/Infrastructure/OpenIddictProvider.Discovery.cs b/src/OpenIddict/OpenIddictProvider.Discovery.cs similarity index 88% rename from src/OpenIddict.Core/Infrastructure/OpenIddictProvider.Discovery.cs rename to src/OpenIddict/OpenIddictProvider.Discovery.cs index f6d36344..43dffe56 100644 --- a/src/OpenIddict.Core/Infrastructure/OpenIddictProvider.Discovery.cs +++ b/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 : 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>(); + var options = context.HttpContext.RequestServices.GetRequiredService>(); // 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); } diff --git a/src/OpenIddict.Core/Infrastructure/OpenIddictProvider.Exchange.cs b/src/OpenIddict/OpenIddictProvider.Exchange.cs similarity index 71% rename from src/OpenIddict.Core/Infrastructure/OpenIddictProvider.Exchange.cs rename to src/OpenIddict/OpenIddictProvider.Exchange.cs index d4840607..6d51b643 100644 --- a/src/OpenIddict.Core/Infrastructure/OpenIddictProvider.Exchange.cs +++ b/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 : 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>(); + var applications = context.HttpContext.RequestServices.GetRequiredService>(); + var logger = context.HttpContext.RequestServices.GetRequiredService>>(); + var options = context.HttpContext.RequestServices.GetRequiredService>(); // 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>(); + var logger = context.HttpContext.RequestServices.GetRequiredService>>(); + var tokens = context.HttpContext.RequestServices.GetRequiredService>(); 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); } } diff --git a/src/OpenIddict.Core/Infrastructure/OpenIddictProvider.Introspection.cs b/src/OpenIddict/OpenIddictProvider.Introspection.cs similarity index 69% rename from src/OpenIddict.Core/Infrastructure/OpenIddictProvider.Introspection.cs rename to src/OpenIddict/OpenIddictProvider.Introspection.cs index 4eae8c3b..31253c66 100644 --- a/src/OpenIddict.Core/Infrastructure/OpenIddictProvider.Introspection.cs +++ b/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 : 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>(); + var applications = context.HttpContext.RequestServices.GetRequiredService>(); + var logger = context.HttpContext.RequestServices.GetRequiredService>>(); // 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>(); + var logger = context.HttpContext.RequestServices.GetRequiredService>>(); + var tokens = context.HttpContext.RequestServices.GetRequiredService>(); 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; diff --git a/src/OpenIddict.Core/Infrastructure/OpenIddictProvider.Revocation.cs b/src/OpenIddict/OpenIddictProvider.Revocation.cs similarity index 72% rename from src/OpenIddict.Core/Infrastructure/OpenIddictProvider.Revocation.cs rename to src/OpenIddict/OpenIddictProvider.Revocation.cs index 8ff12c43..84eaab89 100644 --- a/src/OpenIddict.Core/Infrastructure/OpenIddictProvider.Revocation.cs +++ b/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 : 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>(); + var applications = context.HttpContext.RequestServices.GetRequiredService>(); + var logger = context.HttpContext.RequestServices.GetRequiredService>>(); + var options = context.HttpContext.RequestServices.GetRequiredService>(); // 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>(); + var logger = context.HttpContext.RequestServices.GetRequiredService>>(); + var tokens = context.HttpContext.RequestServices.GetRequiredService>(); 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; } diff --git a/src/OpenIddict.Core/Infrastructure/OpenIddictProvider.Serialization.cs b/src/OpenIddict/OpenIddictProvider.Serialization.cs similarity index 76% rename from src/OpenIddict.Core/Infrastructure/OpenIddictProvider.Serialization.cs rename to src/OpenIddict/OpenIddictProvider.Serialization.cs index 6ded3da3..0fcb6141 100644 --- a/src/OpenIddict.Core/Infrastructure/OpenIddictProvider.Serialization.cs +++ b/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 : 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>(); + var tokens = context.HttpContext.RequestServices.GetRequiredService>(); - 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>(); + var tokens = context.HttpContext.RequestServices.GetRequiredService>(); - 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."); } diff --git a/src/OpenIddict.Core/Infrastructure/OpenIddictProvider.Session.cs b/src/OpenIddict/OpenIddictProvider.Session.cs similarity index 73% rename from src/OpenIddict.Core/Infrastructure/OpenIddictProvider.Session.cs rename to src/OpenIddict/OpenIddictProvider.Session.cs index a5293da3..497254d7 100644 --- a/src/OpenIddict.Core/Infrastructure/OpenIddictProvider.Session.cs +++ b/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 : 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>(); + var logger = context.HttpContext.RequestServices.GetRequiredService>>(); + var options = context.HttpContext.RequestServices.GetRequiredService>(); // 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>(); + var applications = context.HttpContext.RequestServices.GetRequiredService>(); + var logger = context.HttpContext.RequestServices.GetRequiredService>>(); // 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>(); + var options = context.HttpContext.RequestServices.GetRequiredService>(); // 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>(); + var options = context.HttpContext.RequestServices.GetRequiredService>(); // 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. diff --git a/src/OpenIddict.Core/Infrastructure/OpenIddictProvider.Userinfo.cs b/src/OpenIddict/OpenIddictProvider.Userinfo.cs similarity index 95% rename from src/OpenIddict.Core/Infrastructure/OpenIddictProvider.Userinfo.cs rename to src/OpenIddict/OpenIddictProvider.Userinfo.cs index 40097aa7..2e764992 100644 --- a/src/OpenIddict.Core/Infrastructure/OpenIddictProvider.Userinfo.cs +++ b/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 : OpenIdConnectServerProvider where TApplication : class where TAuthorization : class where TScope : class where TToken : class { public override Task HandleUserinfoRequest([NotNull] HandleUserinfoRequestContext context) { diff --git a/src/OpenIddict.Core/Infrastructure/OpenIddictProvider.cs b/src/OpenIddict/OpenIddictProvider.cs similarity index 85% rename from src/OpenIddict.Core/Infrastructure/OpenIddictProvider.cs rename to src/OpenIddict/OpenIddictProvider.cs index 767db7c9..16f7de39 100644 --- a/src/OpenIddict.Core/Infrastructure/OpenIddictProvider.cs +++ b/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 : OpenIdConnectServerProvider where TApplication : class where TAuthorization : class where TScope : class where TToken : class { // Note: this class is split into specialized partial classes. diff --git a/src/OpenIddict/project.json b/src/OpenIddict/project.json index 607192cd..ff9802c9 100644 --- a/src/OpenIddict/project.json +++ b/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": { diff --git a/test/OpenIddict.Core.Tests/OpenIddictBuilderTests.cs b/test/OpenIddict.Core.Tests/OpenIddictBuilderTests.cs index 8e4c9a3d..94ba7f7a 100644 --- a/test/OpenIddict.Core.Tests/OpenIddictBuilderTests.cs +++ b/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>(); - - // 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>( - Mock.Of(), Mock.Of>(), Mock.Of>>()).Object.GetType(); @@ -132,7 +105,6 @@ namespace OpenIddict.Core.Tests { builder.AuthorizationType = typeof(object); var type = new Mock>( - Mock.Of(), Mock.Of>(), Mock.Of>>()).Object.GetType(); @@ -207,7 +179,6 @@ namespace OpenIddict.Core.Tests { builder.ScopeType = typeof(object); var type = new Mock>( - Mock.Of(), Mock.Of>(), Mock.Of>>()).Object.GetType(); @@ -282,7 +253,6 @@ namespace OpenIddict.Core.Tests { builder.TokenType = typeof(object); var type = new Mock>( - Mock.Of(), Mock.Of>(), Mock.Of>>()).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>(); - - // 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>(); - 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(mock => - mock.IsSupportedAlgorithm(algorithm, It.IsAny())); - - var key = Mock.Of(mock => mock.CryptoProviderFactory == factory); - - // Act - builder.AddSigningKey(key); - - var provider = services.BuildServiceProvider(); - var options = provider.GetRequiredService>(); - - // 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>(); - - // 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>(); - - // 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>(); - - // 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>(); - - // 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>(); - - // 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>(); - - // 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>(); - - // 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>(); - - // 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>(); - - // 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>(); - - // 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>(); - - // 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>(); - - // 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>(); - - // 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>(); - - // 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>(); - - // 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>(); - - // 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>(); - - // 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>(); - - // 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>(); - - // 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>(); - - // 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>(); - - // 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>(); - - // 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>(); - - // Assert - Assert.IsType(typeof(JwtSecurityTokenHandler), options.Value.AccessTokenHandler); - } } } diff --git a/test/OpenIddict.Core.Tests/OpenIddictExtensionsTests.cs b/test/OpenIddict.Core.Tests/OpenIddictExtensionsTests.cs index 2c5ac5bd..120431e8 100644 --- a/test/OpenIddict.Core.Tests/OpenIddictExtensionsTests.cs +++ b/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))] + [InlineData(typeof(OpenIddictAuthorizationManager))] + [InlineData(typeof(OpenIddictScopeManager))] + [InlineData(typeof(OpenIddictTokenManager))] + public void AddOpenIddict_KeyTypeDefaultsToString(Type type) { // Arrange var services = new ServiceCollection(); // Act - services.AddOpenIddict(); - - var provider = services.BuildServiceProvider(); - var options = provider.GetRequiredService>(); + services.AddOpenIddict(); // Assert - Assert.IsType(typeof(OpenIddictProvider), options.Value.Provider); + Assert.Contains(services, service => service.ImplementationType == type); } [Theory] - [InlineData(typeof(IDataProtectionProvider))] - [InlineData(typeof(OpenIddictApplicationManager))] - [InlineData(typeof(OpenIddictAuthorizationManager))] - [InlineData(typeof(OpenIddictScopeManager))] - [InlineData(typeof(OpenIddictTokenManager))] - [InlineData(typeof(OpenIddictServices))] - public void AddOpenIddict_RegistersCoreServices(Type type) { + [InlineData(typeof(OpenIddictApplicationManager>>))] + [InlineData(typeof(OpenIddictAuthorizationManager>>))] + [InlineData(typeof(OpenIddictScopeManager>))] + [InlineData(typeof(OpenIddictTokenManager>))] + public void AddOpenIddict_KeyTypeCanBeOverriden(Type type) { // Arrange var services = new ServiceCollection(); // Act - services.AddOpenIddict(); + services.AddOpenIddict(); // Assert - Assert.Contains(services, service => service.ServiceType == type); - } - - [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(() => 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(() => 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(() => 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() - .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(() => 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(() => 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))] + [InlineData(typeof(OpenIddictAuthorizationManager))] + [InlineData(typeof(OpenIddictScopeManager))] + [InlineData(typeof(OpenIddictTokenManager))] + public void AddOpenIddict_DefaultEntitiesCanBeReplaced(Type type) { // Arrange var services = new ServiceCollection(); - services.AddOpenIddict() - .AddEphemeralSigningKey() - .AllowImplicitFlow() - .EnableAuthorizationEndpoint("/connect/authorize"); - - var builder = new Mock(); - builder.SetupGet(mock => mock.ApplicationServices) - .Returns(services.BuildServiceProvider()); - // Act - builder.Object.UseOpenIddict(); + services.AddOpenIddict(); // Assert - builder.Verify(mock => mock.Use(It.IsAny>()), 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); } } } diff --git a/test/OpenIddict.Core.Tests/project.json b/test/OpenIddict.Core.Tests/project.json index 172029df..3f4bd63a 100644 --- a/test/OpenIddict.Core.Tests/project.json +++ b/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" } }, diff --git a/test/OpenIddict.EntityFramework.Tests/OpenIddictExtensionsTests.cs b/test/OpenIddict.EntityFramework.Tests/OpenIddictExtensionsTests.cs index 750409e4..60ae34a5 100644 --- a/test/OpenIddict.EntityFramework.Tests/OpenIddictExtensionsTests.cs +++ b/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, OpenIddictToken, OpenIddictDbContext, Guid>))] - [InlineData(typeof(OpenIddictAuthorizationStore, OpenIddictToken, OpenIddictDbContext, Guid>))] - [InlineData(typeof(OpenIddictScopeStore, OpenIddictDbContext, Guid>))] - [InlineData(typeof(OpenIddictTokenStore, OpenIddictAuthorization, OpenIddictDbContext, Guid>))] - public void AddEntityFramework_RegistersEntityFrameworkStores(Type type) { + [InlineData(typeof(OpenIddictApplicationStore>, OpenIddictToken, DbContext, Guid>))] + [InlineData(typeof(OpenIddictAuthorizationStore>, OpenIddictToken, DbContext, Guid>))] + [InlineData(typeof(OpenIddictScopeStore, DbContext, Guid>))] + [InlineData(typeof(OpenIddictTokenStore, OpenIddictAuthorization>, DbContext, Guid>))] + public void AddEntityFrameworkStores_RegistersEntityFrameworkStores(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(); + services.AddOpenIddict() + .AddEntityFrameworkStores(); // Assert Assert.Contains(services, service => service.ImplementationType == type); } [Theory] - [InlineData(typeof(OpenIddictApplicationStore))] - [InlineData(typeof(OpenIddictAuthorizationStore))] - [InlineData(typeof(OpenIddictScopeStore))] - [InlineData(typeof(OpenIddictTokenStore))] - public void AddEntityFramework_KeyTypeDefaultsToString(Type type) { + [InlineData(typeof(OpenIddictApplicationStore))] + [InlineData(typeof(OpenIddictAuthorizationStore))] + [InlineData(typeof(OpenIddictScopeStore))] + [InlineData(typeof(OpenIddictTokenStore))] + 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(); + services.AddOpenIddict() + .AddEntityFrameworkStores(); // Assert Assert.Contains(services, service => service.ImplementationType == type); diff --git a/test/OpenIddict.EntityFramework.Tests/project.json b/test/OpenIddict.EntityFramework.Tests/project.json index c2bfcfb7..fdb05fe5 100644 --- a/test/OpenIddict.EntityFramework.Tests/project.json +++ b/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" diff --git a/test/OpenIddict.Mvc.Tests/OpenIddictExtensionsTests.cs b/test/OpenIddict.Mvc.Tests/OpenIddictExtensionsTests.cs index 4a9199dd..9910dcf1 100644 --- a/test/OpenIddict.Mvc.Tests/OpenIddictExtensionsTests.cs +++ b/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; diff --git a/test/OpenIddict.Mvc.Tests/project.json b/test/OpenIddict.Mvc.Tests/project.json index 531f4ba3..ee355812 100644 --- a/test/OpenIddict.Mvc.Tests/project.json +++ b/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" diff --git a/test/OpenIddict.Core.Tests/Certificate.pfx b/test/OpenIddict.Tests/Certificate.pfx similarity index 100% rename from test/OpenIddict.Core.Tests/Certificate.pfx rename to test/OpenIddict.Tests/Certificate.pfx diff --git a/test/OpenIddict.Tests/OpenIddictBuilderTests.cs b/test/OpenIddict.Tests/OpenIddictBuilderTests.cs new file mode 100644 index 00000000..c3f17b50 --- /dev/null +++ b/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>(); + + // 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>(); + + // 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>(); + 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(mock => + mock.IsSupportedAlgorithm(algorithm, It.IsAny())); + + var key = Mock.Of(mock => mock.CryptoProviderFactory == factory); + + // Act + builder.AddSigningKey(key); + + var provider = services.BuildServiceProvider(); + var options = provider.GetRequiredService>(); + + // 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>(); + + // 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>(); + + // 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>(); + + // 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>(); + + // 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>(); + + // 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>(); + + // 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>(); + + // 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>(); + + // 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>(); + + // 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>(); + + // 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>(); + + // 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>(); + + // 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>(); + + // 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>(); + + // 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>(); + + // 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>(); + + // 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>(); + + // 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>(); + + // 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>(); + + // 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>(); + + // 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>(); + + // 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>(); + + // 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>(); + + // Assert + Assert.IsType(typeof(JwtSecurityTokenHandler), options.Value.AccessTokenHandler); + } + } +} diff --git a/test/OpenIddict.Tests/OpenIddictExtensionsTests.cs b/test/OpenIddict.Tests/OpenIddictExtensionsTests.cs index 3454f031..bf7dd4af 100644 --- a/test/OpenIddict.Tests/OpenIddictExtensionsTests.cs +++ b/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(() => 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(() => 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(() => 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(() => builder.UseOpenIddict()); + + Assert.Equal("At least one OAuth2/OpenID Connect flow must be enabled.", exception.Message); + } + [Theory] - [InlineData(typeof(OpenIddictApplicationManager>))] - [InlineData(typeof(OpenIddictAuthorizationManager>))] - [InlineData(typeof(OpenIddictScopeManager>))] - [InlineData(typeof(OpenIddictTokenManager>))] - 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(() => 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(() => 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>(); + + // 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(); + builder.SetupGet(mock => mock.ApplicationServices) + .Returns(services.BuildServiceProvider()); + + // Act + builder.Object.UseOpenIddict(); + + // Assert + builder.Verify(mock => mock.Use(It.IsAny>()), Times.Once()); + } + + [Fact] + public void AddEphemeralSigningKey_SigningKeyIsCorrectlyAdded() { // Arrange var services = new ServiceCollection(); + services.AddOptions(); + + var builder = new OpenIddictBuilder(services); // Act - services.AddOpenIddict(); + builder.AddEphemeralSigningKey(); + + var provider = services.BuildServiceProvider(); + var options = provider.GetRequiredService>(); // Assert - Assert.Contains(services, service => service.ImplementationType == type); + Assert.Equal(1, options.Value.SigningCredentials.Count); } [Theory] - [InlineData(typeof(OpenIddictApplicationStore, OpenIddictToken, OpenIddictDbContext, Guid>))] - [InlineData(typeof(OpenIddictAuthorizationStore, OpenIddictToken, OpenIddictDbContext, Guid>))] - [InlineData(typeof(OpenIddictScopeStore, OpenIddictDbContext, Guid>))] - [InlineData(typeof(OpenIddictTokenStore, OpenIddictAuthorization, 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(); + builder.AddEphemeralSigningKey(algorithm); + + var provider = services.BuildServiceProvider(); + var options = provider.GetRequiredService>(); + var credentials = options.Value.SigningCredentials[0]; // Assert - Assert.Contains(services, service => service.ImplementationType == type); + Assert.Equal(algorithm, credentials.Algorithm); } [Theory] - [InlineData(typeof(OpenIddictApplicationManager))] - [InlineData(typeof(OpenIddictAuthorizationManager))] - [InlineData(typeof(OpenIddictScopeManager))] - [InlineData(typeof(OpenIddictTokenManager))] - [InlineData(typeof(OpenIddictApplicationStore))] - [InlineData(typeof(OpenIddictAuthorizationStore))] - [InlineData(typeof(OpenIddictScopeStore))] - [InlineData(typeof(OpenIddictTokenStore))] - 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(mock => + mock.IsSupportedAlgorithm(algorithm, It.IsAny())); + + var key = Mock.Of(mock => mock.CryptoProviderFactory == factory); + + // Act + builder.AddSigningKey(key); + + var provider = services.BuildServiceProvider(); + var options = provider.GetRequiredService>(); + + // 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>(); + + // 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>(); + + // 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>(); + + // 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>(); + + // 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>(); + + // 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>(); + + // 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>(); + + // 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(); + builder.DisableConfigurationEndpoint(); + + var provider = services.BuildServiceProvider(); + var options = provider.GetRequiredService>(); // 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>(); + + // 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>(); + + // 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>(); + + // 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>(); + + // 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>(); + + // 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>(); + + // 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>(); + + // 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>(); + + // 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>(); + + // 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>(); + + // 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>(); + + // 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>(); + + // 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>(); + + // 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>(); + + // 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>(); + + // 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()); } } } diff --git a/test/OpenIddict.Core.Tests/Infrastructure/OpenIddictProviderTests.Authentication.cs b/test/OpenIddict.Tests/OpenIddictProviderTests.Authentication.cs similarity index 91% rename from test/OpenIddict.Core.Tests/Infrastructure/OpenIddictProviderTests.Authentication.cs rename to test/OpenIddict.Tests/OpenIddictProviderTests.Authentication.cs index d2dfdec9..8397a096 100644 --- a/test/OpenIddict.Core.Tests/Infrastructure/OpenIddictProviderTests.Authentication.cs +++ b/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())) .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()), Times.Once()); } [Fact] public async Task ValidateAuthorizationRequest_RequestIsRejectedWhenRedirectUriIsInvalid() { // Arrange - var application = Mock.Of(); + var application = new OpenIddictApplication(); var manager = CreateApplicationManager(instance => { - instance.Setup(mock => mock.FindByClientIdAsync("Fabrikam")) + instance.Setup(mock => mock.FindByClientIdAsync("Fabrikam", It.IsAny())) .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())) .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()), Times.Once()); + Mock.Get(manager).Verify(mock => mock.ValidateRedirectUriAsync(application, "http://www.fabrikam.com/path", It.IsAny()), 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(); + var application = new OpenIddictApplication(); var manager = CreateApplicationManager(instance => { - instance.Setup(mock => mock.FindByClientIdAsync("Fabrikam")) + instance.Setup(mock => mock.FindByClientIdAsync("Fabrikam", It.IsAny())) .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())) .ReturnsAsync(true); - instance.Setup(mock => mock.GetClientTypeAsync(application)) + instance.Setup(mock => mock.GetClientTypeAsync(application, It.IsAny())) .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()), Times.Once()); + Mock.Get(manager).Verify(mock => mock.ValidateRedirectUriAsync(application, "http://www.fabrikam.com/path", It.IsAny()), Times.Once()); + Mock.Get(manager).Verify(mock => mock.GetClientTypeAsync(application, It.IsAny()), Times.Once()); } [Fact] @@ -388,15 +392,15 @@ namespace OpenIddict.Core.Tests.Infrastructure { var server = CreateAuthorizationServer(builder => { builder.Services.AddSingleton(CreateApplicationManager(instance => { - var application = Mock.Of(); + var application = new OpenIddictApplication(); - instance.Setup(mock => mock.FindByClientIdAsync("Fabrikam")) + instance.Setup(mock => mock.FindByClientIdAsync("Fabrikam", It.IsAny())) .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())) .ReturnsAsync(true); - instance.Setup(mock => mock.GetClientTypeAsync(application)) + instance.Setup(mock => mock.GetClientTypeAsync(application, It.IsAny())) .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(); + var application = new OpenIddictApplication(); - instance.Setup(mock => mock.FindByClientIdAsync("Fabrikam")) + instance.Setup(mock => mock.FindByClientIdAsync("Fabrikam", It.IsAny())) .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())) .ReturnsAsync(true); - instance.Setup(mock => mock.GetClientTypeAsync(application)) + instance.Setup(mock => mock.GetClientTypeAsync(application, It.IsAny())) .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())) .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(); + var application = new OpenIddictApplication(); - instance.Setup(mock => mock.FindByClientIdAsync("Fabrikam")) + instance.Setup(mock => mock.FindByClientIdAsync("Fabrikam", It.IsAny())) .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())) .ReturnsAsync(true); - instance.Setup(mock => mock.GetClientTypeAsync(application)) + instance.Setup(mock => mock.GetClientTypeAsync(application, It.IsAny())) .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(); + var application = new OpenIddictApplication(); - instance.Setup(mock => mock.FindByClientIdAsync("Fabrikam")) + instance.Setup(mock => mock.FindByClientIdAsync("Fabrikam", It.IsAny())) .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())) .ReturnsAsync(true); - instance.Setup(mock => mock.GetClientTypeAsync(application)) + instance.Setup(mock => mock.GetClientTypeAsync(application, It.IsAny())) .ReturnsAsync(OpenIddictConstants.ClientTypes.Public); })); diff --git a/test/OpenIddict.Core.Tests/Infrastructure/OpenIddictProviderTests.Discovery.cs b/test/OpenIddict.Tests/OpenIddictProviderTests.Discovery.cs similarity index 97% rename from test/OpenIddict.Core.Tests/Infrastructure/OpenIddictProviderTests.Discovery.cs rename to test/OpenIddict.Tests/OpenIddictProviderTests.Discovery.cs index 6a0678de..0081af37 100644 --- a/test/OpenIddict.Core.Tests/Infrastructure/OpenIddictProviderTests.Discovery.cs +++ b/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() { diff --git a/test/OpenIddict.Core.Tests/Infrastructure/OpenIddictProviderTests.Exchange.cs b/test/OpenIddict.Tests/OpenIddictProviderTests.Exchange.cs similarity index 87% rename from test/OpenIddict.Core.Tests/Infrastructure/OpenIddictProviderTests.Exchange.cs rename to test/OpenIddict.Tests/OpenIddictProviderTests.Exchange.cs index 402f44f7..cd4c8da9 100644 --- a/test/OpenIddict.Core.Tests/Infrastructure/OpenIddictProviderTests.Exchange.cs +++ b/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())) .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()), Times.Once()); } [Fact] public async Task ValidateTokenRequest_ClientCredentialsRequestFromPublicClientIsRejected() { // Arrange - var application = Mock.Of(); + var application = new OpenIddictApplication(); var manager = CreateApplicationManager(instance => { - instance.Setup(mock => mock.FindByClientIdAsync("Fabrikam")) + instance.Setup(mock => mock.FindByClientIdAsync("Fabrikam", It.IsAny())) .ReturnsAsync(application); - instance.Setup(mock => mock.GetClientTypeAsync(application)) + instance.Setup(mock => mock.GetClientTypeAsync(application, It.IsAny())) .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()), Times.Once()); + Mock.Get(manager).Verify(mock => mock.GetClientTypeAsync(application, It.IsAny()), Times.Once()); } [Fact] public async Task ValidateTokenRequest_ClientSecretCannotBeUsedByPublicClients() { // Arrange - var application = Mock.Of(); + var application = new OpenIddictApplication(); var manager = CreateApplicationManager(instance => { - instance.Setup(mock => mock.FindByClientIdAsync("Fabrikam")) + instance.Setup(mock => mock.FindByClientIdAsync("Fabrikam", It.IsAny())) .ReturnsAsync(application); - instance.Setup(mock => mock.GetClientTypeAsync(application)) + instance.Setup(mock => mock.GetClientTypeAsync(application, It.IsAny())) .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()), Times.Once()); + Mock.Get(manager).Verify(mock => mock.GetClientTypeAsync(application, It.IsAny()), Times.Once()); } [Fact] public async Task ValidateTokenRequest_ClientSecretIsRequiredForConfidentialClients() { // Arrange - var application = Mock.Of(); + var application = new OpenIddictApplication(); var manager = CreateApplicationManager(instance => { - instance.Setup(mock => mock.FindByClientIdAsync("Fabrikam")) + instance.Setup(mock => mock.FindByClientIdAsync("Fabrikam", It.IsAny())) .ReturnsAsync(application); - instance.Setup(mock => mock.GetClientTypeAsync(application)) + instance.Setup(mock => mock.GetClientTypeAsync(application, It.IsAny())) .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()), Times.Once()); + Mock.Get(manager).Verify(mock => mock.GetClientTypeAsync(application, It.IsAny()), Times.Once()); } [Fact] public async Task ValidateTokenRequest_RequestIsRejectedWhenClientCredentialsAreInvalid() { // Arrange - var application = Mock.Of(); + var application = new OpenIddictApplication(); var manager = CreateApplicationManager(instance => { - instance.Setup(mock => mock.FindByClientIdAsync("Fabrikam")) + instance.Setup(mock => mock.FindByClientIdAsync("Fabrikam", It.IsAny())) .ReturnsAsync(application); - instance.Setup(mock => mock.GetClientTypeAsync(application)) + instance.Setup(mock => mock.GetClientTypeAsync(application, It.IsAny())) .ReturnsAsync(OpenIddictConstants.ClientTypes.Confidential); - instance.Setup(mock => mock.ValidateSecretAsync(application, "7Fjfp0ZBr1KtDRbnfVdmIw")) + instance.Setup(mock => mock.ValidateSecretAsync(application, "7Fjfp0ZBr1KtDRbnfVdmIw", It.IsAny())) .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()), Times.Once()); + Mock.Get(manager).Verify(mock => mock.GetClientTypeAsync(application, It.IsAny()), Times.Once()); + Mock.Get(manager).Verify(mock => mock.ValidateSecretAsync(application, "7Fjfp0ZBr1KtDRbnfVdmIw", It.IsAny()), 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())) .ReturnsAsync(null); }); var server = CreateAuthorizationServer(builder => { builder.Services.AddSingleton(CreateApplicationManager(instance => { - var application = Mock.Of(); + var application = new OpenIddictApplication(); - instance.Setup(mock => mock.FindByClientIdAsync("Fabrikam")) + instance.Setup(mock => mock.FindByClientIdAsync("Fabrikam", It.IsAny())) .ReturnsAsync(application); - instance.Setup(mock => mock.GetClientTypeAsync(application)) + instance.Setup(mock => mock.GetClientTypeAsync(application, It.IsAny())) .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()), 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())) .ReturnsAsync(null); }); var server = CreateAuthorizationServer(builder => { builder.Services.AddSingleton(CreateApplicationManager(instance => { - var application = Mock.Of(); + var application = new OpenIddictApplication(); - instance.Setup(mock => mock.FindByClientIdAsync("Fabrikam")) + instance.Setup(mock => mock.FindByClientIdAsync("Fabrikam", It.IsAny())) .ReturnsAsync(application); - instance.Setup(mock => mock.GetClientTypeAsync(application)) + instance.Setup(mock => mock.GetClientTypeAsync(application, It.IsAny())) .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()), Times.Once()); } [Fact] @@ -421,21 +425,21 @@ namespace OpenIddict.Core.Tests.Infrastructure { format.Setup(mock => mock.Unprotect("SplxlOBeZQQYbYS6WxSbIA")) .Returns(ticket); - var token = Mock.Of(); + 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())) .ReturnsAsync(token); }); var server = CreateAuthorizationServer(builder => { builder.Services.AddSingleton(CreateApplicationManager(instance => { - var application = Mock.Of(); + var application = new OpenIddictApplication(); - instance.Setup(mock => mock.FindByClientIdAsync("Fabrikam")) + instance.Setup(mock => mock.FindByClientIdAsync("Fabrikam", It.IsAny())) .ReturnsAsync(application); - instance.Setup(mock => mock.GetClientTypeAsync(application)) + instance.Setup(mock => mock.GetClientTypeAsync(application, It.IsAny())) .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()), Times.Once()); + Mock.Get(manager).Verify(mock => mock.RevokeAsync(token, It.IsAny()), Times.Once()); } [Fact] @@ -477,21 +481,21 @@ namespace OpenIddict.Core.Tests.Infrastructure { format.Setup(mock => mock.Unprotect("8xLOxBtZp8")) .Returns(ticket); - var token = Mock.Of(); + 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())) .ReturnsAsync(token); }); var server = CreateAuthorizationServer(builder => { builder.Services.AddSingleton(CreateApplicationManager(instance => { - var application = Mock.Of(); + var application = new OpenIddictApplication(); - instance.Setup(mock => mock.FindByClientIdAsync("Fabrikam")) + instance.Setup(mock => mock.FindByClientIdAsync("Fabrikam", It.IsAny())) .ReturnsAsync(application); - instance.Setup(mock => mock.GetClientTypeAsync(application)) + instance.Setup(mock => mock.GetClientTypeAsync(application, It.IsAny())) .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()), Times.Once()); + Mock.Get(manager).Verify(mock => mock.RevokeAsync(token, It.IsAny()), Times.Once()); } [Theory] @@ -536,24 +540,24 @@ namespace OpenIddict.Core.Tests.Infrastructure { format.Setup(mock => mock.Unprotect("8xLOxBtZp8")) .Returns(ticket); - var token = Mock.Of(); + 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())) .ReturnsAsync(token); }); var server = CreateAuthorizationServer(builder => { builder.Services.AddSingleton(CreateApplicationManager(instance => { - var application = Mock.Of(); + var application = new OpenIddictApplication(); - instance.Setup(mock => mock.FindByClientIdAsync("Fabrikam")) + instance.Setup(mock => mock.FindByClientIdAsync("Fabrikam", It.IsAny())) .ReturnsAsync(application); - instance.Setup(mock => mock.GetClientTypeAsync(application)) + instance.Setup(mock => mock.GetClientTypeAsync(application, It.IsAny())) .ReturnsAsync(OpenIddictConstants.ClientTypes.Confidential); - instance.Setup(mock => mock.ValidateSecretAsync(application, "7Fjfp0ZBr1KtDRbnfVdmIw")) + instance.Setup(mock => mock.ValidateSecretAsync(application, "7Fjfp0ZBr1KtDRbnfVdmIw", It.IsAny())) .ReturnsAsync(true); })); diff --git a/test/OpenIddict.Core.Tests/Infrastructure/OpenIddictProviderTests.Introspection.cs b/test/OpenIddict.Tests/OpenIddictProviderTests.Introspection.cs similarity index 86% rename from test/OpenIddict.Core.Tests/Infrastructure/OpenIddictProviderTests.Introspection.cs rename to test/OpenIddict.Tests/OpenIddictProviderTests.Introspection.cs index 87ed713b..efd41581 100644 --- a/test/OpenIddict.Core.Tests/Infrastructure/OpenIddictProviderTests.Introspection.cs +++ b/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())) .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()), Times.Once()); } [Fact] public async Task ValidateIntrospectionRequest_RequestsSentByPublicClientsAreRejected() { // Arrange - var application = Mock.Of(); + var application = new OpenIddictApplication(); var manager = CreateApplicationManager(instance => { - instance.Setup(mock => mock.FindByClientIdAsync("Fabrikam")) + instance.Setup(mock => mock.FindByClientIdAsync("Fabrikam", It.IsAny())) .ReturnsAsync(application); - instance.Setup(mock => mock.GetClientTypeAsync(application)) + instance.Setup(mock => mock.GetClientTypeAsync(application, It.IsAny())) .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()), Times.Once()); + Mock.Get(manager).Verify(mock => mock.GetClientTypeAsync(application, It.IsAny()), Times.Once()); } [Fact] public async Task ValidateIntrospectionRequest_RequestIsRejectedWhenClientCredentialsAreInvalid() { // Arrange - var application = Mock.Of(); + var application = new OpenIddictApplication(); var manager = CreateApplicationManager(instance => { - instance.Setup(mock => mock.FindByClientIdAsync("Fabrikam")) + instance.Setup(mock => mock.FindByClientIdAsync("Fabrikam", It.IsAny())) .ReturnsAsync(application); - instance.Setup(mock => mock.GetClientTypeAsync(application)) + instance.Setup(mock => mock.GetClientTypeAsync(application, It.IsAny())) .ReturnsAsync(OpenIddictConstants.ClientTypes.Confidential); - instance.Setup(mock => mock.ValidateSecretAsync(application, "7Fjfp0ZBr1KtDRbnfVdmIw")) + instance.Setup(mock => mock.ValidateSecretAsync(application, "7Fjfp0ZBr1KtDRbnfVdmIw", It.IsAny())) .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()), Times.Once()); + Mock.Get(manager).Verify(mock => mock.GetClientTypeAsync(application, It.IsAny()), Times.Once()); + Mock.Get(manager).Verify(mock => mock.ValidateSecretAsync(application, "7Fjfp0ZBr1KtDRbnfVdmIw", It.IsAny()), 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(); + var application = new OpenIddictApplication(); - instance.Setup(mock => mock.FindByClientIdAsync("Fabrikam")) + instance.Setup(mock => mock.FindByClientIdAsync("Fabrikam", It.IsAny())) .ReturnsAsync(application); - instance.Setup(mock => mock.GetClientTypeAsync(application)) + instance.Setup(mock => mock.GetClientTypeAsync(application, It.IsAny())) .ReturnsAsync(OpenIddictConstants.ClientTypes.Confidential); - instance.Setup(mock => mock.ValidateSecretAsync(application, "7Fjfp0ZBr1KtDRbnfVdmIw")) + instance.Setup(mock => mock.ValidateSecretAsync(application, "7Fjfp0ZBr1KtDRbnfVdmIw", It.IsAny())) .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())) .ReturnsAsync(null); }); var server = CreateAuthorizationServer(builder => { builder.Services.AddSingleton(CreateApplicationManager(instance => { - var application = Mock.Of(); + var application = new OpenIddictApplication(); - instance.Setup(mock => mock.FindByClientIdAsync("Fabrikam")) + instance.Setup(mock => mock.FindByClientIdAsync("Fabrikam", It.IsAny())) .ReturnsAsync(application); - instance.Setup(mock => mock.GetClientTypeAsync(application)) + instance.Setup(mock => mock.GetClientTypeAsync(application, It.IsAny())) .ReturnsAsync(OpenIddictConstants.ClientTypes.Confidential); - instance.Setup(mock => mock.ValidateSecretAsync(application, "7Fjfp0ZBr1KtDRbnfVdmIw")) + instance.Setup(mock => mock.ValidateSecretAsync(application, "7Fjfp0ZBr1KtDRbnfVdmIw", It.IsAny())) .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()), 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())) .ReturnsAsync(null); }); var server = CreateAuthorizationServer(builder => { builder.Services.AddSingleton(CreateApplicationManager(instance => { - var application = Mock.Of(); + var application = new OpenIddictApplication(); - instance.Setup(mock => mock.FindByClientIdAsync("Fabrikam")) + instance.Setup(mock => mock.FindByClientIdAsync("Fabrikam", It.IsAny())) .ReturnsAsync(application); - instance.Setup(mock => mock.GetClientTypeAsync(application)) + instance.Setup(mock => mock.GetClientTypeAsync(application, It.IsAny())) .ReturnsAsync(OpenIddictConstants.ClientTypes.Confidential); - instance.Setup(mock => mock.ValidateSecretAsync(application, "7Fjfp0ZBr1KtDRbnfVdmIw")) + instance.Setup(mock => mock.ValidateSecretAsync(application, "7Fjfp0ZBr1KtDRbnfVdmIw", It.IsAny())) .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()), Times.Once()); } } } diff --git a/test/OpenIddict.Core.Tests/Infrastructure/OpenIddictProviderTests.Revocation.cs b/test/OpenIddict.Tests/OpenIddictProviderTests.Revocation.cs similarity index 89% rename from test/OpenIddict.Core.Tests/Infrastructure/OpenIddictProviderTests.Revocation.cs rename to test/OpenIddict.Tests/OpenIddictProviderTests.Revocation.cs index 51d5d2eb..e317dd91 100644 --- a/test/OpenIddict.Core.Tests/Infrastructure/OpenIddictProviderTests.Revocation.cs +++ b/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())) .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()), Times.Once()); } [Fact] public async Task ValidateRevocationRequest_ClientSecretCannotBeUsedByPublicClients() { // Arrange - var application = Mock.Of(); + var application = new OpenIddictApplication(); var manager = CreateApplicationManager(instance => { - instance.Setup(mock => mock.FindByClientIdAsync("Fabrikam")) + instance.Setup(mock => mock.FindByClientIdAsync("Fabrikam", It.IsAny())) .ReturnsAsync(application); - instance.Setup(mock => mock.GetClientTypeAsync(application)) + instance.Setup(mock => mock.GetClientTypeAsync(application, It.IsAny())) .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()), Times.Once()); + Mock.Get(manager).Verify(mock => mock.GetClientTypeAsync(application, It.IsAny()), Times.Once()); } [Fact] public async Task ValidateRevocationRequest_ClientSecretIsRequiredForConfidentialClients() { // Arrange - var application = Mock.Of(); + var application = new OpenIddictApplication(); var manager = CreateApplicationManager(instance => { - instance.Setup(mock => mock.FindByClientIdAsync("Fabrikam")) + instance.Setup(mock => mock.FindByClientIdAsync("Fabrikam", It.IsAny())) .ReturnsAsync(application); - instance.Setup(mock => mock.GetClientTypeAsync(application)) + instance.Setup(mock => mock.GetClientTypeAsync(application, It.IsAny())) .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()), Times.Once()); + Mock.Get(manager).Verify(mock => mock.GetClientTypeAsync(application, It.IsAny()), Times.Once()); } [Fact] public async Task ValidateRevocationRequest_RequestIsRejectedWhenClientCredentialsAreInvalid() { // Arrange - var application = Mock.Of(); + var application = new OpenIddictApplication(); var manager = CreateApplicationManager(instance => { - instance.Setup(mock => mock.FindByClientIdAsync("Fabrikam")) + instance.Setup(mock => mock.FindByClientIdAsync("Fabrikam", It.IsAny())) .ReturnsAsync(application); - instance.Setup(mock => mock.GetClientTypeAsync(application)) + instance.Setup(mock => mock.GetClientTypeAsync(application, It.IsAny())) .ReturnsAsync(OpenIddictConstants.ClientTypes.Confidential); - instance.Setup(mock => mock.ValidateSecretAsync(application, "7Fjfp0ZBr1KtDRbnfVdmIw")) + instance.Setup(mock => mock.ValidateSecretAsync(application, "7Fjfp0ZBr1KtDRbnfVdmIw", It.IsAny())) .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()), Times.Once()); + Mock.Get(manager).Verify(mock => mock.GetClientTypeAsync(application, It.IsAny()), Times.Once()); + Mock.Get(manager).Verify(mock => mock.ValidateSecretAsync(application, "7Fjfp0ZBr1KtDRbnfVdmIw", It.IsAny()), 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())) .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()), Times.Never()); + Mock.Get(manager).Verify(mock => mock.FindByIdAsync("3E228451-1555-46F7-A471-951EFBA23A56", It.IsAny()), Times.Once()); + Mock.Get(manager).Verify(mock => mock.RevokeAsync(It.IsAny(), It.IsAny()), Times.Never()); } [Fact] @@ -324,10 +328,10 @@ namespace OpenIddict.Core.Tests.Infrastructure { format.Setup(mock => mock.Unprotect("SlAV32hkKG")) .Returns(ticket); - var token = Mock.Of(); + 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())) .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()), Times.Once()); + Mock.Get(manager).Verify(mock => mock.RevokeAsync(token, It.IsAny()), Times.Once()); } } } diff --git a/test/OpenIddict.Core.Tests/Infrastructure/OpenIddictProviderTests.Serialization.cs b/test/OpenIddict.Tests/OpenIddictProviderTests.Serialization.cs similarity index 78% rename from test/OpenIddict.Core.Tests/Infrastructure/OpenIddictProviderTests.Serialization.cs rename to test/OpenIddict.Tests/OpenIddictProviderTests.Serialization.cs index 67c066da..dcad715e 100644 --- a/test/OpenIddict.Core.Tests/Infrastructure/OpenIddictProviderTests.Serialization.cs +++ b/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())) .ReturnsAsync("3E228451-1555-46F7-A471-951EFBA23A56"); }); var server = CreateAuthorizationServer(builder => { builder.Services.AddSingleton(CreateApplicationManager(instance => { - var application = Mock.Of(); + var application = new OpenIddictApplication(); - instance.Setup(mock => mock.FindByClientIdAsync("Fabrikam")) + instance.Setup(mock => mock.FindByClientIdAsync("Fabrikam", It.IsAny())) .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())) .ReturnsAsync(true); - instance.Setup(mock => mock.GetClientTypeAsync(application)) + instance.Setup(mock => mock.GetClientTypeAsync(application, It.IsAny())) .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()), 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())) .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()), Times.Once()); } } } diff --git a/test/OpenIddict.Core.Tests/Infrastructure/OpenIddictProviderTests.Session.cs b/test/OpenIddict.Tests/OpenIddictProviderTests.Session.cs similarity index 90% rename from test/OpenIddict.Core.Tests/Infrastructure/OpenIddictProviderTests.Session.cs rename to test/OpenIddict.Tests/OpenIddictProviderTests.Session.cs index faab7e68..c7c263b5 100644 --- a/test/OpenIddict.Core.Tests/Infrastructure/OpenIddictProviderTests.Session.cs +++ b/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())) .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()), 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(); + 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())) .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(); + 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())) .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())) .ReturnsAsync(null); })); diff --git a/test/OpenIddict.Core.Tests/Infrastructure/OpenIddictProviderTests.Userinfo.cs b/test/OpenIddict.Tests/OpenIddictProviderTests.Userinfo.cs similarity index 96% rename from test/OpenIddict.Core.Tests/Infrastructure/OpenIddictProviderTests.Userinfo.cs rename to test/OpenIddict.Tests/OpenIddictProviderTests.Userinfo.cs index 750b00ea..e6e35d56 100644 --- a/test/OpenIddict.Core.Tests/Infrastructure/OpenIddictProviderTests.Userinfo.cs +++ b/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() { diff --git a/test/OpenIddict.Core.Tests/Infrastructure/OpenIddictProviderTests.cs b/test/OpenIddict.Tests/OpenIddictProviderTests.cs similarity index 83% rename from test/OpenIddict.Core.Tests/Infrastructure/OpenIddictProviderTests.cs rename to test/OpenIddict.Tests/OpenIddictProviderTests.cs index f156c4ca..43c7e0ee 100644 --- a/test/OpenIddict.Core.Tests/Infrastructure/OpenIddictProviderTests.cs +++ b/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() + 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 CreateApplicationManager(Action>> setup = null) { - var manager = new Mock>( - Mock.Of(), - Mock.Of>(), - Mock.Of>>()); + private static OpenIddictApplicationManager CreateApplicationManager(Action>> setup = null) { + var manager = new Mock>( + Mock.Of>(), + Mock.Of>>()); setup?.Invoke(manager); return manager.Object; } - private static OpenIddictTokenManager CreateTokenManager(Action>> setup = null) { - var manager = new Mock>( - Mock.Of(), - Mock.Of>(), - Mock.Of>>()); + private static OpenIddictTokenManager CreateTokenManager(Action>> setup = null) { + var manager = new Mock>( + Mock.Of>(), + Mock.Of>>()); setup?.Invoke(manager); diff --git a/test/OpenIddict.Tests/project.json b/test/OpenIddict.Tests/project.json index 81267ca2..74c65031 100644 --- a/test/OpenIddict.Tests/project.json +++ b/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" } },