Browse Source

Introduce application settings and support configuring token lifetimes per client

pull/1879/head
Kévin Chalet 2 years ago
parent
commit
1c382d90ba
  1. 6
      sandbox/OpenIddict.Sandbox.AspNet.Server/Startup.cs
  2. 5
      sandbox/OpenIddict.Sandbox.AspNetCore.Server/Worker.cs
  3. 5
      src/OpenIddict.Abstractions/Descriptors/OpenIddictApplicationDescriptor.cs
  4. 11
      src/OpenIddict.Abstractions/Managers/IOpenIddictApplicationManager.cs
  5. 44
      src/OpenIddict.Abstractions/OpenIddictConstants.cs
  6. 21
      src/OpenIddict.Abstractions/Stores/IOpenIddictApplicationStore.cs
  7. 31
      src/OpenIddict.Core/Managers/OpenIddictApplicationManager.cs
  8. 6
      src/OpenIddict.EntityFramework.Models/OpenIddictEntityFrameworkApplication.cs
  9. 80
      src/OpenIddict.EntityFramework/Stores/OpenIddictEntityFrameworkApplicationStore.cs
  10. 6
      src/OpenIddict.EntityFrameworkCore.Models/OpenIddictEntityFrameworkCoreApplication.cs
  11. 80
      src/OpenIddict.EntityFrameworkCore/Stores/OpenIddictEntityFrameworkCoreApplicationStore.cs
  12. 7
      src/OpenIddict.MongoDb.Models/OpenIddictMongoDbApplication.cs
  13. 37
      src/OpenIddict.MongoDb/Stores/OpenIddictMongoDbApplicationStore.cs
  14. 285
      src/OpenIddict.Server/OpenIddictServerHandlers.cs
  15. 18
      test/OpenIddict.Server.IntegrationTests/OpenIddictServerIntegrationTests.Authentication.cs
  16. 6
      test/OpenIddict.Server.IntegrationTests/OpenIddictServerIntegrationTests.Device.cs
  17. 15
      test/OpenIddict.Server.IntegrationTests/OpenIddictServerIntegrationTests.Exchange.cs
  18. 12
      test/OpenIddict.Server.IntegrationTests/OpenIddictServerIntegrationTests.cs

6
sandbox/OpenIddict.Sandbox.AspNet.Server/Startup.cs

@ -1,4 +1,5 @@
using System;
using System.Globalization;
using System.Threading.Tasks;
using System.Web.Http;
using System.Web.Mvc;
@ -237,6 +238,11 @@ namespace OpenIddict.Sandbox.AspNet.Server
Permissions.Scopes.Email,
Permissions.Scopes.Profile,
Permissions.Scopes.Roles
},
Settings =
{
// Use a shorter access token lifetime for tokens issued to the Postman application.
[Settings.TokenLifetimes.AccessToken] = TimeSpan.FromMinutes(10).ToString("c", CultureInfo.InvariantCulture)
}
});
}

5
sandbox/OpenIddict.Sandbox.AspNetCore.Server/Worker.cs

@ -228,6 +228,11 @@ public class Worker : IHostedService
Permissions.Scopes.Email,
Permissions.Scopes.Profile,
Permissions.Scopes.Roles
},
Settings =
{
// Use a shorter access token lifetime for tokens issued to the Postman application.
[Settings.TokenLifetimes.AccessToken] = TimeSpan.FromMinutes(10).ToString("c", CultureInfo.InvariantCulture)
}
});
}

5
src/OpenIddict.Abstractions/Descriptors/OpenIddictApplicationDescriptor.cs

@ -71,6 +71,11 @@ public class OpenIddictApplicationDescriptor
/// </summary>
public HashSet<string> Requirements { get; } = new(StringComparer.Ordinal);
/// <summary>
/// Gets the settings associated with the application.
/// </summary>
public Dictionary<string, string> Settings { get; } = new(StringComparer.Ordinal);
/// <summary>
/// Gets or sets the client type associated with the application.
/// </summary>

11
src/OpenIddict.Abstractions/Managers/IOpenIddictApplicationManager.cs

@ -318,6 +318,17 @@ public interface IOpenIddictApplicationManager
/// </returns>
ValueTask<ImmutableArray<string>> GetRequirementsAsync(object application, CancellationToken cancellationToken = default);
/// <summary>
/// Retrieves the settings associated with an application.
/// </summary>
/// <param name="application">The application.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="ValueTask{TResult}"/> that can be used to monitor the asynchronous operation,
/// whose result returns all the settings associated with the application.
/// </returns>
ValueTask<ImmutableDictionary<string, string>> GetSettingsAsync(object application, CancellationToken cancellationToken = default);
/// <summary>
/// Determines whether a given application has the specified application type.
/// </summary>

44
src/OpenIddict.Abstractions/OpenIddictConstants.cs

@ -457,6 +457,23 @@ public static class OpenIddictConstants
public const string Token = "token";
}
public static class Schemes
{
public const string Basic = "Basic";
public const string Bearer = "Bearer";
}
public static class Scopes
{
public const string Address = "address";
public const string Email = "email";
public const string OfflineAccess = "offline_access";
public const string OpenId = "openid";
public const string Phone = "phone";
public const string Profile = "profile";
public const string Roles = "roles";
}
public static class Separators
{
public static readonly char[] Ampersand = { '&' };
@ -471,21 +488,22 @@ public static class OpenIddictConstants
public static readonly char[] Space = { ' ' };
}
public static class Schemes
public static class Settings
{
public const string Basic = "Basic";
public const string Bearer = "Bearer";
}
public static class Prefixes
{
public const string TokenLifetime = "tkn_lft:";
}
public static class Scopes
{
public const string Address = "address";
public const string Email = "email";
public const string OfflineAccess = "offline_access";
public const string OpenId = "openid";
public const string Phone = "phone";
public const string Profile = "profile";
public const string Roles = "roles";
public static class TokenLifetimes
{
public const string AccessToken = "tkn_lft:act";
public const string AuthorizationCode = "tkn_lft:auc";
public const string DeviceCode = "tkn_lft:dvc";
public const string IdentityToken = "tkn_lft:idt";
public const string RefreshToken = "tkn_lft:reft";
public const string UserCode = "tkn_lft:usrc";
}
}
public static class Statuses

