From 61ea06e5980c55583e169bd11cebb638e776ca07 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A9vin=20Chalet?= Date: Mon, 8 Mar 2021 17:12:33 +0100 Subject: [PATCH 1/8] Fix the authorizations/tokens pruning logic and allow more entries to be removed in a single PruneAsync() call --- .../Stores/OpenIddictEntityFrameworkAuthorizationStore.cs | 6 ++++-- .../Stores/OpenIddictEntityFrameworkTokenStore.cs | 6 +++--- .../OpenIddictEntityFrameworkCoreAuthorizationStore.cs | 6 ++++-- .../Stores/OpenIddictEntityFrameworkCoreTokenStore.cs | 6 +++--- .../Stores/OpenIddictMongoDbAuthorizationStore.cs | 4 ++-- .../Stores/OpenIddictMongoDbTokenStore.cs | 4 ++-- 6 files changed, 18 insertions(+), 14 deletions(-) diff --git a/src/OpenIddict.EntityFramework/Stores/OpenIddictEntityFrameworkAuthorizationStore.cs b/src/OpenIddict.EntityFramework/Stores/OpenIddictEntityFrameworkAuthorizationStore.cs index e4e32187..65038a22 100644 --- a/src/OpenIddict.EntityFramework/Stores/OpenIddictEntityFrameworkAuthorizationStore.cs +++ b/src/OpenIddict.EntityFramework/Stores/OpenIddictEntityFrameworkAuthorizationStore.cs @@ -621,7 +621,9 @@ namespace OpenIddict.EntityFramework } } - for (var offset = 0; offset < 100_000; offset += 1_000) + // Note: to avoid sending too many queries, the maximum number of elements + // that can be removed by a single call to PruneAsync() is deliberately limited. + for (var index = 0; index < 1_000; index++) { cancellationToken.ThrowIfCancellationRequested(); @@ -637,7 +639,7 @@ namespace OpenIddict.EntityFramework where authorization.Status != Statuses.Valid || (authorization.Type == AuthorizationTypes.AdHoc && !authorization.Tokens.Any()) orderby authorization.Id - select authorization).Skip(offset).Take(1_000).ToListAsync(cancellationToken); + select authorization).Take(1_000).ToListAsync(cancellationToken); if (authorizations.Count == 0) { diff --git a/src/OpenIddict.EntityFramework/Stores/OpenIddictEntityFrameworkTokenStore.cs b/src/OpenIddict.EntityFramework/Stores/OpenIddictEntityFrameworkTokenStore.cs index fe00d7b0..069c8b59 100644 --- a/src/OpenIddict.EntityFramework/Stores/OpenIddictEntityFrameworkTokenStore.cs +++ b/src/OpenIddict.EntityFramework/Stores/OpenIddictEntityFrameworkTokenStore.cs @@ -607,8 +607,8 @@ namespace OpenIddict.EntityFramework } // Note: to avoid sending too many queries, the maximum number of elements - // that can be removed by a single call to PruneAsync() is limited to 50000. - for (var offset = 0; offset < 50_000; offset += 1_000) + // that can be removed by a single call to PruneAsync() is deliberately limited. + for (var index = 0; index < 1_000; index++) { cancellationToken.ThrowIfCancellationRequested(); @@ -625,7 +625,7 @@ namespace OpenIddict.EntityFramework (token.Authorization != null && token.Authorization.Status != Statuses.Valid) || token.ExpirationDate < DateTime.UtcNow orderby token.Id - select token).Skip(offset).Take(1_000).ToListAsync(cancellationToken); + select token).Take(1_000).ToListAsync(cancellationToken); if (tokens.Count == 0) { diff --git a/src/OpenIddict.EntityFrameworkCore/Stores/OpenIddictEntityFrameworkCoreAuthorizationStore.cs b/src/OpenIddict.EntityFrameworkCore/Stores/OpenIddictEntityFrameworkCoreAuthorizationStore.cs index 4bba4cd2..fc7da06a 100644 --- a/src/OpenIddict.EntityFrameworkCore/Stores/OpenIddictEntityFrameworkCoreAuthorizationStore.cs +++ b/src/OpenIddict.EntityFrameworkCore/Stores/OpenIddictEntityFrameworkCoreAuthorizationStore.cs @@ -701,7 +701,9 @@ namespace OpenIddict.EntityFrameworkCore return null; } - for (var offset = 0; offset < 100_000; offset += 1_000) + // Note: to avoid sending too many queries, the maximum number of elements + // that can be removed by a single call to PruneAsync() is deliberately limited. + for (var index = 0; index < 1_000; index++) { cancellationToken.ThrowIfCancellationRequested(); @@ -717,7 +719,7 @@ namespace OpenIddict.EntityFrameworkCore where authorization.Status != Statuses.Valid || (authorization.Type == AuthorizationTypes.AdHoc && !authorization.Tokens.Any()) orderby authorization.Id - select authorization).Skip(offset).Take(1_000).ToListAsync(cancellationToken); + select authorization).Take(1_000).ToListAsync(cancellationToken); if (authorizations.Count == 0) { diff --git a/src/OpenIddict.EntityFrameworkCore/Stores/OpenIddictEntityFrameworkCoreTokenStore.cs b/src/OpenIddict.EntityFrameworkCore/Stores/OpenIddictEntityFrameworkCoreTokenStore.cs index 67499a3e..99cbd945 100644 --- a/src/OpenIddict.EntityFrameworkCore/Stores/OpenIddictEntityFrameworkCoreTokenStore.cs +++ b/src/OpenIddict.EntityFrameworkCore/Stores/OpenIddictEntityFrameworkCoreTokenStore.cs @@ -670,8 +670,8 @@ namespace OpenIddict.EntityFrameworkCore } // Note: to avoid sending too many queries, the maximum number of elements - // that can be removed by a single call to PruneAsync() is limited to 50000. - for (var offset = 0; offset < 50_000; offset += 1_000) + // that can be removed by a single call to PruneAsync() is deliberately limited. + for (var index = 0; index < 1_000; index++) { cancellationToken.ThrowIfCancellationRequested(); @@ -688,7 +688,7 @@ namespace OpenIddict.EntityFrameworkCore (token.Authorization != null && token.Authorization.Status != Statuses.Valid) || token.ExpirationDate < DateTime.UtcNow orderby token.Id - select token).Skip(offset).Take(1_000).ToListAsync(cancellationToken); + select token).Take(1_000).ToListAsync(cancellationToken); if (tokens.Count == 0) { diff --git a/src/OpenIddict.MongoDb/Stores/OpenIddictMongoDbAuthorizationStore.cs b/src/OpenIddict.MongoDb/Stores/OpenIddictMongoDbAuthorizationStore.cs index 0510a722..edb501ed 100644 --- a/src/OpenIddict.MongoDb/Stores/OpenIddictMongoDbAuthorizationStore.cs +++ b/src/OpenIddict.MongoDb/Stores/OpenIddictMongoDbAuthorizationStore.cs @@ -548,8 +548,8 @@ namespace OpenIddict.MongoDb select authorization.Id).ToListAsync(cancellationToken); // Note: to avoid generating delete requests with very large filters, a buffer is used here and the - // maximum number of elements that can be removed by a single call to PruneAsync() is limited to 50000. - foreach (var buffer in Buffer(identifiers.Take(50_000), 1_000)) + // maximum number of elements that can be removed by a single call to PruneAsync() is deliberately limited. + foreach (var buffer in Buffer(identifiers.Take(1_000_000), 1_000)) { await collection.DeleteManyAsync(authorization => buffer.Contains(authorization.Id), cancellationToken); } diff --git a/src/OpenIddict.MongoDb/Stores/OpenIddictMongoDbTokenStore.cs b/src/OpenIddict.MongoDb/Stores/OpenIddictMongoDbTokenStore.cs index c34f7b43..5f24e44e 100644 --- a/src/OpenIddict.MongoDb/Stores/OpenIddictMongoDbTokenStore.cs +++ b/src/OpenIddict.MongoDb/Stores/OpenIddictMongoDbTokenStore.cs @@ -585,8 +585,8 @@ namespace OpenIddict.MongoDb select token.Id).ToListAsync(cancellationToken); // Note: to avoid generating delete requests with very large filters, a buffer is used here and the - // maximum number of elements that can be removed by a single call to PruneAsync() is limited to 50000. - foreach (var buffer in Buffer(identifiers.Take(50_000), 1_000)) + // maximum number of elements that can be removed by a single call to PruneAsync() is deliberately limited. + foreach (var buffer in Buffer(identifiers.Take(1_000_000), 1_000)) { await collection.DeleteManyAsync(token => buffer.Contains(token.Id), cancellationToken); } From 157afeb7f26eebd32209c65ab7953a7f5b42b53e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A9vin=20Chalet?= Date: Mon, 8 Mar 2021 16:46:42 +0100 Subject: [PATCH 2/8] Rework the encryption/signing development certificates mechanism to support multiple certificates --- .../OpenIddictServerBuilder.cs | 66 ++++++++----------- 1 file changed, 28 insertions(+), 38 deletions(-) diff --git a/src/OpenIddict.Server/OpenIddictServerBuilder.cs b/src/OpenIddict.Server/OpenIddictServerBuilder.cs index e586f9bb..642f81e0 100644 --- a/src/OpenIddict.Server/OpenIddictServerBuilder.cs +++ b/src/OpenIddict.Server/OpenIddictServerBuilder.cs @@ -217,29 +217,21 @@ namespace Microsoft.Extensions.DependencyInjection using var store = new X509Store(StoreName.My, StoreLocation.CurrentUser); store.Open(OpenFlags.ReadWrite); - // Try to retrieve the development certificate from the specified store. - // If a certificate was found but is not yet or no longer valid, remove it - // from the store before creating and persisting a new encryption certificate. - var certificate = store.Certificates.Find(X509FindType.FindBySubjectDistinguishedName, subject.Name, validOnly: false) + // Try to retrieve the existing development certificates from the specified store. + // If no valid existing certificate was found, create a new encryption certificate. + var certificates = store.Certificates.Find(X509FindType.FindBySubjectDistinguishedName, subject.Name, validOnly: false) .OfType() - .SingleOrDefault(); + .ToList(); - if (certificate is not null && (certificate.NotBefore > DateTime.Now || certificate.NotAfter < DateTime.Now)) + if (!certificates.Any(certificate => certificate.NotBefore < DateTime.Now && certificate.NotAfter > DateTime.Now)) { - store.Remove(certificate); - certificate = null; - } - #if SUPPORTS_CERTIFICATE_GENERATION - // If no appropriate certificate can be found, generate and persist a new certificate in the specified store. - if (certificate is null) - { using var algorithm = RSA.Create(keySizeInBits: 2048); var request = new CertificateRequest(subject, algorithm, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1); request.CertificateExtensions.Add(new X509KeyUsageExtension(X509KeyUsageFlags.KeyEncipherment, critical: true)); - certificate = request.CreateSelfSigned(DateTimeOffset.UtcNow, DateTimeOffset.UtcNow.AddYears(2)); + var certificate = request.CreateSelfSigned(DateTimeOffset.UtcNow, DateTimeOffset.UtcNow.AddYears(2)); // Note: setting the friendly name is not supported on Unix machines (including Linux and macOS). // To ensure an exception is not thrown by the property setter, an OS runtime check is used here. @@ -265,7 +257,7 @@ namespace Microsoft.Extensions.DependencyInjection flags |= X509KeyStorageFlags.Exportable; } - certificate = new X509Certificate2(data, string.Empty, flags); + certificates.Insert(0, certificate = new X509Certificate2(data, string.Empty, flags)); } finally @@ -274,12 +266,15 @@ namespace Microsoft.Extensions.DependencyInjection } store.Add(certificate); - } - - return AddEncryptionCertificate(certificate); #else - throw new PlatformNotSupportedException(SR.GetResourceString(SR.ID0264)); + throw new PlatformNotSupportedException(SR.GetResourceString(SR.ID0264)); #endif + } + + return Configure(options => options.EncryptionCredentials.AddRange( + from certificate in certificates + let key = new X509SecurityKey(certificate) + select new EncryptingCredentials(key, SecurityAlgorithms.RsaOAEP, SecurityAlgorithms.Aes256CbcHmacSha512))); } /// @@ -654,29 +649,21 @@ namespace Microsoft.Extensions.DependencyInjection using var store = new X509Store(StoreName.My, StoreLocation.CurrentUser); store.Open(OpenFlags.ReadWrite); - // Try to retrieve the development certificate from the specified store. - // If a certificate was found but is not yet or no longer valid, remove it - // from the store before creating and persisting a new signing certificate. - var certificate = store.Certificates.Find(X509FindType.FindBySubjectDistinguishedName, subject.Name, validOnly: false) + // Try to retrieve the existing development certificates from the specified store. + // If no valid existing certificate was found, create a new signing certificate. + var certificates = store.Certificates.Find(X509FindType.FindBySubjectDistinguishedName, subject.Name, validOnly: false) .OfType() - .SingleOrDefault(); + .ToList(); - if (certificate is not null && (certificate.NotBefore > DateTime.Now || certificate.NotAfter < DateTime.Now)) + if (!certificates.Any(certificate => certificate.NotBefore < DateTime.Now && certificate.NotAfter > DateTime.Now)) { - store.Remove(certificate); - certificate = null; - } - #if SUPPORTS_CERTIFICATE_GENERATION - // If no appropriate certificate can be found, generate and persist a new certificate in the specified store. - if (certificate is null) - { using var algorithm = RSA.Create(keySizeInBits: 2048); var request = new CertificateRequest(subject, algorithm, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1); request.CertificateExtensions.Add(new X509KeyUsageExtension(X509KeyUsageFlags.DigitalSignature, critical: true)); - certificate = request.CreateSelfSigned(DateTimeOffset.UtcNow, DateTimeOffset.UtcNow.AddYears(2)); + var certificate = request.CreateSelfSigned(DateTimeOffset.UtcNow, DateTimeOffset.UtcNow.AddYears(2)); // Note: setting the friendly name is not supported on Unix machines (including Linux and macOS). // To ensure an exception is not thrown by the property setter, an OS runtime check is used here. @@ -702,7 +689,7 @@ namespace Microsoft.Extensions.DependencyInjection flags |= X509KeyStorageFlags.Exportable; } - certificate = new X509Certificate2(data, string.Empty, flags); + certificates.Insert(0, certificate = new X509Certificate2(data, string.Empty, flags)); } finally @@ -711,12 +698,15 @@ namespace Microsoft.Extensions.DependencyInjection } store.Add(certificate); - } - - return AddSigningCertificate(certificate); #else - throw new PlatformNotSupportedException(SR.GetResourceString(SR.ID0264)); + throw new PlatformNotSupportedException(SR.GetResourceString(SR.ID0264)); #endif + } + + return Configure(options => options.SigningCredentials.AddRange( + from certificate in certificates + let key = new X509SecurityKey(certificate) + select new SigningCredentials(key, SecurityAlgorithms.RsaSha256))); } /// From 35a89f6d724875184eb08e29f14510fe1c2a6928 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A9vin=20Chalet?= Date: Wed, 10 Mar 2021 14:56:08 +0100 Subject: [PATCH 3/8] Bump the .NET SDK and ASP.NET Core/.NET Core dependencies --- Packages.props | 69 ++++++++++--------- global.json | 6 +- .../OpenIddict.Abstractions.csproj | 1 + 3 files changed, 40 insertions(+), 36 deletions(-) diff --git a/Packages.props b/Packages.props index f0babddb..846474fe 100644 --- a/Packages.props +++ b/Packages.props @@ -4,8 +4,8 @@ - - + + @@ -13,10 +13,10 @@ - - - - + + + + @@ -32,10 +32,10 @@ - - + + @@ -45,56 +45,59 @@ + - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + - - - - - - - - + + + + + + + + - + - + + diff --git a/global.json b/global.json index 73862807..2eb4cb3f 100644 --- a/global.json +++ b/global.json @@ -1,11 +1,11 @@ { "tools": { - "dotnet": "5.0.101", + "dotnet": "5.0.201", "runtimes": { "aspnetcore": [ - "2.1.23", - "3.1.10" + "2.1.26", + "3.1.13" ] } }, diff --git a/src/OpenIddict.Abstractions/OpenIddict.Abstractions.csproj b/src/OpenIddict.Abstractions/OpenIddict.Abstractions.csproj index 5b056321..9d6c1878 100644 --- a/src/OpenIddict.Abstractions/OpenIddict.Abstractions.csproj +++ b/src/OpenIddict.Abstractions/OpenIddict.Abstractions.csproj @@ -13,6 +13,7 @@ + From 266db522e0c860d95acaa861a57495645e4f651a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A9vin=20Chalet?= Date: Wed, 10 Mar 2021 14:58:13 +0100 Subject: [PATCH 4/8] Update Versions.props to build 3.0.2 packages --- eng/Versions.props | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/eng/Versions.props b/eng/Versions.props index cea30a74..766331a0 100644 --- a/eng/Versions.props +++ b/eng/Versions.props @@ -3,7 +3,7 @@ 3 0 - 1 + 2 $(MajorVersion).$(MinorVersion).$(PatchVersion) rtm From d122c5dfb0187d9cc11af83587c41911d16c16fc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A9vin=20Chalet?= Date: Mon, 5 Apr 2021 17:19:34 +0200 Subject: [PATCH 5/8] Rework the development signing/encryption certificates unit tests --- .../OpenIddictServerBuilderTests.cs | 61 +++++++++++++++---- 1 file changed, 48 insertions(+), 13 deletions(-) diff --git a/test/OpenIddict.Server.Tests/OpenIddictServerBuilderTests.cs b/test/OpenIddict.Server.Tests/OpenIddictServerBuilderTests.cs index 744a3065..101f7308 100644 --- a/test/OpenIddict.Server.Tests/OpenIddictServerBuilderTests.cs +++ b/test/OpenIddict.Server.Tests/OpenIddictServerBuilderTests.cs @@ -1,5 +1,8 @@ using System; +using System.Globalization; using System.Reflection; +using System.Security.Cryptography; +using System.Security.Cryptography.X509Certificates; using System.Threading.Tasks; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Options; @@ -268,30 +271,65 @@ namespace OpenIddict.Server.Tests } [Fact] - public void AddDevelopmentSigningCertificate_ThrowsAnExceptionForNullSubject() + public void AddDevelopmentEncryptionCertificate_ThrowsAnExceptionForNullSubject() { // Arrange var services = CreateServices(); var builder = CreateBuilder(services); // Act and assert - var exception = Assert.Throws(delegate - { - builder.AddDevelopmentSigningCertificate(subject: null!); - }); - + var exception = Assert.Throws(() => builder.AddDevelopmentEncryptionCertificate(subject: null!)); Assert.Equal("subject", exception.ParamName); } +#if SUPPORTS_CERTIFICATE_GENERATION [Fact] - public void AddDevelopmentEncryptionCertificate_ThrowsAnExceptionForNullSubject() + public void AddDevelopmentEncryptionCertificate_CanGenerateCertificate() + { + // Arrange + var services = CreateServices(); + var builder = CreateBuilder(services); + + // Act + builder.AddDevelopmentEncryptionCertificate(); + + var options = GetOptions(services); + + // Assert + Assert.Single(options.EncryptionCredentials); + Assert.Equal(SecurityAlgorithms.RsaOAEP, options.EncryptionCredentials[0].Alg); + Assert.Equal(SecurityAlgorithms.Aes256CbcHmacSha512, options.EncryptionCredentials[0].Enc); + Assert.NotNull(options.EncryptionCredentials[0].Key.KeyId); + } +#else + [Fact] + public void AddDevelopmentEncryptionCertificate_ThrowsAnExceptionOnUnsupportedPlatforms() { // Arrange var services = CreateServices(); var builder = CreateBuilder(services); // Act and assert - var exception = Assert.Throws(() => builder.AddDevelopmentEncryptionCertificate(subject: null!)); + var exception = Assert.Throws(() => builder.AddDevelopmentEncryptionCertificate( + subject: new X500DistinguishedName("CN=" + Guid.NewGuid().ToString("N", CultureInfo.InvariantCulture)))); + + Assert.Equal("X.509 certificate generation is not supported on this platform.", exception.Message); + } +#endif + + [Fact] + public void AddDevelopmentSigningCertificate_ThrowsAnExceptionForNullSubject() + { + // Arrange + var services = CreateServices(); + var builder = CreateBuilder(services); + + // Act and assert + var exception = Assert.Throws(delegate + { + builder.AddDevelopmentSigningCertificate(subject: null!); + }); + Assert.Equal("subject", exception.ParamName); } @@ -322,11 +360,8 @@ namespace OpenIddict.Server.Tests var builder = CreateBuilder(services); // Act and assert - var exception = Assert.Throws(delegate - { - builder.AddDevelopmentSigningCertificate(); - return GetOptions(services); - }); + var exception = Assert.Throws(() => builder.AddDevelopmentSigningCertificate( + subject: new X500DistinguishedName("CN=" + Guid.NewGuid().ToString("N", CultureInfo.InvariantCulture)))); Assert.Equal("X.509 certificate generation is not supported on this platform.", exception.Message); } From 970ce43ca7837a0bc0ef20355513ef6aaaf241ab Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A9vin=20Chalet?= Date: Wed, 7 Apr 2021 11:38:25 +0200 Subject: [PATCH 6/8] Allow authorization requests that don't specify response_type=code when PKCE is enforced --- ...OpenIddictServerHandlers.Authentication.cs | 6 +- ...ctServerIntegrationTests.Authentication.cs | 57 +++++++++++++++++++ 2 files changed, 60 insertions(+), 3 deletions(-) diff --git a/src/OpenIddict.Server/OpenIddictServerHandlers.Authentication.cs b/src/OpenIddict.Server/OpenIddictServerHandlers.Authentication.cs index d5986036..fda3e1d3 100644 --- a/src/OpenIddict.Server/OpenIddictServerHandlers.Authentication.cs +++ b/src/OpenIddict.Server/OpenIddictServerHandlers.Authentication.cs @@ -1588,9 +1588,9 @@ namespace OpenIddict.Server Debug.Assert(!string.IsNullOrEmpty(context.ClientId), SR.FormatID4000(Parameters.ClientId)); - // If a code_challenge was provided, the request is always considered valid, - // whether the proof key for code exchange requirement is enforced or not. - if (!string.IsNullOrEmpty(context.Request.CodeChallenge)) + // If a code_challenge was provided or if no authorization code is requested, the request is always + // considered valid, whether the proof key for code exchange requirement is enforced or not. + if (!string.IsNullOrEmpty(context.Request.CodeChallenge) || !context.Request.HasResponseType(ResponseTypes.Code)) { return; } diff --git a/test/OpenIddict.Server.IntegrationTests/OpenIddictServerIntegrationTests.Authentication.cs b/test/OpenIddict.Server.IntegrationTests/OpenIddictServerIntegrationTests.Authentication.cs index f34329a3..7c670be5 100644 --- a/test/OpenIddict.Server.IntegrationTests/OpenIddictServerIntegrationTests.Authentication.cs +++ b/test/OpenIddict.Server.IntegrationTests/OpenIddictServerIntegrationTests.Authentication.cs @@ -1734,6 +1734,63 @@ namespace OpenIddict.Server.IntegrationTests Requirements.Features.ProofKeyForCodeExchange, It.IsAny()), Times.Never()); } + [Fact] + public async Task ValidateAuthorizationRequest_RequestIsValidatedWhenCodeIsNotRequestedWithPkceFeatureEnforced() + { + // Arrange + var application = new OpenIddictApplication(); + + var manager = CreateApplicationManager(mock => + { + mock.Setup(manager => manager.FindByClientIdAsync("Fabrikam", It.IsAny())) + .ReturnsAsync(application); + + mock.Setup(manager => manager.ValidateRedirectUriAsync(application, "http://www.fabrikam.com/path", It.IsAny())) + .ReturnsAsync(true); + + mock.Setup(manager => manager.HasRequirementAsync(application, + Requirements.Features.ProofKeyForCodeExchange, It.IsAny())) + .ReturnsAsync(true); + }); + + await using var server = await CreateServerAsync(options => + { + options.SetRevocationEndpointUris(Array.Empty()); + options.DisableAuthorizationStorage(); + options.DisableTokenStorage(); + options.DisableSlidingRefreshTokenExpiration(); + + options.Services.AddSingleton(manager); + + options.AddEventHandler(builder => + builder.UseInlineHandler(context => + { + context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer")) + .SetClaim(Claims.Subject, "Bob le Magnifique"); + + return default; + })); + }); + + await using var client = await server.CreateClientAsync(); + + // Act + var response = await client.PostAsync("/connect/authorize", new OpenIddictRequest + { + ClientId = "Fabrikam", + RedirectUri = "http://www.fabrikam.com/path", + ResponseType = ResponseTypes.Token + }); + + // Assert + Assert.Null(response.Code); + Assert.NotNull(response.AccessToken); + + Mock.Get(manager).Verify(manager => manager.FindByClientIdAsync("Fabrikam", It.IsAny()), Times.AtLeastOnce()); + Mock.Get(manager).Verify(manager => manager.HasRequirementAsync(application, + Requirements.Features.ProofKeyForCodeExchange, It.IsAny()), Times.Never()); + } + [Theory] [InlineData("custom_error", null, null)] [InlineData("custom_error", "custom_description", null)] From fb90db89f67c35006bc5afc2618d28d1aca2d1b7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A9vin=20Chalet?= Date: Wed, 7 Apr 2021 12:30:10 +0200 Subject: [PATCH 7/8] Convert DateTimeOffset to DateTime outside EF queries to work around a limitation in the Oracle MySQL provider --- .../Stores/OpenIddictEntityFrameworkAuthorizationStore.cs | 8 +++++++- .../Stores/OpenIddictEntityFrameworkTokenStore.cs | 8 +++++++- .../OpenIddictEntityFrameworkCoreAuthorizationStore.cs | 8 +++++++- .../Stores/OpenIddictEntityFrameworkCoreTokenStore.cs | 8 +++++++- 4 files changed, 28 insertions(+), 4 deletions(-) diff --git a/src/OpenIddict.EntityFramework/Stores/OpenIddictEntityFrameworkAuthorizationStore.cs b/src/OpenIddict.EntityFramework/Stores/OpenIddictEntityFrameworkAuthorizationStore.cs index 65038a22..d8b7ab19 100644 --- a/src/OpenIddict.EntityFramework/Stores/OpenIddictEntityFrameworkAuthorizationStore.cs +++ b/src/OpenIddict.EntityFramework/Stores/OpenIddictEntityFrameworkAuthorizationStore.cs @@ -633,9 +633,15 @@ namespace OpenIddict.EntityFramework // and thus prevent them from being concurrently modified outside this block. using var transaction = CreateTransaction(); + // Note: the Oracle MySQL provider doesn't support DateTimeOffset and is unable + // to create a SQL query with an expression calling DateTimeOffset.UtcDateTime. + // To work around this limitation, the threshold represented as a DateTimeOffset + // instance is manually converted to a UTC DateTime instance outside the query. + var date = threshold.UtcDateTime; + var authorizations = await (from authorization in Authorizations.Include(authorization => authorization.Tokens) - where authorization.CreationDate < threshold.UtcDateTime + where authorization.CreationDate < date where authorization.Status != Statuses.Valid || (authorization.Type == AuthorizationTypes.AdHoc && !authorization.Tokens.Any()) orderby authorization.Id diff --git a/src/OpenIddict.EntityFramework/Stores/OpenIddictEntityFrameworkTokenStore.cs b/src/OpenIddict.EntityFramework/Stores/OpenIddictEntityFrameworkTokenStore.cs index 069c8b59..66598ec4 100644 --- a/src/OpenIddict.EntityFramework/Stores/OpenIddictEntityFrameworkTokenStore.cs +++ b/src/OpenIddict.EntityFramework/Stores/OpenIddictEntityFrameworkTokenStore.cs @@ -618,9 +618,15 @@ namespace OpenIddict.EntityFramework // and thus prevent them from being concurrently modified outside this block. using var transaction = CreateTransaction(); + // Note: the Oracle MySQL provider doesn't support DateTimeOffset and is unable + // to create a SQL query with an expression calling DateTimeOffset.UtcDateTime. + // To work around this limitation, the threshold represented as a DateTimeOffset + // instance is manually converted to a UTC DateTime instance outside the query. + var date = threshold.UtcDateTime; + var tokens = await (from token in Tokens - where token.CreationDate < threshold.UtcDateTime + where token.CreationDate < date where (token.Status != Statuses.Inactive && token.Status != Statuses.Valid) || (token.Authorization != null && token.Authorization.Status != Statuses.Valid) || token.ExpirationDate < DateTime.UtcNow diff --git a/src/OpenIddict.EntityFrameworkCore/Stores/OpenIddictEntityFrameworkCoreAuthorizationStore.cs b/src/OpenIddict.EntityFrameworkCore/Stores/OpenIddictEntityFrameworkCoreAuthorizationStore.cs index fc7da06a..265b2d8e 100644 --- a/src/OpenIddict.EntityFrameworkCore/Stores/OpenIddictEntityFrameworkCoreAuthorizationStore.cs +++ b/src/OpenIddict.EntityFrameworkCore/Stores/OpenIddictEntityFrameworkCoreAuthorizationStore.cs @@ -713,9 +713,15 @@ namespace OpenIddict.EntityFrameworkCore // and thus prevent them from being concurrently modified outside this block. using var transaction = await CreateTransactionAsync(); + // Note: the Oracle MySQL provider doesn't support DateTimeOffset and is unable + // to create a SQL query with an expression calling DateTimeOffset.UtcDateTime. + // To work around this limitation, the threshold represented as a DateTimeOffset + // instance is manually converted to a UTC DateTime instance outside the query. + var date = threshold.UtcDateTime; + var authorizations = await (from authorization in Authorizations.Include(authorization => authorization.Tokens).AsTracking() - where authorization.CreationDate < threshold.UtcDateTime + where authorization.CreationDate < date where authorization.Status != Statuses.Valid || (authorization.Type == AuthorizationTypes.AdHoc && !authorization.Tokens.Any()) orderby authorization.Id diff --git a/src/OpenIddict.EntityFrameworkCore/Stores/OpenIddictEntityFrameworkCoreTokenStore.cs b/src/OpenIddict.EntityFrameworkCore/Stores/OpenIddictEntityFrameworkCoreTokenStore.cs index 99cbd945..7ad98c28 100644 --- a/src/OpenIddict.EntityFrameworkCore/Stores/OpenIddictEntityFrameworkCoreTokenStore.cs +++ b/src/OpenIddict.EntityFrameworkCore/Stores/OpenIddictEntityFrameworkCoreTokenStore.cs @@ -681,9 +681,15 @@ namespace OpenIddict.EntityFrameworkCore // and thus prevent them from being concurrently modified outside this block. using var transaction = await CreateTransactionAsync(); + // Note: the Oracle MySQL provider doesn't support DateTimeOffset and is unable + // to create a SQL query with an expression calling DateTimeOffset.UtcDateTime. + // To work around this limitation, the threshold represented as a DateTimeOffset + // instance is manually converted to a UTC DateTime instance outside the query. + var date = threshold.UtcDateTime; + var tokens = await (from token in Tokens.AsTracking() - where token.CreationDate < threshold.UtcDateTime + where token.CreationDate < date where (token.Status != Statuses.Inactive && token.Status != Statuses.Valid) || (token.Authorization != null && token.Authorization.Status != Statuses.Valid) || token.ExpirationDate < DateTime.UtcNow From 57d592322546b12277fc2709d77e7e6c7b94274f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A9vin=20Chalet?= Date: Wed, 7 Apr 2021 13:05:07 +0200 Subject: [PATCH 8/8] Update Versions.props to build 3.0.3 packages --- eng/Versions.props | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/eng/Versions.props b/eng/Versions.props index 766331a0..3fa86bdd 100644 --- a/eng/Versions.props +++ b/eng/Versions.props @@ -3,7 +3,7 @@ 3 0 - 2 + 3 $(MajorVersion).$(MinorVersion).$(PatchVersion) rtm