Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Use TimeProvider #2071

Merged
merged 30 commits into from
May 12, 2024
Merged
Show file tree
Hide file tree
Changes from 14 commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
304f05a
Use TimeProvider
trejjam May 10, 2024
a76a3f3
Use TimeProvider.GetUtcNow
trejjam May 10, 2024
67d9874
Set TimeProvider if not set
trejjam May 10, 2024
6d11cb0
Replace DateTime.Now with TimeProvider.GetUtcNow().DateTime
trejjam May 10, 2024
1262d0f
Fix tests
trejjam May 10, 2024
972b4fa
Replace forgotten DateTimeOffset.UtcNow
trejjam May 10, 2024
7fca60e
Fix XML doc
trejjam May 10, 2024
ea0d2b7
Return duplicated space
trejjam May 10, 2024
51c8188
Drop TimeProvider from tests
trejjam May 10, 2024
252850b
Inline GetUtcNow extension method
trejjam May 10, 2024
c95824d
Implement AddDevelopment*Certificate(subject, DateTimeOffset) as exte…
trejjam May 10, 2024
eeede96
Drop IOptionWithTimeProvider
trejjam May 10, 2024
3907ec3
Drop TimeProviderExtensions
trejjam May 10, 2024
893826e
Drop TimeProvider set in tests
trejjam May 10, 2024
3fa99dd
Fix CS
trejjam May 10, 2024
1a31bd3
Try to resolve TimeProvider from container
trejjam May 10, 2024
a0fa491
Rollback samples changes
trejjam May 10, 2024
b9e64eb
Use DateTime in AddDevelopment*Certificate
trejjam May 10, 2024
5d130ba
Fix OpenIddictQuartzConfigurationTests
trejjam May 10, 2024
83a86c2
Fix CS
trejjam May 10, 2024
0557320
Use TimeProvider in AddDevelopment*Certificate
trejjam May 10, 2024
f4ef680
Use DateTimeOffset.LocalDateTime
trejjam May 10, 2024
28321dc
Fix AddDevelopmentEncryptionCertificate_ThrowsAnExceptionOnUnsupporte…
trejjam May 10, 2024
a8e0fa7
Add spaces
trejjam May 10, 2024
6195f80
Use now instead of notBefore
trejjam May 10, 2024
b163d3b
Make IServiceProvider non-nullable
trejjam May 10, 2024
44eecb5
Fix XML doc
trejjam May 10, 2024
8c24ddc
Add missing inheritdoc
trejjam May 10, 2024
3716666
Use .LocalDateTime fot working with certificate
trejjam May 10, 2024
77f0cad
Add a parameterless constructor to OpenIddictQuartzConfiguration and …
kevinchalet May 12, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,12 @@
using System.Web.Mvc;
using Microsoft.AspNet.Identity;
using Microsoft.AspNet.Identity.Owin;
using Microsoft.Extensions.Options;
using Microsoft.Owin.Security;
using OpenIddict.Abstractions;
using OpenIddict.Client;
using OpenIddict.Client.Owin;
using OpenIddict.Core;
using OpenIddict.Sandbox.AspNet.Server.Helpers;
using OpenIddict.Sandbox.AspNet.Server.ViewModels.Authorization;
using OpenIddict.Server.Owin;
Expand All @@ -32,17 +34,20 @@
private readonly IOpenIddictAuthorizationManager _authorizationManager;
private readonly OpenIddictClientService _clientService;
private readonly IOpenIddictScopeManager _scopeManager;
private readonly IOptionsSnapshot<OpenIddictCoreOptions> _openIddictCoreOptions;

public AuthorizationController(
IOpenIddictApplicationManager applicationManager,
IOpenIddictAuthorizationManager authorizationManager,
OpenIddictClientService clientService,
IOpenIddictScopeManager scopeManager)
IOpenIddictScopeManager scopeManager,
IOptionsSnapshot<OpenIddictCoreOptions> openIddictCoreOptions)
{
_applicationManager = applicationManager;
_authorizationManager = authorizationManager;
_clientService = clientService;
_scopeManager = scopeManager;
_openIddictCoreOptions = openIddictCoreOptions;
}

[HttpGet, Route("~/connect/authorize")]
Expand All @@ -57,7 +62,7 @@
// If the user principal can't be extracted or the cookie is too old, redirect the user to the login page.
var result = await context.Authentication.AuthenticateAsync(DefaultAuthenticationTypes.ApplicationCookie);
if (result?.Identity == null || (request.MaxAge != null && result.Properties?.IssuedUtc != null &&
DateTimeOffset.UtcNow - result.Properties.IssuedUtc > TimeSpan.FromSeconds(request.MaxAge.Value)))
_openIddictCoreOptions.Value.GetUtcNow() - result.Properties.IssuedUtc > TimeSpan.FromSeconds(request.MaxAge.Value)))