21
src/OpenIddict.Abstractions/Stores/IOpenIddictApplicationStore.cs

@ -256,6 +256,17 @@ public interface IOpenIddictApplicationStore<TApplication> where TApplication :
/// </returns>
ValueTask<ImmutableArray<string>> GetRequirementsAsync(TApplication application, CancellationToken cancellationToken);
/// <summary>
/// Retrieves the settings associated with an application.
/// </summary>
/// <param name="application">The application.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="ValueTask{TResult}"/> that can be used to monitor the asynchronous operation,
/// whose result returns all the settings associated with the application.
/// </returns>
ValueTask<ImmutableDictionary<string, string>> GetSettingsAsync(TApplication application, CancellationToken cancellationToken);
/// <summary>
/// Instantiates a new application.
/// </summary>
@ -400,6 +411,16 @@ public interface IOpenIddictApplicationStore<TApplication> where TApplication :
/// <returns>A <see cref="ValueTask"/> that can be used to monitor the asynchronous operation.</returns>
ValueTask SetRequirementsAsync(TApplication application, ImmutableArray<string> requirements, CancellationToken cancellationToken);
/// <summary>
/// Sets the settings associated with an application.
/// </summary>
/// <param name="application">The application.</param>
/// <param name="settings">The settings associated with the application.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>A <see cref="ValueTask"/> that can be used to monitor the asynchronous operation.</returns>
ValueTask SetSettingsAsync(TApplication application,
ImmutableDictionary<string, string> settings, CancellationToken cancellationToken);
/// <summary>
/// Updates an existing application.
/// </summary>

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

@ -770,6 +770,26 @@ public class OpenIddictApplicationManager<TApplication> : IOpenIddictApplication
return Store.GetRequirementsAsync(application, cancellationToken);
}
/// <summary>
/// Retrieves the settings associated with an application.
/// </summary>
/// <param name="application">The application.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="ValueTask{TResult}"/> that can be used to monitor the asynchronous operation,
/// whose result returns all the settings associated with the application.
/// </returns>
public virtual ValueTask<ImmutableDictionary<string, string>> GetSettingsAsync(
TApplication application, CancellationToken cancellationToken = default)
{
if (application is null)
{
throw new ArgumentNullException(nameof(application));
}
return Store.GetSettingsAsync(application, cancellationToken);
}
/// <summary>
/// Determines whether a given application has the specified application type.
/// </summary>
@ -971,6 +991,7 @@ public class OpenIddictApplicationManager<TApplication> : IOpenIddictApplication
await Store.SetRedirectUrisAsync(application, ImmutableArray.CreateRange(
descriptor.RedirectUris.Select(uri => uri.OriginalString)), cancellationToken);
await Store.SetRequirementsAsync(application, descriptor.Requirements.ToImmutableArray(), cancellationToken);
await Store.SetSettingsAsync(application, descriptor.Settings.ToImmutableDictionary(), cancellationToken);
}
/// <summary>
@ -1054,6 +1075,12 @@ public class OpenIddictApplicationManager<TApplication> : IOpenIddictApplication
descriptor.RedirectUris.Add(value);
}
descriptor.Settings.Clear();
foreach (var pair in await Store.GetSettingsAsync(application, cancellationToken))
{
descriptor.Settings.Add(pair.Key, pair.Value);
}
}
/// <summary>
@ -1763,6 +1790,10 @@ public class OpenIddictApplicationManager<TApplication> : IOpenIddictApplication
ValueTask<ImmutableArray<string>> IOpenIddictApplicationManager.GetRequirementsAsync(object application, CancellationToken cancellationToken)
=> GetRequirementsAsync((TApplication) application, cancellationToken);
/// <inheritdoc/>
ValueTask<ImmutableDictionary<string, string>> IOpenIddictApplicationManager.GetSettingsAsync(object application, CancellationToken cancellationToken)
=> GetSettingsAsync((TApplication) application, cancellationToken);
/// <inheritdoc/>
ValueTask<bool> IOpenIddictApplicationManager.HasApplicationTypeAsync(object application, string type, CancellationToken cancellationToken)
=> HasApplicationTypeAsync((TApplication) application, type, cancellationToken);

6
src/OpenIddict.EntityFramework.Models/OpenIddictEntityFrameworkApplication.cs

@ -120,6 +120,12 @@ public class OpenIddictEntityFrameworkApplication<TKey, TAuthorization, TToken>
[StringSyntax(StringSyntaxAttribute.Json)]
public virtual string? Requirements { get; set; }
/// <summary>
/// Gets or sets the settings serialized as a JSON object.
/// </summary>
[StringSyntax(StringSyntaxAttribute.Json)]
public virtual string? Settings { get; set; }
/// <summary>
/// Gets the list of the tokens associated with this application.
/// </summary>

80
src/OpenIddict.EntityFramework/Stores/OpenIddictEntityFrameworkApplicationStore.cs

@ -634,6 +634,47 @@ public class OpenIddictEntityFrameworkApplicationStore<TApplication, TAuthorizat
return new(requirements);
}
/// <inheritdoc/>
public virtual ValueTask<ImmutableDictionary<string, string>> GetSettingsAsync(TApplication application, CancellationToken cancellationToken)
{
if (application is null)
{
throw new ArgumentNullException(nameof(application));
}
if (string.IsNullOrEmpty(application.Settings))
{
return new(ImmutableDictionary.Create<string, string>());
}
// Note: parsing the stringified settings is an expensive operation.
// To mitigate that, the resulting object is stored in the memory cache.
var key = string.Concat("492ea63f-c26f-47ea-bf9b-b0a0c3d02656", "\x1e", application.Settings);
var settings = Cache.GetOrCreate(key, entry =>
{
entry.SetPriority(CacheItemPriority.High)
.SetSlidingExpiration(TimeSpan.FromMinutes(1));
using var document = JsonDocument.Parse(application.Settings);
var builder = ImmutableDictionary.CreateBuilder<string, string>();
foreach (var property in document.RootElement.EnumerateObject())
{
var value = property.Value.GetString();
if (string.IsNullOrEmpty(value))
{
continue;
}
builder[property.Name] = value;
}
return builder.ToImmutable();
})!;
return new(settings);
}
/// <inheritdoc/>
public virtual ValueTask<TApplication> InstantiateAsync(CancellationToken cancellationToken)
{
@ -988,6 +1029,45 @@ public class OpenIddictEntityFrameworkApplicationStore<TApplication, TAuthorizat
return default;
}
/// <inheritdoc/>
public virtual ValueTask SetSettingsAsync(TApplication application,
ImmutableDictionary<string, string> settings, CancellationToken cancellationToken)
{
if (application is null)
{
throw new ArgumentNullException(nameof(application));
}
if (settings is not { Count: > 0 })
{
application.Settings = null;
return default;
}
using var stream = new MemoryStream();
using var writer = new Utf8JsonWriter(stream, new JsonWriterOptions
{
Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping,
Indented = false
});
writer.WriteStartObject();
foreach (var setting in settings)
{
writer.WritePropertyName(setting.Key);
writer.WriteStringValue(setting.Value);
}
writer.WriteEndObject();
writer.Flush();
application.Settings = Encoding.UTF8.GetString(stream.ToArray());
return default;
}
/// <inheritdoc/>
public virtual async ValueTask UpdateAsync(TApplication application, CancellationToken cancellationToken)
{

6
src/OpenIddict.EntityFrameworkCore.Models/OpenIddictEntityFrameworkCoreApplication.cs

@ -128,6 +128,12 @@ public class OpenIddictEntityFrameworkCoreApplication<TKey, TAuthorization, TTok
[StringSyntax(StringSyntaxAttribute.Json)]
public virtual string? Requirements { get; set; }
/// <summary>
/// Gets or sets the settings serialized as a JSON object.
/// </summary>
[StringSyntax(StringSyntaxAttribute.Json)]
public virtual string? Settings { get; set; }
/// <summary>
/// Gets the list of the tokens associated with this application.
/// </summary>

80
src/OpenIddict.EntityFrameworkCore/Stores/OpenIddictEntityFrameworkCoreApplicationStore.cs

@ -676,6 +676,47 @@ public class OpenIddictEntityFrameworkCoreApplicationStore<TApplication, TAuthor
return new(requirements);
}
/// <inheritdoc/>
public virtual ValueTask<ImmutableDictionary<string, string>> GetSettingsAsync(TApplication application, CancellationToken cancellationToken)
{
if (application is null)
{
throw new ArgumentNullException(nameof(application));
}
if (string.IsNullOrEmpty(application.Settings))
{
return new(ImmutableDictionary.Create<string, string>());
}
// Note: parsing the stringified settings is an expensive operation.
// To mitigate that, the resulting object is stored in the memory cache.
var key = string.Concat("492ea63f-c26f-47ea-bf9b-b0a0c3d02656", "\x1e", application.Settings);
var settings = Cache.GetOrCreate(key, entry =>
{
entry.SetPriority(CacheItemPriority.High)
.SetSlidingExpiration(TimeSpan.FromMinutes(1));
using var document = JsonDocument.Parse(application.Settings);
var builder = ImmutableDictionary.CreateBuilder<string, string>();
foreach (var property in document.RootElement.EnumerateObject())
{
var value = property.Value.GetString();
if (string.IsNullOrEmpty(value))
{
continue;
}
builder[property.Name] = value;
}
return builder.ToImmutable();
})!;
return new(settings);
}
/// <inheritdoc/>
public virtual ValueTask<TApplication> InstantiateAsync(CancellationToken cancellationToken)
{
@ -1029,6 +1070,45 @@ public class OpenIddictEntityFrameworkCoreApplicationStore<TApplication, TAuthor
return default;
}
/// <inheritdoc/>
public virtual ValueTask SetSettingsAsync(TApplication application,
ImmutableDictionary<string, string> settings, CancellationToken cancellationToken)
{
if (application is null)
{
throw new ArgumentNullException(nameof(application));
}
if (settings is not { Count: > 0 })
{
application.Settings = null;
return default;
}
using var stream = new MemoryStream();
using var writer = new Utf8JsonWriter(stream, new JsonWriterOptions
{
Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping,
Indented = false
});
writer.WriteStartObject();
foreach (var setting in settings)
{
writer.WritePropertyName(setting.Key);
writer.WriteStringValue(setting.Value);
}
writer.WriteEndObject();
writer.Flush();
application.Settings = Encoding.UTF8.GetString(stream.ToArray());
return default;
}
/// <inheritdoc/>
public virtual async ValueTask UpdateAsync(TApplication application, CancellationToken cancellationToken)
{

7
src/OpenIddict.MongoDb.Models/OpenIddictMongoDbApplication.cs

@ -101,4 +101,11 @@ public class OpenIddictMongoDbApplication
/// </summary>
[BsonElement("requirements"), BsonIgnoreIfNull]
public virtual IReadOnlyList<string>? Requirements { get; set; } = ImmutableList.Create<string>();
/// <summary>
/// Gets or sets the settings associated with the current application.
/// </summary>
[BsonElement("settings"), BsonIgnoreIfNull]
public virtual IReadOnlyDictionary<string, string>? Settings { get; set; }
= ImmutableDictionary.Create<string, string>();
}

37
src/OpenIddict.MongoDb/Stores/OpenIddictMongoDbApplicationStore.cs