Check failure on line 65 in sandbox/OpenIddict.Sandbox.AspNet.Server/Controllers/AuthorizationController.cs

View workflow job for this annotation

GitHub Actions / build-windows-latest

'OpenIddictCoreOptions' does not contain a definition for 'GetUtcNow' and no accessible extension method 'GetUtcNow' accepting a first argument of type 'OpenIddictCoreOptions' could be found (are you missing a using directive or an assembly reference?)

Check failure on line 65 in sandbox/OpenIddict.Sandbox.AspNet.Server/Controllers/AuthorizationController.cs

View workflow job for this annotation

GitHub Actions / build-windows-latest

'OpenIddictCoreOptions' does not contain a definition for 'GetUtcNow' and no accessible extension method 'GetUtcNow' accepting a first argument of type 'OpenIddictCoreOptions' could be found (are you missing a using directive or an assembly reference?)
trejjam marked this conversation as resolved.
Show resolved Hide resolved
{
// For applications that want to allow the client to select the external authentication provider
// that will be used to authenticate the user, the identity_provider parameter can be used for that.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,13 @@
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Options;
using Microsoft.Extensions.Primitives;
using Microsoft.IdentityModel.Tokens;
using OpenIddict.Abstractions;
using OpenIddict.Client;
using OpenIddict.Client.AspNetCore;
using OpenIddict.Core;
using OpenIddict.Sandbox.AspNetCore.Server.Helpers;
using OpenIddict.Sandbox.AspNetCore.Server.Models;
using OpenIddict.Sandbox.AspNetCore.Server.ViewModels.Authorization;
Expand All @@ -32,21 +34,24 @@ public class AuthorizationController : Controller
private readonly IOpenIddictScopeManager _scopeManager;
private readonly SignInManager<ApplicationUser> _signInManager;
private readonly UserManager<ApplicationUser> _userManager;
private readonly IOptionsSnapshot<OpenIddictCoreOptions> _openIddictCoreOptions;

public AuthorizationController(
IOpenIddictApplicationManager applicationManager,
IOpenIddictAuthorizationManager authorizationManager,
OpenIddictClientService clientService,
IOpenIddictScopeManager scopeManager,
SignInManager<ApplicationUser> signInManager,
UserManager<ApplicationUser> userManager)
UserManager<ApplicationUser> userManager,
IOptionsSnapshot<OpenIddictCoreOptions> openIddictCoreOptions)
{
_applicationManager = applicationManager;
_authorizationManager = authorizationManager;
_clientService = clientService;
_scopeManager = scopeManager;
_signInManager = signInManager;
_userManager = userManager;
_openIddictCoreOptions = openIddictCoreOptions;
}