@ -386,6 +386,22 @@ public class OpenIddictMongoDbApplicationStore<TApplication> : IOpenIddictApplic
return new(application.Requirements.ToImmutableArray());
}
/// <inheritdoc/>
public virtual ValueTask<ImmutableDictionary<string, string>> GetSettingsAsync(TApplication application, CancellationToken cancellationToken)
{
if (application is null)
{
throw new ArgumentNullException(nameof(application));
}
if (application.Settings is not { Count: > 0 })
{
return new(ImmutableDictionary.Create<string, string>());
}
return new(application.Settings.ToImmutableDictionary());
}
/// <inheritdoc/>
public virtual ValueTask<TApplication> InstantiateAsync(CancellationToken cancellationToken)
{
@ -679,6 +695,27 @@ public class OpenIddictMongoDbApplicationStore<TApplication> : IOpenIddictApplic
return default;
}
/// <inheritdoc/>
public virtual ValueTask SetSettingsAsync(TApplication application,
ImmutableDictionary<string, string> settings, CancellationToken cancellationToken)
{
if (application is null)
{
throw new ArgumentNullException(nameof(application));
}
if (settings is not { Count: > 0 })
{
application.Settings = null;
return default;
}
application.Settings = settings;
return default;
}
/// <inheritdoc/>
public virtual async ValueTask UpdateAsync(TApplication application, CancellationToken cancellationToken)
{

285
src/OpenIddict.Server/OpenIddictServerHandlers.cs

@ -7,9 +7,12 @@
using System.Collections.Immutable;
using System.ComponentModel;
using System.Diagnostics;
using System.Globalization;
using System.Security.Claims;
using System.Text;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using Microsoft.IdentityModel.Tokens;
using OpenIddict.Extensions;
@ -1868,19 +1871,34 @@ public static partial class OpenIddictServerHandlers
/// </summary>
public sealed class PrepareAccessTokenPrincipal : IOpenIddictServerHandler<ProcessSignInContext>
{
private readonly IOpenIddictApplicationManager? _applicationManager;
public PrepareAccessTokenPrincipal(IOpenIddictApplicationManager? applicationManager = null)
=> _applicationManager = applicationManager;
/// <summary>
/// Gets the default descriptor definition assigned to this handler.
/// </summary>
public static OpenIddictServerHandlerDescriptor Descriptor { get; }
= OpenIddictServerHandlerDescriptor.CreateBuilder<ProcessSignInContext>()
.AddFilter<RequireAccessTokenGenerated>()
.UseSingletonHandler<PrepareAccessTokenPrincipal>()
.UseScopedHandler<PrepareAccessTokenPrincipal>(static provider =>
{
// Note: the application manager is only resolved if the degraded mode was not enabled to ensure
// invalid core configuration exceptions are not thrown even if the managers were registered.
var options = provider.GetRequiredService<IOptionsMonitor<OpenIddictServerOptions>>().CurrentValue;
return options.EnableDegradedMode ?
new PrepareAccessTokenPrincipal() :
new PrepareAccessTokenPrincipal(provider.GetService<IOpenIddictApplicationManager>() ??
throw new InvalidOperationException(SR.GetResourceString(SR.ID0016)));
})
.SetOrder(AttachAuthorization.Descriptor.Order + 1_000)
.SetType(OpenIddictServerHandlerType.BuiltIn)
.Build();
/// <inheritdoc/>
public ValueTask HandleAsync(ProcessSignInContext context)
public async ValueTask HandleAsync(ProcessSignInContext context)
{
if (context is null)
{
@ -1950,7 +1968,31 @@ public static partial class OpenIddictServerHandlers
principal.SetCreationDate(DateTimeOffset.UtcNow);
var lifetime = context.Principal.GetAccessTokenLifetime() ?? context.Options.AccessTokenLifetime;
// If a specific token lifetime was attached to the principal, prefer it over any other value.
var lifetime = context.Principal.GetAccessTokenLifetime();
// If the client to which the token is returned is known, use the attached setting if available.
if (lifetime is null && !context.Options.EnableDegradedMode && !string.IsNullOrEmpty(context.ClientId))
{
if (_applicationManager is null)
{
throw new InvalidOperationException(SR.GetResourceString(SR.ID0016));
}
var application = await _applicationManager.FindByClientIdAsync(context.ClientId) ??
throw new InvalidOperationException(SR.GetResourceString(SR.ID0017));
var settings = await _applicationManager.GetSettingsAsync(application);
if (settings.TryGetValue(Settings.TokenLifetimes.AccessToken, out string? setting) &&
TimeSpan.TryParse(setting, CultureInfo.InvariantCulture, out var value))
{
lifetime = value;
}
}
// Otherwise, fall back to the global value.
lifetime ??= context.Options.AccessTokenLifetime;
if (lifetime.HasValue)
{
principal.SetExpirationDate(principal.GetCreationDate() + lifetime.Value);
@ -1978,8 +2020,6 @@ public static partial class OpenIddictServerHandlers
}
context.AccessTokenPrincipal = principal;
return default;
}
}
@ -1989,19 +2029,34 @@ public static partial class OpenIddictServerHandlers
/// </summary>
public sealed class PrepareAuthorizationCodePrincipal : IOpenIddictServerHandler<ProcessSignInContext>
{
private readonly IOpenIddictApplicationManager? _applicationManager;
public PrepareAuthorizationCodePrincipal(IOpenIddictApplicationManager? applicationManager = null)
=> _applicationManager = applicationManager;
/// <summary>
/// Gets the default descriptor definition assigned to this handler.
/// </summary>
public static OpenIddictServerHandlerDescriptor Descriptor { get; }
= OpenIddictServerHandlerDescriptor.CreateBuilder<ProcessSignInContext>()
.AddFilter<RequireAuthorizationCodeGenerated>()
.UseSingletonHandler<PrepareAuthorizationCodePrincipal>()
.UseScopedHandler<PrepareAuthorizationCodePrincipal>(static provider =>
{
// Note: the application manager is only resolved if the degraded mode was not enabled to ensure
// invalid core configuration exceptions are not thrown even if the managers were registered.
var options = provider.GetRequiredService<IOptionsMonitor<OpenIddictServerOptions>>().CurrentValue;
return options.EnableDegradedMode ?
new PrepareAuthorizationCodePrincipal() :
new PrepareAuthorizationCodePrincipal(provider.GetService<IOpenIddictApplicationManager>() ??
throw new InvalidOperationException(SR.GetResourceString(SR.ID0016)));
})
.SetOrder(PrepareAccessTokenPrincipal.Descriptor.Order + 1_000)
.SetType(OpenIddictServerHandlerType.BuiltIn)
.Build();
/// <inheritdoc/>
public ValueTask HandleAsync(ProcessSignInContext context)
public async ValueTask HandleAsync(ProcessSignInContext context)
{
if (context is null)
{
@ -2037,7 +2092,31 @@ public static partial class OpenIddictServerHandlers
principal.SetCreationDate(DateTimeOffset.UtcNow);
var lifetime = context.Principal.GetAuthorizationCodeLifetime() ?? context.Options.AuthorizationCodeLifetime;
// If a specific token lifetime was attached to the principal, prefer it over any other value.
var lifetime = context.Principal.GetAuthorizationCodeLifetime();
// If the client to which the token is returned is known, use the attached setting if available.
if (lifetime is null && !context.Options.EnableDegradedMode && !string.IsNullOrEmpty(context.ClientId))
{
if (_applicationManager is null)
{
throw new InvalidOperationException(SR.GetResourceString(SR.ID0016));
}
var application = await _applicationManager.FindByClientIdAsync(context.ClientId) ??
throw new InvalidOperationException(SR.GetResourceString(SR.ID0017));
var settings = await _applicationManager.GetSettingsAsync(application);
if (settings.TryGetValue(Settings.TokenLifetimes.AuthorizationCode, out string? setting) &&
TimeSpan.TryParse(setting, CultureInfo.InvariantCulture, out var value))
{
lifetime = value;
}
}
// Otherwise, fall back to the global value.
lifetime ??= context.Options.AuthorizationCodeLifetime;
if (lifetime.HasValue)
{
principal.SetExpirationDate(principal.GetCreationDate() + lifetime.Value);
@ -2067,8 +2146,6 @@ public static partial class OpenIddictServerHandlers
principal.SetClaim(Claims.Private.Nonce, context.Request.Nonce);
context.AuthorizationCodePrincipal = principal;
return default;
}
}
@ -2078,19 +2155,34 @@ public static partial class OpenIddictServerHandlers
/// </summary>
public sealed class PrepareDeviceCodePrincipal : IOpenIddictServerHandler<ProcessSignInContext>
{
private readonly IOpenIddictApplicationManager? _applicationManager;
public PrepareDeviceCodePrincipal(IOpenIddictApplicationManager? applicationManager = null)
=> _applicationManager = applicationManager;
/// <summary>
/// Gets the default descriptor definition assigned to this handler.
/// </summary>
public static OpenIddictServerHandlerDescriptor Descriptor { get; }
= OpenIddictServerHandlerDescriptor.CreateBuilder<ProcessSignInContext>()
.AddFilter<RequireDeviceCodeGenerated>()
.UseSingletonHandler<PrepareDeviceCodePrincipal>()
.UseScopedHandler<PrepareDeviceCodePrincipal>(static provider =>
{
// Note: the application manager is only resolved if the degraded mode was not enabled to ensure
// invalid core configuration exceptions are not thrown even if the managers were registered.
var options = provider.GetRequiredService<IOptionsMonitor<OpenIddictServerOptions>>().CurrentValue;
return options.EnableDegradedMode ?
new PrepareDeviceCodePrincipal() :
new PrepareDeviceCodePrincipal(provider.GetService<IOpenIddictApplicationManager>() ??
throw new InvalidOperationException(SR.GetResourceString(SR.ID0016)));
})
.SetOrder(PrepareAuthorizationCodePrincipal.Descriptor.Order + 1_000)
.SetType(OpenIddictServerHandlerType.BuiltIn)
.Build();
/// <inheritdoc/>
public ValueTask HandleAsync(ProcessSignInContext context)
public async ValueTask HandleAsync(ProcessSignInContext context)
{
if (context is null)
{
@ -2126,7 +2218,31 @@ public static partial class OpenIddictServerHandlers
principal.SetCreationDate(DateTimeOffset.UtcNow);
var lifetime = context.Principal.GetDeviceCodeLifetime() ?? context.Options.DeviceCodeLifetime;
// If a specific token lifetime was attached to the principal, prefer it over any other value.
var lifetime = context.Principal.GetDeviceCodeLifetime();
// If the client to which the token is returned is known, use the attached setting if available.
if (lifetime is null && !context.Options.EnableDegradedMode && !string.IsNullOrEmpty(context.ClientId))
{
if (_applicationManager is null)
{
throw new InvalidOperationException(SR.GetResourceString(SR.ID0016));
}
var application = await _applicationManager.FindByClientIdAsync(context.ClientId) ??
throw new InvalidOperationException(SR.GetResourceString(SR.ID0017));
var settings = await _applicationManager.GetSettingsAsync(application);
if (settings.TryGetValue(Settings.TokenLifetimes.DeviceCode, out string? setting) &&
TimeSpan.TryParse(setting, CultureInfo.InvariantCulture, out var value))
{
lifetime = value;
}
}
// Otherwise, fall back to the global value.
lifetime ??= context.Options.DeviceCodeLifetime;
if (lifetime.HasValue)
{
principal.SetExpirationDate(principal.GetCreationDate() + lifetime.Value);
@ -2143,8 +2259,6 @@ public static partial class OpenIddictServerHandlers
}
context.DeviceCodePrincipal = principal;
return default;
}
}
@ -2154,19 +2268,34 @@ public static partial class OpenIddictServerHandlers
/// </summary>
public sealed class PrepareRefreshTokenPrincipal : IOpenIddictServerHandler<ProcessSignInContext>
{
private readonly IOpenIddictApplicationManager? _applicationManager;
public PrepareRefreshTokenPrincipal(IOpenIddictApplicationManager? applicationManager = null)
=> _applicationManager = applicationManager;
/// <summary>
/// Gets the default descriptor definition assigned to this handler.
/// </summary>
public static OpenIddictServerHandlerDescriptor Descriptor { get; }
= OpenIddictServerHandlerDescriptor.CreateBuilder<ProcessSignInContext>()
.AddFilter<RequireRefreshTokenGenerated>()
.UseSingletonHandler<PrepareRefreshTokenPrincipal>()
.UseScopedHandler<PrepareRefreshTokenPrincipal>(static provider =>
{
// Note: the application manager is only resolved if the degraded mode was not enabled to ensure
// invalid core configuration exceptions are not thrown even if the managers were registered.
var options = provider.GetRequiredService<IOptionsMonitor<OpenIddictServerOptions>>().CurrentValue;
return options.EnableDegradedMode ?
new PrepareRefreshTokenPrincipal() :
new PrepareRefreshTokenPrincipal(provider.GetService<IOpenIddictApplicationManager>() ??
throw new InvalidOperationException(SR.GetResourceString(SR.ID0016)));
})
.SetOrder(PrepareDeviceCodePrincipal.Descriptor.Order + 1_000)
.SetType(OpenIddictServerHandlerType.BuiltIn)
.Build();
/// <inheritdoc/>
public ValueTask HandleAsync(ProcessSignInContext context)
public async ValueTask HandleAsync(ProcessSignInContext context)
{
if (context is null)
{
@ -2219,7 +2348,31 @@ public static partial class OpenIddictServerHandlers
else
{
var lifetime = context.Principal.GetRefreshTokenLifetime() ?? context.Options.RefreshTokenLifetime;
// If a specific token lifetime was attached to the principal, prefer it over any other value.
var lifetime = context.Principal.GetRefreshTokenLifetime();
// If the client to which the token is returned is known, use the attached setting if available.
if (lifetime is null && !context.Options.EnableDegradedMode && !string.IsNullOrEmpty(context.ClientId))
{
if (_applicationManager is null)
{
throw new InvalidOperationException(SR.GetResourceString(SR.ID0016));
}
var application = await _applicationManager.FindByClientIdAsync(context.ClientId) ??
throw new InvalidOperationException(SR.GetResourceString(SR.ID0017));
var settings = await _applicationManager.GetSettingsAsync(application);
if (settings.TryGetValue(Settings.TokenLifetimes.RefreshToken, out string? setting) &&
TimeSpan.TryParse(setting, CultureInfo.InvariantCulture, out var value))
{
lifetime = value;
}
}
// Otherwise, fall back to the global value.
lifetime ??= context.Options.RefreshTokenLifetime;
if (lifetime.HasValue)
{
principal.SetExpirationDate(principal.GetCreationDate() + lifetime.Value);
@ -2230,8 +2383,6 @@ public static partial class OpenIddictServerHandlers
principal.SetClaim(Claims.Private.Issuer, (context.Options.Issuer ?? context.BaseUri)?.AbsoluteUri);
context.RefreshTokenPrincipal = principal;
return default;
}
}
@ -2241,19 +2392,34 @@ public static partial class OpenIddictServerHandlers
/// </summary>
public sealed class PrepareIdentityTokenPrincipal : IOpenIddictServerHandler<ProcessSignInContext>
{
private readonly IOpenIddictApplicationManager? _applicationManager;
public PrepareIdentityTokenPrincipal(IOpenIddictApplicationManager? applicationManager = null)
=> _applicationManager = applicationManager;
/// <summary>
/// Gets the default descriptor definition assigned to this handler.
/// </summary>
public static OpenIddictServerHandlerDescriptor Descriptor { get; }
= OpenIddictServerHandlerDescriptor.CreateBuilder<ProcessSignInContext>()
.AddFilter<RequireIdentityTokenGenerated>()
.UseSingletonHandler<PrepareIdentityTokenPrincipal>()
.UseScopedHandler<PrepareIdentityTokenPrincipal>(static provider =>
{
// Note: the application manager is only resolved if the degraded mode was not enabled to ensure
// invalid core configuration exceptions are not thrown even if the managers were registered.
var options = provider.GetRequiredService<IOptionsMonitor<OpenIddictServerOptions>>().CurrentValue;
return options.EnableDegradedMode ?
new PrepareIdentityTokenPrincipal() :
new PrepareIdentityTokenPrincipal(provider.GetService<IOpenIddictApplicationManager>() ??
throw new InvalidOperationException(SR.GetResourceString(SR.ID0016)));
})
.SetOrder(PrepareRefreshTokenPrincipal.Descriptor.Order + 1_000)
.SetType(OpenIddictServerHandlerType.BuiltIn)
.Build();
/// <inheritdoc/>
public ValueTask HandleAsync(ProcessSignInContext context)
public async ValueTask HandleAsync(ProcessSignInContext context)
{
if (context is null)
{
@ -2316,7 +2482,31 @@ public static partial class OpenIddictServerHandlers
principal.SetCreationDate(DateTimeOffset.UtcNow);
var lifetime = context.Principal.GetIdentityTokenLifetime() ?? context.Options.IdentityTokenLifetime;
// If a specific token lifetime was attached to the principal, prefer it over any other value.
var lifetime = context.Principal.GetIdentityTokenLifetime();
// If the client to which the token is returned is known, use the attached setting if available.
if (lifetime is null && !context.Options.EnableDegradedMode && !string.IsNullOrEmpty(context.ClientId))
{
if (_applicationManager is null)
{
throw new InvalidOperationException(SR.GetResourceString(SR.ID0016));
}
var application = await _applicationManager.FindByClientIdAsync(context.ClientId) ??
throw new InvalidOperationException(SR.GetResourceString(SR.ID0017));
var settings = await _applicationManager.GetSettingsAsync(application);
if (settings.TryGetValue(Settings.TokenLifetimes.IdentityToken, out string? setting) &&
TimeSpan.TryParse(setting, CultureInfo.InvariantCulture, out var value))
{
lifetime = value;
}
}
// Otherwise, fall back to the global value.
lifetime ??= context.Options.IdentityTokenLifetime;
if (lifetime.HasValue)
{
principal.SetExpirationDate(principal.GetCreationDate() + lifetime.Value);
@ -2345,8 +2535,6 @@ public static partial class OpenIddictServerHandlers
});
context.IdentityTokenPrincipal = principal;
return default;
}
}
@ -2356,19 +2544,34 @@ public static partial class OpenIddictServerHandlers
/// </summary>
public sealed class PrepareUserCodePrincipal : IOpenIddictServerHandler<ProcessSignInContext>
{
private readonly IOpenIddictApplicationManager? _applicationManager;
public PrepareUserCodePrincipal(IOpenIddictApplicationManager? applicationManager = null)
=> _applicationManager = applicationManager;
/// <summary>
/// Gets the default descriptor definition assigned to this handler.
/// </summary>
public static OpenIddictServerHandlerDescriptor Descriptor { get; }
= OpenIddictServerHandlerDescriptor.CreateBuilder<ProcessSignInContext>()
.AddFilter<RequireUserCodeGenerated>()
.UseSingletonHandler<PrepareUserCodePrincipal>()
.UseScopedHandler<PrepareUserCodePrincipal>(static provider =>
{
// Note: the application manager is only resolved if the degraded mode was not enabled to ensure
// invalid core configuration exceptions are not thrown even if the managers were registered.
var options = provider.GetRequiredService<IOptionsMonitor<OpenIddictServerOptions>>().CurrentValue;
return options.EnableDegradedMode ?
new PrepareUserCodePrincipal() :
new PrepareUserCodePrincipal(provider.GetService<IOpenIddictApplicationManager>() ??
throw new InvalidOperationException(SR.GetResourceString(SR.ID0016)));
})
.SetOrder(PrepareIdentityTokenPrincipal.Descriptor.Order + 1_000)
.SetType(OpenIddictServerHandlerType.BuiltIn)
.Build();
/// <inheritdoc/>
public ValueTask HandleAsync(ProcessSignInContext context)
public async ValueTask HandleAsync(ProcessSignInContext context)
{
if (context is null)
{
@ -2404,7 +2607,31 @@ public static partial class OpenIddictServerHandlers
principal.SetCreationDate(DateTimeOffset.UtcNow);
var lifetime = context.Principal.GetUserCodeLifetime() ?? context.Options.UserCodeLifetime;
// If a specific token lifetime was attached to the principal, prefer it over any other value.
var lifetime = context.Principal.GetUserCodeLifetime();
// If the client to which the token is returned is known, use the attached setting if available.
if (lifetime is null && !context.Options.EnableDegradedMode && !string.IsNullOrEmpty(context.ClientId))
{
if (_applicationManager is null)
{
throw new InvalidOperationException(SR.GetResourceString(SR.ID0016));
}
var application = await _applicationManager.FindByClientIdAsync(context.ClientId) ??
throw new InvalidOperationException(SR.GetResourceString(SR.ID0017));
var settings = await _applicationManager.GetSettingsAsync(application);
if (settings.TryGetValue(Settings.TokenLifetimes.UserCode, out string? setting) &&
TimeSpan.TryParse(setting, CultureInfo.InvariantCulture, out var value))
{
lifetime = value;
}
}
// Otherwise, fall back to the global value.
lifetime ??= context.Options.UserCodeLifetime;
if (lifetime.HasValue)
{
principal.SetExpirationDate(principal.GetCreationDate() + lifetime.Value);
@ -2417,8 +2644,6 @@ public static partial class OpenIddictServerHandlers
principal.SetClaim(Claims.ClientId, context.Request.ClientId);
context.UserCodePrincipal = principal;
return default;
}
}

18
test/OpenIddict.Server.IntegrationTests/OpenIddictServerIntegrationTests.Authentication.cs

@ -852,6 +852,9 @@ public abstract partial class OpenIddictServerIntegrationTests
mock.Setup(manager => manager.HasClientTypeAsync(application, ClientTypes.Public, It.IsAny<CancellationToken>()))
.ReturnsAsync(true);
mock.Setup(manager => manager.GetSettingsAsync(application, It.IsAny<CancellationToken>()))
.ReturnsAsync(ImmutableDictionary.Create<string, string>());
}));
options.Services.AddSingleton(CreateScopeManager(mock =>
@ -1426,6 +1429,9 @@ public abstract partial class OpenIddictServerIntegrationTests
mock.Setup(manager => manager.ValidateRedirectUriAsync(application, "http://www.fabrikam.com/path", It.IsAny<CancellationToken>()))
.ReturnsAsync(true);
mock.Setup(manager => manager.GetSettingsAsync(application, It.IsAny<CancellationToken>()))
.ReturnsAsync(ImmutableDictionary.Create<string, string>());
});
await using var server = await CreateServerAsync(options =>
@ -1681,6 +1687,9 @@ public abstract partial class OpenIddictServerIntegrationTests
mock.Setup(manager => manager.HasRequirementAsync(application,
Requirements.Features.ProofKeyForCodeExchange, It.IsAny<CancellationToken>()))
.ReturnsAsync(false);
mock.Setup(manager => manager.GetSettingsAsync(application, It.IsAny<CancellationToken>()))
.ReturnsAsync(ImmutableDictionary.Create<string, string>());
});
await using var server = await CreateServerAsync(options =>
@ -1741,6 +1750,9 @@ public abstract partial class OpenIddictServerIntegrationTests
mock.Setup(manager => manager.HasRequirementAsync(application,
Requirements.Features.ProofKeyForCodeExchange, It.IsAny<CancellationToken>()))
.ReturnsAsync(true);
mock.Setup(manager => manager.GetSettingsAsync(application, It.IsAny<CancellationToken>()))
.ReturnsAsync(ImmutableDictionary.Create<string, string>());
});
await using var server = await CreateServerAsync(options =>
@ -1801,6 +1813,9 @@ public abstract partial class OpenIddictServerIntegrationTests
mock.Setup(manager => manager.HasRequirementAsync(application,
Requirements.Features.ProofKeyForCodeExchange, It.IsAny<CancellationToken>()))
.ReturnsAsync(true);
mock.Setup(manager => manager.GetSettingsAsync(application, It.IsAny<CancellationToken>()))
.ReturnsAsync(ImmutableDictionary.Create<string, string>());
});
await using var server = await CreateServerAsync(options =>
@ -1979,6 +1994,9 @@ public abstract partial class OpenIddictServerIntegrationTests
mock.Setup(manager => manager.HasPermissionAsync(application,
Permissions.Endpoints.Authorization, It.IsAny<CancellationToken>()))
.ReturnsAsync(true);
mock.Setup(manager => manager.GetSettingsAsync(application, It.IsAny<CancellationToken>()))
.ReturnsAsync(ImmutableDictionary.Create<string, string>());
});
await using var server = await CreateServerAsync(options =>

6
test/OpenIddict.Server.IntegrationTests/OpenIddictServerIntegrationTests.Device.cs

@ -242,6 +242,9 @@ public abstract partial class OpenIddictServerIntegrationTests
mock.Setup(manager => manager.HasClientTypeAsync(application, ClientTypes.Public, It.IsAny<CancellationToken>()))
.ReturnsAsync(true);
mock.Setup(manager => manager.GetSettingsAsync(application, It.IsAny<CancellationToken>()))
.ReturnsAsync(ImmutableDictionary.Create<string, string>());
}));
options.Services.AddSingleton(CreateTokenManager(mock =>
@ -315,6 +318,9 @@ public abstract partial class OpenIddictServerIntegrationTests
mock.Setup(manager => manager.HasClientTypeAsync(application, ClientTypes.Public, It.IsAny<CancellationToken>()))
.ReturnsAsync(true);
mock.Setup(manager => manager.GetSettingsAsync(application, It.IsAny<CancellationToken>()))
.ReturnsAsync(ImmutableDictionary.Create<string, string>());
}));
options.Services.AddSingleton(CreateTokenManager(mock =>

15
test/OpenIddict.Server.IntegrationTests/OpenIddictServerIntegrationTests.Exchange.cs

@ -1887,6 +1887,9 @@ public abstract partial class OpenIddictServerIntegrationTests
mock.Setup(manager => manager.HasRequirementAsync(application,
Requirements.Features.ProofKeyForCodeExchange, It.IsAny<CancellationToken>()))
.ReturnsAsync(false);
mock.Setup(manager => manager.GetSettingsAsync(application, It.IsAny<CancellationToken>()))
.ReturnsAsync(ImmutableDictionary.Create<string, string>());
});
await using var server = await CreateServerAsync(options =>
@ -1955,6 +1958,9 @@ public abstract partial class OpenIddictServerIntegrationTests
mock.Setup(manager => manager.HasRequirementAsync(application,
Requirements.Features.ProofKeyForCodeExchange, It.IsAny<CancellationToken>()))
.ReturnsAsync(false);
mock.Setup(manager => manager.GetSettingsAsync(application, It.IsAny<CancellationToken>()))
.ReturnsAsync(ImmutableDictionary.Create<string, string>());
});
await using var server = await CreateServerAsync(options =>
@ -2158,6 +2164,9 @@ public abstract partial class OpenIddictServerIntegrationTests
mock.Setup(manager => manager.HasClientTypeAsync(application, ClientTypes.Public, It.IsAny<CancellationToken>()))
.ReturnsAsync(true);
mock.Setup(manager => manager.GetSettingsAsync(application, It.IsAny<CancellationToken>()))
.ReturnsAsync(ImmutableDictionary.Create<string, string>());
}));
options.SetDeviceEndpointUris(Array.Empty<Uri>());
@ -3304,6 +3313,9 @@ public abstract partial class OpenIddictServerIntegrationTests
mock.Setup(manager => manager.HasClientTypeAsync(application, ClientTypes.Public, It.IsAny<CancellationToken>()))
.ReturnsAsync(true);
mock.Setup(manager => manager.GetSettingsAsync(application, It.IsAny<CancellationToken>()))
.ReturnsAsync(ImmutableDictionary.Create<string, string>());
}));
options.Services.AddSingleton(CreateTokenManager(mock =>
@ -3994,6 +4006,9 @@ public abstract partial class OpenIddictServerIntegrationTests
mock.Setup(manager => manager.ValidateClientSecretAsync(application, "7Fjfp0ZBr1KtDRbnfVdmIw", It.IsAny<CancellationToken>()))
.ReturnsAsync(true);
mock.Setup(manager => manager.GetSettingsAsync(application, It.IsAny<CancellationToken>()))
.ReturnsAsync(ImmutableDictionary.Create<string, string>());
}));
options.Services.AddSingleton(manager);

12
test/OpenIddict.Server.IntegrationTests/OpenIddictServerIntegrationTests.cs

@ -6,6 +6,8 @@
* the license and the contributors participating to this project.
*/
using System.Collections.Immutable;
using System.Globalization;
using System.Security.Claims;
using System.Text.Json;
using Microsoft.Extensions.DependencyInjection;
@ -3100,6 +3102,10 @@ public abstract partial class OpenIddictServerIntegrationTests
mock.Setup(manager => manager.HasClientTypeAsync(application, ClientTypes.Public, It.IsAny<CancellationToken>()))
.ReturnsAsync(true);
mock.Setup(manager => manager.GetSettingsAsync(application, It.IsAny<CancellationToken>()))
.ReturnsAsync(ImmutableDictionary.Create<string, string>()
.SetItem(Settings.TokenLifetimes.AccessToken, TimeSpan.FromMinutes(5).ToString("c", CultureInfo.InvariantCulture)));
}));
options.Services.AddSingleton(manager);
@ -3292,6 +3298,9 @@ public abstract partial class OpenIddictServerIntegrationTests
mock.Setup(manager => manager.GetIdAsync(application, It.IsAny<CancellationToken>()))
.ReturnsAsync("3E228451-1555-46F7-A471-951EFBA23A56");
mock.Setup(manager => manager.GetSettingsAsync(application, It.IsAny<CancellationToken>()))
.ReturnsAsync(ImmutableDictionary.Create<string, string>());
}));
options.Services.AddSingleton(CreateTokenManager(mock =>
@ -3369,6 +3378,9 @@ public abstract partial class OpenIddictServerIntegrationTests
mock.Setup(manager => manager.GetIdAsync(application, It.IsAny<CancellationToken>()))
.ReturnsAsync("3E228451-1555-46F7-A471-951EFBA23A56");
mock.Setup(manager => manager.GetSettingsAsync(application, It.IsAny<CancellationToken>()))
.ReturnsAsync(ImmutableDictionary.Create<string, string>());
}));
options.Services.AddSingleton(CreateTokenManager(mock =>

Loading…
Cancel
Save