#region Authorization code, implicit and hybrid flows
Expand All @@ -73,7 +78,13 @@ public async Task<IActionResult> Authorize()
var result = await HttpContext.AuthenticateAsync();
if (result == null || !result.Succeeded || request.HasPrompt(Prompts.Login) ||
(request.MaxAge != null && result.Properties?.IssuedUtc != null &&
DateTimeOffset.UtcNow - result.Properties.IssuedUtc > TimeSpan.FromSeconds(request.MaxAge.Value)))
(
#if SUPPORTS_TIME_PROVIDER
_openIddictCoreOptions.Value.TimeProvider?.GetUtcNow() ??
#endif
DateTimeOffset.UtcNow
)
- result.Properties.IssuedUtc > TimeSpan.FromSeconds(request.MaxAge.Value)))
{
// If the client application requested promptless authentication,
// return an error indicating that the user is not logged in.
Expand Down
34 changes: 28 additions & 6 deletions src/OpenIddict.Client/OpenIddictClientBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -193,13 +193,24 @@ public OpenIddictClientBuilder AddDevelopmentEncryptionCertificate()
/// </summary>
/// <param name="subject">The subject name associated with the certificate.</param>
/// <returns>The <see cref="OpenIddictClientBuilder"/> instance.</returns>
public OpenIddictClientBuilder AddDevelopmentEncryptionCertificate(X500DistinguishedName subject)
public OpenIddictClientBuilder AddDevelopmentEncryptionCertificate(X500DistinguishedName subject) =>
AddDevelopmentEncryptionCertificate(subject, DateTimeOffset.UtcNow);

/// <summary>
/// Registers (and generates if necessary) a user-specific development encryption certificate.
/// </summary>
/// <param name="subject">The subject name associated with the certificate.</param>
/// <param name="notBefore">The NotBefore of the certificate.</param>
/// <returns>The <see cref="OpenIddictClientBuilder"/> instance.</returns>
public OpenIddictClientBuilder AddDevelopmentEncryptionCertificate(X500DistinguishedName subject, DateTimeOffset notBefore)
{
if (subject is null)
{
throw new ArgumentNullException(nameof(subject));
}

var notBeforeDateTime = notBefore.DateTime;
trejjam marked this conversation as resolved.
Show resolved Hide resolved

using var store = new X509Store(StoreName.My, StoreLocation.CurrentUser);
store.Open(OpenFlags.ReadWrite);

Expand All @@ -209,15 +220,15 @@ public OpenIddictClientBuilder AddDevelopmentEncryptionCertificate(X500Distingui
.OfType<X509Certificate2>()
.ToList();

if (!certificates.Exists(static certificate => certificate.NotBefore < DateTime.Now && certificate.NotAfter > DateTime.Now))
if (!certificates.Exists(certificate => certificate.NotBefore < notBeforeDateTime && certificate.NotAfter > notBeforeDateTime))
{
#if SUPPORTS_CERTIFICATE_GENERATION
using var algorithm = OpenIddictHelpers.CreateRsaKey(size: 2048);

var request = new CertificateRequest(subject, algorithm, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1);
request.CertificateExtensions.Add(new X509KeyUsageExtension(X509KeyUsageFlags.KeyEncipherment, critical: true));

var certificate = request.CreateSelfSigned(DateTimeOffset.UtcNow, DateTimeOffset.UtcNow.AddYears(2));
var certificate = request.CreateSelfSigned(notBeforeDateTime, notBeforeDateTime.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.
Expand Down Expand Up @@ -556,13 +567,24 @@ public OpenIddictClientBuilder AddDevelopmentSigningCertificate()
/// </summary>
/// <param name="subject">The subject name associated with the certificate.</param>
/// <returns>The <see cref="OpenIddictClientBuilder"/> instance.</returns>
public OpenIddictClientBuilder AddDevelopmentSigningCertificate(X500DistinguishedName subject)
public OpenIddictClientBuilder AddDevelopmentSigningCertificate(X500DistinguishedName subject) =>
AddDevelopmentSigningCertificate(subject, DateTimeOffset.UtcNow);

/// <summary>
/// Registers (and generates if necessary) a user-specific development signing certificate.
/// </summary>
/// <param name="subject">The subject name associated with the certificate.</param>
/// <param name="notBefore">The NotBefore of the certificate.</param>
/// <returns>The <see cref="OpenIddictClientBuilder"/> instance.</returns>
public OpenIddictClientBuilder AddDevelopmentSigningCertificate(X500DistinguishedName subject, DateTimeOffset notBefore)
{
if (subject is null)
{
throw new ArgumentNullException(nameof(subject));
}

var notBeforeDateTime = notBefore.DateTime;

using var store = new X509Store(StoreName.My, StoreLocation.CurrentUser);
store.Open(OpenFlags.ReadWrite);

Expand All @@ -572,15 +594,15 @@ public OpenIddictClientBuilder AddDevelopmentSigningCertificate(X500Distinguishe
.OfType<X509Certificate2>()
.ToList();

if (!certificates.Exists(static certificate => certificate.NotBefore < DateTime.Now && certificate.NotAfter > DateTime.Now))
if (!certificates.Exists(certificate => certificate.NotBefore < notBeforeDateTime && certificate.NotAfter > notBeforeDateTime))
{
#if SUPPORTS_CERTIFICATE_GENERATION
using var algorithm = OpenIddictHelpers.CreateRsaKey(size: 2048);

var request = new CertificateRequest(subject, algorithm, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1);
request.CertificateExtensions.Add(new X509KeyUsageExtension(X509KeyUsageFlags.DigitalSignature, critical: true));

var certificate = request.CreateSelfSigned(DateTimeOffset.UtcNow, DateTimeOffset.UtcNow.AddYears(2));
var certificate = request.CreateSelfSigned(notBeforeDateTime, notBeforeDateTime.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.
Expand Down
25 changes: 20 additions & 5 deletions src/OpenIddict.Client/OpenIddictClientConfiguration.cs
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,13 @@ public void PostConfigure(string? name, OpenIddictClientOptions options)
throw new InvalidOperationException(SR.GetResourceString(SR.ID0075));
}

#if SUPPORTS_TIME_PROVIDER
if (options.TimeProvider is null)
{
options.TimeProvider = TimeProvider.System;
trejjam marked this conversation as resolved.
Show resolved Hide resolved
}
#endif

foreach (var registration in options.Registrations)
{
if (registration.Issuer is null)
Expand Down Expand Up @@ -212,9 +219,17 @@ public void PostConfigure(string? name, OpenIddictClientOptions options)
// Sort the handlers collection using the order associated with each handler.
options.Handlers.Sort((left, right) => left.Order.CompareTo(right.Order));

var now = (
#if SUPPORTS_TIME_PROVIDER
options.TimeProvider?.GetUtcNow() ??
trejjam marked this conversation as resolved.
Show resolved Hide resolved
#endif
DateTimeOffset.UtcNow
)
.DateTime;

// Sort the encryption and signing credentials.
options.EncryptionCredentials.Sort((left, right) => Compare(left.Key, right.Key));
options.SigningCredentials.Sort((left, right) => Compare(left.Key, right.Key));
options.EncryptionCredentials.Sort((left, right) => Compare(left.Key, right.Key, now));
options.SigningCredentials.Sort((left, right) => Compare(left.Key, right.Key, now));

// Generate a key identifier for the encryption/signing keys that don't already have one.
foreach (var key in options.EncryptionCredentials.Select(credentials => credentials.Key)
Expand All @@ -234,7 +249,7 @@ public void PostConfigure(string? name, OpenIddictClientOptions options)
from credentials in options.EncryptionCredentials
select credentials.Key;

static int Compare(SecurityKey left, SecurityKey right) => (left, right) switch
static int Compare(SecurityKey left, SecurityKey right, DateTime now) => (left, right) switch
{
// If the two keys refer to the same instances, return 0.
(SecurityKey first, SecurityKey second) when ReferenceEquals(first, second) => 0,
Expand All @@ -245,8 +260,8 @@ public void PostConfigure(string? name, OpenIddictClientOptions options)
(SecurityKey, SymmetricSecurityKey) => 1,

// If one of the keys is backed by a X.509 certificate, don't prefer it if it's not valid yet.
(X509SecurityKey first, SecurityKey) when first.Certificate.NotBefore > DateTime.Now => 1,
(SecurityKey, X509SecurityKey second) when second.Certificate.NotBefore > DateTime.Now => -1,
(X509SecurityKey first, SecurityKey) when first.Certificate.NotBefore > now => 1,
(SecurityKey, X509SecurityKey second) when second.Certificate.NotBefore > now => -1,

// If the two keys are backed by a X.509 certificate, prefer the one with the furthest expiration date.
(X509SecurityKey first, X509SecurityKey second) => -first.Certificate.NotAfter.CompareTo(second.Certificate.NotAfter),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -295,7 +295,11 @@ public ValueTask HandleAsync(HandleIntrospectionResponseContext context)
if (long.TryParse((string?) context.Response[Claims.ExpiresAt],
NumberStyles.Integer, CultureInfo.InvariantCulture, out var value) &&
DateTimeOffset.FromUnixTimeSeconds(value) is DateTimeOffset date &&
date.Add(context.Registration.TokenValidationParameters.ClockSkew) < DateTimeOffset.UtcNow)
date.Add(context.Registration.TokenValidationParameters.ClockSkew) < (
#if SUPPORTS_TIME_PROVIDER
context.Options.TimeProvider?.GetUtcNow() ??
#endif
DateTimeOffset.UtcNow))
{
context.Reject(
error: Errors.ServerError,
Expand Down
6 changes: 5 additions & 1 deletion src/OpenIddict.Client/OpenIddictClientHandlers.Protection.cs
Original file line number Diff line number Diff line change
Expand Up @@ -653,7 +653,11 @@ public ValueTask HandleAsync(ValidateTokenContext context)
Debug.Assert(context.Principal is { Identity: ClaimsIdentity }, SR.GetResourceString(SR.ID4006));

var date = context.Principal.GetExpirationDate();
if (date.HasValue && date.Value.Add(context.TokenValidationParameters.ClockSkew) < DateTimeOffset.UtcNow)
if (date.HasValue && date.Value.Add(context.TokenValidationParameters.ClockSkew) < (
#if SUPPORTS_TIME_PROVIDER
context.Options.TimeProvider?.GetUtcNow() ??
#endif
DateTimeOffset.UtcNow))
{
context.Reject(
error: Errors.InvalidToken,
Expand Down