Browse Source

Represent complex properties as strongly-typed CLR properties in the Entity Framework Core models

pull/2505/head
Kévin Chalet 2 months ago
parent
commit
50e741efa2
  1. 1
      Directory.Packages.props
  2. 1
      sandbox/OpenIddict.Sandbox.AspNetCore.Client/OpenIddict.Sandbox.AspNetCore.Client.csproj
  3. 6
      sandbox/OpenIddict.Sandbox.AspNetCore.Client/Program.cs
  4. 1
      sandbox/OpenIddict.Sandbox.AspNetCore.Server/OpenIddict.Sandbox.AspNetCore.Server.csproj
  5. 6
      sandbox/OpenIddict.Sandbox.AspNetCore.Server/Program.cs
  6. 2
      src/OpenIddict.Abstractions/Primitives/OpenIddictSerializer.cs
  7. 52
      src/OpenIddict.EntityFramework.Models/OpenIddictEntityFrameworkApplication.cs
  8. 22
      src/OpenIddict.EntityFramework.Models/OpenIddictEntityFrameworkAuthorization.cs
  9. 20
      src/OpenIddict.EntityFramework.Models/OpenIddictEntityFrameworkResource.cs
  10. 23
      src/OpenIddict.EntityFramework.Models/OpenIddictEntityFrameworkScope.cs
  11. 38
      src/OpenIddict.EntityFramework.Models/OpenIddictEntityFrameworkToken.cs
  12. 29
      src/OpenIddict.EntityFramework/Configurations/OpenIddictEntityFrameworkApplicationConfiguration.cs
  13. 10
      src/OpenIddict.EntityFramework/Configurations/OpenIddictEntityFrameworkAuthorizationConfiguration.cs
  14. 15
      src/OpenIddict.EntityFramework/Configurations/OpenIddictEntityFrameworkResourceConfiguration.cs
  15. 15
      src/OpenIddict.EntityFramework/Configurations/OpenIddictEntityFrameworkScopeConfiguration.cs
  16. 13
      src/OpenIddict.EntityFramework/Configurations/OpenIddictEntityFrameworkTokenConfiguration.cs
  17. 7
      src/OpenIddict.EntityFramework/Stores/OpenIddictEntityFrameworkAuthorizationStore.cs
  18. 21
      src/OpenIddict.EntityFramework/Stores/OpenIddictEntityFrameworkTokenStore.cs
  19. 4
      src/OpenIddict.EntityFrameworkCore.Models/OpenIddict.EntityFrameworkCore.Models.csproj
  20. 83
      src/OpenIddict.EntityFrameworkCore.Models/OpenIddictEntityFrameworkCoreApplication.cs
  21. 33
      src/OpenIddict.EntityFrameworkCore.Models/OpenIddictEntityFrameworkCoreAuthorization.cs
  22. 31
      src/OpenIddict.EntityFrameworkCore.Models/OpenIddictEntityFrameworkCoreResource.cs
  23. 37
      src/OpenIddict.EntityFrameworkCore.Models/OpenIddictEntityFrameworkCoreScope.cs
  24. 43
      src/OpenIddict.EntityFrameworkCore.Models/OpenIddictEntityFrameworkCoreToken.cs
  25. 36
      src/OpenIddict.EntityFrameworkCore/Configurations/OpenIddictEntityFrameworkCoreApplicationConfiguration.cs
  26. 16
      src/OpenIddict.EntityFrameworkCore/Configurations/OpenIddictEntityFrameworkCoreAuthorizationConfiguration.cs
  27. 26
      src/OpenIddict.EntityFrameworkCore/Configurations/OpenIddictEntityFrameworkCoreResourceConfiguration.cs
  28. 26
      src/OpenIddict.EntityFrameworkCore/Configurations/OpenIddictEntityFrameworkCoreScopeConfiguration.cs
  29. 16
      src/OpenIddict.EntityFrameworkCore/Configurations/OpenIddictEntityFrameworkCoreTokenConfiguration.cs
  30. 457
      src/OpenIddict.EntityFrameworkCore/Stores/OpenIddictEntityFrameworkCoreApplicationStore.cs
  31. 127
      src/OpenIddict.EntityFrameworkCore/Stores/OpenIddictEntityFrameworkCoreAuthorizationStore.cs
  32. 195
      src/OpenIddict.EntityFrameworkCore/Stores/OpenIddictEntityFrameworkCoreResourceStore.cs
  33. 266
      src/OpenIddict.EntityFrameworkCore/Stores/OpenIddictEntityFrameworkCoreScopeStore.cs
  34. 90
      src/OpenIddict.EntityFrameworkCore/Stores/OpenIddictEntityFrameworkCoreTokenStore.cs
  35. 40
      src/OpenIddict.MongoDb.Models/OpenIddictMongoDbApplication.cs
  36. 18
      src/OpenIddict.MongoDb.Models/OpenIddictMongoDbAuthorization.cs
  37. 16
      src/OpenIddict.MongoDb.Models/OpenIddictMongoDbResource.cs
  38. 18
      src/OpenIddict.MongoDb.Models/OpenIddictMongoDbScope.cs
  39. 37
      src/OpenIddict.MongoDb.Models/OpenIddictMongoDbToken.cs
  40. 7
      src/OpenIddict.MongoDb/Stores/OpenIddictMongoDbApplicationStore.cs
  41. 14
      src/OpenIddict.MongoDb/Stores/OpenIddictMongoDbAuthorizationStore.cs
  42. 35
      src/OpenIddict.MongoDb/Stores/OpenIddictMongoDbTokenStore.cs

1
Directory.Packages.props

@ -145,6 +145,7 @@
<PackageVersion Include="Dapplo.Microsoft.Extensions.Hosting.Wpf" Version="1.0.14" /> <PackageVersion Include="Dapplo.Microsoft.Extensions.Hosting.Wpf" Version="1.0.14" />
<PackageVersion Include="Microsoft.AspNetCore.Identity.EntityFrameworkCore" Version="10.0.10" /> <PackageVersion Include="Microsoft.AspNetCore.Identity.EntityFrameworkCore" Version="10.0.10" />
<PackageVersion Include="Microsoft.EntityFrameworkCore.Sqlite" Version="10.0.10" /> <PackageVersion Include="Microsoft.EntityFrameworkCore.Sqlite" Version="10.0.10" />
<PackageVersion Include="Microsoft.EntityFrameworkCore.SqlServer" Version="10.0.10" />
<PackageVersion Include="Microsoft.Extensions.Hosting" Version="10.0.10" /> <PackageVersion Include="Microsoft.Extensions.Hosting" Version="10.0.10" />
<PackageVersion Include="Microsoft.Maui.Controls" Version="10.0.80" /> <PackageVersion Include="Microsoft.Maui.Controls" Version="10.0.80" />
<PackageVersion Include="Microsoft.Maui.Controls.Compatibility" Version="10.0.80" /> <PackageVersion Include="Microsoft.Maui.Controls.Compatibility" Version="10.0.80" />

1
sandbox/OpenIddict.Sandbox.AspNetCore.Client/OpenIddict.Sandbox.AspNetCore.Client.csproj

@ -12,6 +12,7 @@
<ItemGroup> <ItemGroup>
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" /> <PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" />
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" />
<PackageReference Include="Quartz.Extensions.Hosting" /> <PackageReference Include="Quartz.Extensions.Hosting" />
</ItemGroup> </ItemGroup>

6
sandbox/OpenIddict.Sandbox.AspNetCore.Client/Program.cs

@ -14,6 +14,12 @@ var builder = WebApplication.CreateBuilder(args);
builder.Services.AddDbContext<ApplicationDbContext>(options => builder.Services.AddDbContext<ApplicationDbContext>(options =>
{ {
options.UseSqlite($"Filename={Path.Combine(Path.GetTempPath(), "openiddict-sandbox-aspnetcore-client.sqlite3")}"); options.UseSqlite($"Filename={Path.Combine(Path.GetTempPath(), "openiddict-sandbox-aspnetcore-client.sqlite3")}");
// Developers who prefer using Microsoft SQL Server instead of SQLite can remove
// the previous line and configure OpenIddict to use the specified database:
//
// options.UseSqlServer($"Server=(localdb)\\MSSQLLocalDB;Database=openiddict-sandbox-aspnetcore-client;Trusted_Connection=True");
options.UseOpenIddict(); options.UseOpenIddict();
}); });

1
sandbox/OpenIddict.Sandbox.AspNetCore.Server/OpenIddict.Sandbox.AspNetCore.Server.csproj

@ -15,6 +15,7 @@
<ItemGroup> <ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.Identity.EntityFrameworkCore" /> <PackageReference Include="Microsoft.AspNetCore.Identity.EntityFrameworkCore" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" /> <PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" />
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" />
<PackageReference Include="Quartz.Extensions.Hosting" /> <PackageReference Include="Quartz.Extensions.Hosting" />
</ItemGroup> </ItemGroup>

6
sandbox/OpenIddict.Sandbox.AspNetCore.Server/Program.cs

@ -20,6 +20,12 @@ builder.Services.AddMvc();
builder.Services.AddDbContext<ApplicationDbContext>(options => builder.Services.AddDbContext<ApplicationDbContext>(options =>
{ {
options.UseSqlite($"Filename={Path.Combine(Path.GetTempPath(), "openiddict-sandbox-aspnetcore-server.sqlite3")}"); options.UseSqlite($"Filename={Path.Combine(Path.GetTempPath(), "openiddict-sandbox-aspnetcore-server.sqlite3")}");
// Developers who prefer using Microsoft SQL Server instead of SQLite can remove
// the previous line and configure OpenIddict to use the specified database:
//
// options.UseSqlServer($"Server=(localdb)\\MSSQLLocalDB;Database=openiddict-sandbox-aspnetcore-server;Trusted_Connection=True");
options.UseOpenIddict(); options.UseOpenIddict();
}); });

2
src/OpenIddict.Abstractions/Primitives/OpenIddictSerializer.cs

@ -13,6 +13,8 @@ namespace OpenIddict.Abstractions;
/// </summary> /// </summary>
[EditorBrowsable(EditorBrowsableState.Never)] [EditorBrowsable(EditorBrowsableState.Never)]
[JsonSerializable(typeof(bool))] [JsonSerializable(typeof(bool))]
[JsonSerializable(typeof(IDictionary<string, JsonElement>))]
[JsonSerializable(typeof(IDictionary<string, string>))]
[JsonSerializable(typeof(JsonArray))] [JsonSerializable(typeof(JsonArray))]
[JsonSerializable(typeof(JsonElement))] [JsonSerializable(typeof(JsonElement))]
[JsonSerializable(typeof(JsonNode))] [JsonSerializable(typeof(JsonNode))]

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

@ -27,116 +27,112 @@ public class OpenIddictEntityFrameworkApplication<TKey, TAuthorization, TToken>
where TToken : class where TToken : class
{ {
/// <summary> /// <summary>
/// Gets or sets the application type associated with the current application. /// Gets or sets the application type of the application.
/// </summary> /// </summary>
public virtual string? ApplicationType { get; set; } public virtual string? ApplicationType { get; set; }
/// <summary> /// <summary>
/// Gets the list of the authorizations associated with this application. /// Gets the list of the authorizations associated with the application.
/// </summary> /// </summary>
public virtual ICollection<TAuthorization> Authorizations { get; } = new HashSet<TAuthorization>(); public virtual ICollection<TAuthorization> Authorizations { get; } = new HashSet<TAuthorization>();
/// <summary> /// <summary>
/// Gets or sets the client identifier associated with the current application. /// Gets or sets the client identifier of the application.
/// </summary> /// </summary>
public virtual string? ClientId { get; set; } public virtual string? ClientId { get; set; }
/// <summary> /// <summary>
/// Gets or sets the client secret associated with the current application. /// Gets or sets the client secret of the application.
/// Note: depending on the application manager used to create this instance,
/// this property may be hashed or encrypted for security reasons.
/// </summary> /// </summary>
/// <remarks> /// <remarks>
/// <para>
/// Note: depending on the application manager used to create this instance,
/// this property may be hashed or encrypted for security reasons.
/// </para>
/// <para>
/// Note: client authentication based on shared secrets is not recommended and should /// Note: client authentication based on shared secrets is not recommended and should
/// only be used for backward compatibility with legacy applications that only support /// only be used for backward compatibility with legacy applications that only support
/// client secrets. When possible, consider using public/private key pairs or TLS client /// client secrets. When possible, consider using public/private key pairs or TLS client
/// certificates instead, as these client authentication methods are significantly safer. /// certificates instead, as these client authentication methods are significantly safer.
/// </para>
/// </remarks> /// </remarks>
public virtual string? ClientSecret { get; set; } public virtual string? ClientSecret { get; set; }
/// <summary> /// <summary>
/// Gets or sets the client type associated with the current application. /// Gets or sets the client type of the application.
/// </summary> /// </summary>
public virtual string? ClientType { get; set; } public virtual string? ClientType { get; set; }
/// <summary> /// <summary>
/// Gets or sets the concurrency token. /// Gets or sets the concurrency token of the application.
/// </summary> /// </summary>
public virtual string? ConcurrencyToken { get; set; } = Guid.NewGuid().ToString(); public virtual string? ConcurrencyToken { get; set; } = Guid.NewGuid().ToString();
/// <summary> /// <summary>
/// Gets or sets the consent type associated with the current application. /// Gets or sets the consent type of the application.
/// </summary> /// </summary>
public virtual string? ConsentType { get; set; } public virtual string? ConsentType { get; set; }
/// <summary> /// <summary>
/// Gets or sets the display name associated with the current application. /// Gets or sets the display name of the application.
/// </summary> /// </summary>
public virtual string? DisplayName { get; set; } public virtual string? DisplayName { get; set; }
/// <summary> /// <summary>
/// Gets or sets the localized display names /// Gets or sets the localized display names of the application, serialized as a JSON object.
/// associated with the current application,
/// serialized as a JSON object.
/// </summary> /// </summary>
[StringSyntax(StringSyntaxAttribute.Json)] [StringSyntax(StringSyntaxAttribute.Json)]
public virtual string? DisplayNames { get; set; } public virtual string? DisplayNames { get; set; }
/// <summary> /// <summary>
/// Gets or sets the unique identifier associated with the current application. /// Gets or sets the unique identifier of the application.
/// </summary> /// </summary>
public virtual TKey? Id { get; set; } public virtual TKey? Id { get; set; }
/// <summary> /// <summary>
/// Gets or sets the JSON Web Key Set associated with /// Gets or sets the JSON Web Key Set of the application, serialized as a JSON object.
/// the application, serialized as a JSON object.
/// </summary> /// </summary>
[StringSyntax(StringSyntaxAttribute.Json)] [StringSyntax(StringSyntaxAttribute.Json)]
public virtual string? JsonWebKeySet { get; set; } public virtual string? JsonWebKeySet { get; set; }
/// <summary> /// <summary>
/// Gets or sets the permissions associated with the /// Gets or sets the permissions of the application, serialized as a JSON array.
/// current application, serialized as a JSON array.
/// </summary> /// </summary>
[StringSyntax(StringSyntaxAttribute.Json)] [StringSyntax(StringSyntaxAttribute.Json)]
public virtual string? Permissions { get; set; } public virtual string? Permissions { get; set; }
/// <summary> /// <summary>
/// Gets or sets the post-logout redirect URIs associated with /// Gets or sets the post-logout redirect URIs of the application, serialized as a JSON array.
/// the current application, serialized as a JSON array.
/// </summary> /// </summary>
[StringSyntax(StringSyntaxAttribute.Json)] [StringSyntax(StringSyntaxAttribute.Json)]
public virtual string? PostLogoutRedirectUris { get; set; } public virtual string? PostLogoutRedirectUris { get; set; }
/// <summary> /// <summary>
/// Gets or sets the additional properties serialized as a JSON object, /// Gets or sets the additional properties of the application, serialized as a JSON object.
/// or <see langword="null"/> if no bag was associated with the current application.
/// </summary> /// </summary>
[StringSyntax(StringSyntaxAttribute.Json)] [StringSyntax(StringSyntaxAttribute.Json)]
public virtual string? Properties { get; set; } public virtual string? Properties { get; set; }
/// <summary> /// <summary>
/// Gets or sets the redirect URIs associated with the /// Gets or sets the redirect URIs of the application, serialized as a JSON array.
/// current application, serialized as a JSON array.
/// </summary> /// </summary>
[StringSyntax(StringSyntaxAttribute.Json)] [StringSyntax(StringSyntaxAttribute.Json)]
public virtual string? RedirectUris { get; set; } public virtual string? RedirectUris { get; set; }
/// <summary> /// <summary>
/// Gets or sets the requirements associated with the /// Gets or sets the requirements of the application, serialized as a JSON array.
/// current application, serialized as a JSON array.
/// </summary> /// </summary>
[StringSyntax(StringSyntaxAttribute.Json)] [StringSyntax(StringSyntaxAttribute.Json)]
public virtual string? Requirements { get; set; } public virtual string? Requirements { get; set; }
/// <summary> /// <summary>
/// Gets or sets the settings serialized as a JSON object. /// Gets or sets the settings of the application, serialized as a JSON object.
/// </summary> /// </summary>
[StringSyntax(StringSyntaxAttribute.Json)] [StringSyntax(StringSyntaxAttribute.Json)]
public virtual string? Settings { get; set; } public virtual string? Settings { get; set; }
/// <summary> /// <summary>
/// Gets the list of the tokens associated with this application. /// Gets the list of the tokens associated with the application.
/// </summary> /// </summary>
public virtual ICollection<TToken> Tokens { get; } = new HashSet<TToken>(); public virtual ICollection<TToken> Tokens { get; } = new HashSet<TToken>();
} }

22
src/OpenIddict.EntityFramework.Models/OpenIddictEntityFrameworkAuthorization.cs

@ -27,56 +27,54 @@ public class OpenIddictEntityFrameworkAuthorization<TKey, TApplication, TToken>
where TToken : class where TToken : class
{ {
/// <summary> /// <summary>
/// Gets or sets the application associated with the current authorization. /// Gets or sets the application associated with the authorization.
/// </summary> /// </summary>
public virtual TApplication? Application { get; set; } public virtual TApplication? Application { get; set; }
/// <summary> /// <summary>
/// Gets or sets the concurrency token. /// Gets or sets the concurrency token of the authorization.
/// </summary> /// </summary>
public virtual string? ConcurrencyToken { get; set; } = Guid.NewGuid().ToString(); public virtual string? ConcurrencyToken { get; set; } = Guid.NewGuid().ToString();
/// <summary> /// <summary>
/// Gets or sets the UTC creation date of the current authorization. /// Gets or sets the UTC creation date of the authorization.
/// </summary> /// </summary>
public virtual DateTime? CreationDate { get; set; } public virtual DateTime? CreationDate { get; set; }
/// <summary> /// <summary>
/// Gets or sets the unique identifier associated with the current authorization. /// Gets or sets the unique identifier of the authorization.
/// </summary> /// </summary>
public virtual TKey? Id { get; set; } public virtual TKey? Id { get; set; }
/// <summary> /// <summary>
/// Gets or sets the additional properties serialized as a JSON object, /// Gets or sets the additional properties of the authorization, serialized as a JSON object.
/// or <see langword="null"/> if no bag was associated with the current authorization.
/// </summary> /// </summary>
[StringSyntax(StringSyntaxAttribute.Json)] [StringSyntax(StringSyntaxAttribute.Json)]
public virtual string? Properties { get; set; } public virtual string? Properties { get; set; }
/// <summary> /// <summary>
/// Gets or sets the scopes associated with the current /// Gets or sets the scopes of the authorization, serialized as a JSON array.
/// authorization, serialized as a JSON array.
/// </summary> /// </summary>
[StringSyntax(StringSyntaxAttribute.Json)] [StringSyntax(StringSyntaxAttribute.Json)]
public virtual string? Scopes { get; set; } public virtual string? Scopes { get; set; }
/// <summary> /// <summary>
/// Gets or sets the status of the current authorization. /// Gets or sets the status of the authorization.
/// </summary> /// </summary>
public virtual string? Status { get; set; } public virtual string? Status { get; set; }
/// <summary> /// <summary>
/// Gets or sets the subject associated with the current authorization. /// Gets or sets the subject of the authorization.
/// </summary> /// </summary>
public virtual string? Subject { get; set; } public virtual string? Subject { get; set; }
/// <summary> /// <summary>
/// Gets the list of tokens associated with the current authorization. /// Gets the list of tokens associated with the authorization.
/// </summary> /// </summary>
public virtual ICollection<TToken> Tokens { get; } = new HashSet<TToken>(); public virtual ICollection<TToken> Tokens { get; } = new HashSet<TToken>();
/// <summary> /// <summary>
/// Gets or sets the type of the current authorization. /// Gets or sets the type of the authorization.
/// </summary> /// </summary>
public virtual string? Type { get; set; } public virtual string? Type { get; set; }
} }

20
src/OpenIddict.EntityFramework.Models/OpenIddictEntityFrameworkResource.cs

@ -24,48 +24,44 @@ public class OpenIddictEntityFrameworkResource : OpenIddictEntityFrameworkResour
public class OpenIddictEntityFrameworkResource<TKey> where TKey : notnull, IEquatable<TKey> public class OpenIddictEntityFrameworkResource<TKey> where TKey : notnull, IEquatable<TKey>
{ {
/// <summary> /// <summary>
/// Gets or sets the concurrency token. /// Gets or sets the concurrency token of the resource.
/// </summary> /// </summary>
public virtual string? ConcurrencyToken { get; set; } = Guid.NewGuid().ToString(); public virtual string? ConcurrencyToken { get; set; } = Guid.NewGuid().ToString();
/// <summary> /// <summary>
/// Gets or sets the public description associated with the current resource. /// Gets or sets the public description of the resource.
/// </summary> /// </summary>
public virtual string? Description { get; set; } public virtual string? Description { get; set; }
/// <summary> /// <summary>
/// Gets or sets the localized public descriptions associated /// Gets or sets the localized public descriptions of the resource, serialized as a JSON object.
/// with the current resource, serialized as a JSON object.
/// </summary> /// </summary>
[StringSyntax(StringSyntaxAttribute.Json)] [StringSyntax(StringSyntaxAttribute.Json)]
public virtual string? Descriptions { get; set; } public virtual string? Descriptions { get; set; }
/// <summary> /// <summary>
/// Gets or sets the display name associated with the current resource. /// Gets or sets the display name of the resource.
/// </summary> /// </summary>
public virtual string? DisplayName { get; set; } public virtual string? DisplayName { get; set; }
/// <summary> /// <summary>
/// Gets or sets the localized display names /// Gets or sets the localized display names of the resource, serialized as a JSON object.
/// associated with the current application,
/// serialized as a JSON object.
/// </summary> /// </summary>
[StringSyntax(StringSyntaxAttribute.Json)] [StringSyntax(StringSyntaxAttribute.Json)]
public virtual string? DisplayNames { get; set; } public virtual string? DisplayNames { get; set; }
/// <summary> /// <summary>
/// Gets or sets the unique identifier associated with the current resource. /// Gets or sets the unique identifier of the resource.
/// </summary> /// </summary>
public virtual TKey? Id { get; set; } public virtual TKey? Id { get; set; }
/// <summary> /// <summary>
/// Gets or sets the unique name associated with the current resource. /// Gets or sets the unique name of the resource.
/// </summary> /// </summary>
public virtual string? Name { get; set; } public virtual string? Name { get; set; }
/// <summary> /// <summary>
/// Gets or sets the additional properties serialized as a JSON object, /// Gets or sets the additional properties of the resource, serialized as a JSON object.
/// or <see langword="null"/> if no bag was associated with the current resource.
/// </summary> /// </summary>
[StringSyntax(StringSyntaxAttribute.Json)] [StringSyntax(StringSyntaxAttribute.Json)]
public virtual string? Properties { get; set; } public virtual string? Properties { get; set; }

23
src/OpenIddict.EntityFramework.Models/OpenIddictEntityFrameworkScope.cs

@ -24,55 +24,50 @@ public class OpenIddictEntityFrameworkScope : OpenIddictEntityFrameworkScope<str
public class OpenIddictEntityFrameworkScope<TKey> where TKey : notnull, IEquatable<TKey> public class OpenIddictEntityFrameworkScope<TKey> where TKey : notnull, IEquatable<TKey>
{ {
/// <summary> /// <summary>
/// Gets or sets the concurrency token. /// Gets or sets the concurrency token of the scope.
/// </summary> /// </summary>
public virtual string? ConcurrencyToken { get; set; } = Guid.NewGuid().ToString(); public virtual string? ConcurrencyToken { get; set; } = Guid.NewGuid().ToString();
/// <summary> /// <summary>
/// Gets or sets the public description associated with the current scope. /// Gets or sets the public description of the scope.
/// </summary> /// </summary>
public virtual string? Description { get; set; } public virtual string? Description { get; set; }
/// <summary> /// <summary>
/// Gets or sets the localized public descriptions associated /// Gets or sets the localized public descriptions of the scope, serialized as a JSON object.
/// with the current scope, serialized as a JSON object.
/// </summary> /// </summary>
[StringSyntax(StringSyntaxAttribute.Json)] [StringSyntax(StringSyntaxAttribute.Json)]
public virtual string? Descriptions { get; set; } public virtual string? Descriptions { get; set; }
/// <summary> /// <summary>
/// Gets or sets the display name associated with the current scope. /// Gets or sets the display name of the scope.
/// </summary> /// </summary>
public virtual string? DisplayName { get; set; } public virtual string? DisplayName { get; set; }
/// <summary> /// <summary>
/// Gets or sets the localized display names /// Gets or sets the localized display names of the scope, serialized as a JSON object.
/// associated with the current application,
/// serialized as a JSON object.
/// </summary> /// </summary>
[StringSyntax(StringSyntaxAttribute.Json)] [StringSyntax(StringSyntaxAttribute.Json)]
public virtual string? DisplayNames { get; set; } public virtual string? DisplayNames { get; set; }
/// <summary> /// <summary>
/// Gets or sets the unique identifier associated with the current scope. /// Gets or sets the unique identifier of the scope.
/// </summary> /// </summary>
public virtual TKey? Id { get; set; } public virtual TKey? Id { get; set; }
/// <summary> /// <summary>
/// Gets or sets the unique name associated with the current scope. /// Gets or sets the unique name of the scope.
/// </summary> /// </summary>
public virtual string? Name { get; set; } public virtual string? Name { get; set; }
/// <summary> /// <summary>
/// Gets or sets the additional properties serialized as a JSON object, /// Gets or sets the additional properties of the scope, serialized as a JSON object.
/// or <see langword="null"/> if no bag was associated with the current scope.
/// </summary> /// </summary>
[StringSyntax(StringSyntaxAttribute.Json)] [StringSyntax(StringSyntaxAttribute.Json)]
public virtual string? Properties { get; set; } public virtual string? Properties { get; set; }
/// <summary> /// <summary>
/// Gets or sets the resources associated with the /// Gets or sets the resources of the scope, serialized as a JSON array.
/// current scope, serialized as a JSON array.
/// </summary> /// </summary>
[StringSyntax(StringSyntaxAttribute.Json)] [StringSyntax(StringSyntaxAttribute.Json)]
public virtual string? Resources { get; set; } public virtual string? Resources { get; set; }

38
src/OpenIddict.EntityFramework.Models/OpenIddictEntityFrameworkToken.cs

@ -27,74 +27,76 @@ public class OpenIddictEntityFrameworkToken<TKey, TApplication, TAuthorization>
where TAuthorization : class where TAuthorization : class
{ {
/// <summary> /// <summary>
/// Gets or sets the application associated with the current token. /// Gets or sets the application associated with the token.
/// </summary> /// </summary>
public virtual TApplication? Application { get; set; } public virtual TApplication? Application { get; set; }
/// <summary> /// <summary>
/// Gets or sets the authorization associated with the current token. /// Gets or sets the authorization associated with the token.
/// </summary> /// </summary>
public virtual TAuthorization? Authorization { get; set; } public virtual TAuthorization? Authorization { get; set; }
/// <summary> /// <summary>
/// Gets or sets the concurrency token. /// Gets or sets the concurrency token of the token.
/// </summary> /// </summary>
public virtual string? ConcurrencyToken { get; set; } = Guid.NewGuid().ToString(); public virtual string? ConcurrencyToken { get; set; } = Guid.NewGuid().ToString();
/// <summary> /// <summary>
/// Gets or sets the UTC creation date of the current token. /// Gets or sets the UTC creation date of the token.
/// </summary> /// </summary>
public virtual DateTime? CreationDate { get; set; } public virtual DateTime? CreationDate { get; set; }
/// <summary> /// <summary>
/// Gets or sets the UTC expiration date of the current token. /// Gets or sets the UTC expiration date of the token.
/// </summary> /// </summary>
public virtual DateTime? ExpirationDate { get; set; } public virtual DateTime? ExpirationDate { get; set; }
/// <summary> /// <summary>
/// Gets or sets the unique identifier associated with the current token. /// Gets or sets the unique identifier of the token.
/// </summary> /// </summary>
public virtual TKey? Id { get; set; } public virtual TKey? Id { get; set; }
/// <summary> /// <summary>
/// Gets or sets the payload of the current token, if applicable. /// Gets or sets the payload of the token.
/// Note: this property is only used for reference tokens
/// and may be encrypted for security reasons.
/// </summary> /// </summary>
/// <remarks>
/// Note: this property is only used for reference tokens
/// and may be hashed or encrypted for security reasons.
/// </remarks>
public virtual string? Payload { get; set; } public virtual string? Payload { get; set; }
/// <summary> /// <summary>
/// Gets or sets the additional properties serialized as a JSON object, /// Gets or sets the additional properties of the token, serialized as a JSON object.
/// or <see langword="null"/> if no bag was associated with the current token.
/// </summary> /// </summary>
[StringSyntax(StringSyntaxAttribute.Json)] [StringSyntax(StringSyntaxAttribute.Json)]
public virtual string? Properties { get; set; } public virtual string? Properties { get; set; }
/// <summary> /// <summary>
/// Gets or sets the UTC redemption date of the current token. /// Gets or sets the UTC redemption date of the token.
/// </summary> /// </summary>
public virtual DateTime? RedemptionDate { get; set; } public virtual DateTime? RedemptionDate { get; set; }
/// <summary> /// <summary>
/// Gets or sets the reference identifier associated /// Gets or sets the reference identifier associated with the token.
/// with the current token, if applicable. /// </summary>
/// <remarks>
/// Note: this property is only used for reference tokens /// Note: this property is only used for reference tokens
/// and may be hashed or encrypted for security reasons. /// and may be hashed or encrypted for security reasons.
/// </summary> /// </remarks>
public virtual string? ReferenceId { get; set; } public virtual string? ReferenceId { get; set; }
/// <summary> /// <summary>
/// Gets or sets the status of the current token. /// Gets or sets the status of the token.
/// </summary> /// </summary>
public virtual string? Status { get; set; } public virtual string? Status { get; set; }
/// <summary> /// <summary>
/// Gets or sets the subject associated with the current token. /// Gets or sets the subject associated with the token.
/// </summary> /// </summary>
public virtual string? Subject { get; set; } public virtual string? Subject { get; set; }
/// <summary> /// <summary>
/// Gets or sets the type of the current token. /// Gets or sets the type of the token.
/// </summary> /// </summary>
public virtual string? Type { get; set; } public virtual string? Type { get; set; }
} }

29
src/OpenIddict.EntityFramework/Configurations/OpenIddictEntityFrameworkApplicationConfiguration.cs

@ -5,8 +5,6 @@
*/ */
using System.ComponentModel; using System.ComponentModel;
using System.ComponentModel.DataAnnotations.Schema;
using System.Data.Entity.Infrastructure.Annotations;
using System.Data.Entity.ModelConfiguration; using System.Data.Entity.ModelConfiguration;
using System.Diagnostics.CodeAnalysis; using System.Diagnostics.CodeAnalysis;
using System.Linq.Expressions; using System.Linq.Expressions;
@ -38,17 +36,22 @@ public sealed class OpenIddictEntityFrameworkApplicationConfiguration<
// Entity Framework would throw an exception due to the TKey generic parameter // Entity Framework would throw an exception due to the TKey generic parameter
// being non-nullable when using value types like short, int, long or Guid. // being non-nullable when using value types like short, int, long or Guid.
HasKey(static application => application.Id); HasMany(static application => application.Authorizations)
.WithOptional(static authorization => authorization.Application!)
.Map(static association =>
{
association.MapKey(nameof(OpenIddictEntityFrameworkAuthorization.Application) +
nameof(OpenIddictEntityFrameworkApplication.Id));
});
Property(static application => application.ApplicationType) Property(static application => application.ApplicationType)
.HasMaxLength(50); .HasMaxLength(50);
Property(static application => application.ClientId) Property(static application => application.ClientId)
.HasMaxLength(100) .HasMaxLength(100);
.HasColumnAnnotation(IndexAnnotation.AnnotationName, new IndexAnnotation(new IndexAttribute
{ HasIndex(static application => application.ClientId)
IsUnique = true .IsUnique();
}));
Property(static application => application.ClientType) Property(static application => application.ClientType)
.HasMaxLength(50); .HasMaxLength(50);
@ -60,6 +63,8 @@ public sealed class OpenIddictEntityFrameworkApplicationConfiguration<
Property(static application => application.ConsentType) Property(static application => application.ConsentType)
.HasMaxLength(50); .HasMaxLength(50);
HasKey(static application => application.Id);
if (typeof(TKey) == typeof(string)) if (typeof(TKey) == typeof(string))
{ {
var parameter = Expression.Parameter(typeof(TApplication), "application"); var parameter = Expression.Parameter(typeof(TApplication), "application");
@ -70,14 +75,6 @@ public sealed class OpenIddictEntityFrameworkApplicationConfiguration<
Property(lambda).HasMaxLength(100); Property(lambda).HasMaxLength(100);
} }
HasMany(static application => application.Authorizations)
.WithOptional(static authorization => authorization.Application!)
.Map(static association =>
{
association.MapKey(nameof(OpenIddictEntityFrameworkAuthorization.Application) +
nameof(OpenIddictEntityFrameworkApplication.Id));
});
HasMany(static application => application.Tokens) HasMany(static application => application.Tokens)
.WithOptional(static token => token.Application!) .WithOptional(static token => token.Application!)
.Map(static association => .Map(static association =>

10
src/OpenIddict.EntityFramework/Configurations/OpenIddictEntityFrameworkAuthorizationConfiguration.cs

@ -36,12 +36,12 @@ public sealed class OpenIddictEntityFrameworkAuthorizationConfiguration<
// Entity Framework would throw an exception due to the TKey generic parameter // Entity Framework would throw an exception due to the TKey generic parameter
// being non-nullable when using value types like short, int, long or Guid. // being non-nullable when using value types like short, int, long or Guid.
HasKey(static authorization => authorization.Id);
Property(static authorization => authorization.ConcurrencyToken) Property(static authorization => authorization.ConcurrencyToken)
.HasMaxLength(50) .HasMaxLength(50)
.IsConcurrencyToken(); .IsConcurrencyToken();
HasKey(static authorization => authorization.Id);
if (typeof(TKey) == typeof(string)) if (typeof(TKey) == typeof(string))
{ {
var parameter = Expression.Parameter(typeof(TAuthorization), "authorization"); var parameter = Expression.Parameter(typeof(TAuthorization), "authorization");
@ -58,15 +58,15 @@ public sealed class OpenIddictEntityFrameworkAuthorizationConfiguration<
Property(static authorization => authorization.Subject) Property(static authorization => authorization.Subject)
.HasMaxLength(400); .HasMaxLength(400);
Property(static authorization => authorization.Type)
.HasMaxLength(50);
HasMany(static authorization => authorization.Tokens) HasMany(static authorization => authorization.Tokens)
.WithOptional(static token => token.Authorization!) .WithOptional(static token => token.Authorization!)
.Map(static association => association.MapKey(nameof(OpenIddictEntityFrameworkToken.Authorization) + .Map(static association => association.MapKey(nameof(OpenIddictEntityFrameworkToken.Authorization) +
nameof(OpenIddictEntityFrameworkAuthorization.Id))) nameof(OpenIddictEntityFrameworkAuthorization.Id)))
.WillCascadeOnDelete(); .WillCascadeOnDelete();
Property(static authorization => authorization.Type)
.HasMaxLength(50);
ToTable("OpenIddictAuthorizations"); ToTable("OpenIddictAuthorizations");
} }
} }

15
src/OpenIddict.EntityFramework/Configurations/OpenIddictEntityFrameworkResourceConfiguration.cs

@ -5,8 +5,6 @@
*/ */
using System.ComponentModel; using System.ComponentModel;
using System.ComponentModel.DataAnnotations.Schema;
using System.Data.Entity.Infrastructure.Annotations;
using System.Data.Entity.ModelConfiguration; using System.Data.Entity.ModelConfiguration;
using System.Diagnostics.CodeAnalysis; using System.Diagnostics.CodeAnalysis;
using System.Linq.Expressions; using System.Linq.Expressions;
@ -32,12 +30,12 @@ public sealed class OpenIddictEntityFrameworkResourceConfiguration<
// Entity Framework would throw an exception due to the TKey generic parameter // Entity Framework would throw an exception due to the TKey generic parameter
// being non-nullable when using value types like short, int, long or Guid. // being non-nullable when using value types like short, int, long or Guid.
HasKey(static resource => resource.Id);
Property(static resource => resource.ConcurrencyToken) Property(static resource => resource.ConcurrencyToken)
.HasMaxLength(50) .HasMaxLength(50)
.IsConcurrencyToken(); .IsConcurrencyToken();
HasKey(static resource => resource.Id);
if (typeof(TKey) == typeof(string)) if (typeof(TKey) == typeof(string))
{ {
var parameter = Expression.Parameter(typeof(TResource), "resource"); var parameter = Expression.Parameter(typeof(TResource), "resource");
@ -49,11 +47,10 @@ public sealed class OpenIddictEntityFrameworkResourceConfiguration<
} }
Property(static resource => resource.Name) Property(static resource => resource.Name)
.HasMaxLength(200) .HasMaxLength(200);
.HasColumnAnnotation(IndexAnnotation.AnnotationName, new IndexAnnotation(new IndexAttribute
{ HasIndex(static resource => resource.Name)
IsUnique = true .IsUnique();
}));
ToTable("OpenIddictResources"); ToTable("OpenIddictResources");
} }

15
src/OpenIddict.EntityFramework/Configurations/OpenIddictEntityFrameworkScopeConfiguration.cs

@ -5,8 +5,6 @@
*/ */
using System.ComponentModel; using System.ComponentModel;
using System.ComponentModel.DataAnnotations.Schema;
using System.Data.Entity.Infrastructure.Annotations;
using System.Data.Entity.ModelConfiguration; using System.Data.Entity.ModelConfiguration;
using System.Diagnostics.CodeAnalysis; using System.Diagnostics.CodeAnalysis;
using System.Linq.Expressions; using System.Linq.Expressions;
@ -32,12 +30,12 @@ public sealed class OpenIddictEntityFrameworkScopeConfiguration<
// Entity Framework would throw an exception due to the TKey generic parameter // Entity Framework would throw an exception due to the TKey generic parameter
// being non-nullable when using value types like short, int, long or Guid. // being non-nullable when using value types like short, int, long or Guid.
HasKey(static scope => scope.Id);
Property(static scope => scope.ConcurrencyToken) Property(static scope => scope.ConcurrencyToken)
.HasMaxLength(50) .HasMaxLength(50)
.IsConcurrencyToken(); .IsConcurrencyToken();
HasKey(static scope => scope.Id);
if (typeof(TKey) == typeof(string)) if (typeof(TKey) == typeof(string))
{ {
var parameter = Expression.Parameter(typeof(TScope), "scope"); var parameter = Expression.Parameter(typeof(TScope), "scope");
@ -49,11 +47,10 @@ public sealed class OpenIddictEntityFrameworkScopeConfiguration<
} }
Property(static scope => scope.Name) Property(static scope => scope.Name)
.HasMaxLength(200) .HasMaxLength(200);
.HasColumnAnnotation(IndexAnnotation.AnnotationName, new IndexAnnotation(new IndexAttribute
{ HasIndex(static scope => scope.Name)
IsUnique = true .IsUnique();
}));
ToTable("OpenIddictScopes"); ToTable("OpenIddictScopes");
} }

13
src/OpenIddict.EntityFramework/Configurations/OpenIddictEntityFrameworkTokenConfiguration.cs

@ -5,8 +5,6 @@
*/ */
using System.ComponentModel; using System.ComponentModel;
using System.ComponentModel.DataAnnotations.Schema;
using System.Data.Entity.Infrastructure.Annotations;
using System.Data.Entity.ModelConfiguration; using System.Data.Entity.ModelConfiguration;
using System.Diagnostics.CodeAnalysis; using System.Diagnostics.CodeAnalysis;
using System.Linq.Expressions; using System.Linq.Expressions;
@ -38,12 +36,12 @@ public sealed class OpenIddictEntityFrameworkTokenConfiguration<
// Entity Framework would throw an exception due to the TKey generic parameter // Entity Framework would throw an exception due to the TKey generic parameter
// being non-nullable when using value types like short, int, long or Guid. // being non-nullable when using value types like short, int, long or Guid.
HasKey(static token => token.Id);
Property(static token => token.ConcurrencyToken) Property(static token => token.ConcurrencyToken)
.HasMaxLength(50) .HasMaxLength(50)
.IsConcurrencyToken(); .IsConcurrencyToken();
HasKey(static token => token.Id);
if (typeof(TKey) == typeof(string)) if (typeof(TKey) == typeof(string))
{ {
var parameter = Expression.Parameter(typeof(TToken), "token"); var parameter = Expression.Parameter(typeof(TToken), "token");
@ -54,12 +52,13 @@ public sealed class OpenIddictEntityFrameworkTokenConfiguration<
Property(lambda).HasMaxLength(100); Property(lambda).HasMaxLength(100);
} }
Property(static token => token.ReferenceId)
.HasMaxLength(100);
// Warning: the index on the ReferenceId property MUST NOT be declared as // Warning: the index on the ReferenceId property MUST NOT be declared as
// a unique index, as Entity Framework 6.x doesn't support creating indexes // a unique index, as Entity Framework 6.x doesn't support creating indexes
// with null-friendly WHERE conditions, unlike Entity Framework Core. // with null-friendly WHERE conditions, unlike Entity Framework Core.
Property(static token => token.ReferenceId) HasIndex(static token => token.ReferenceId);
.HasMaxLength(100)
.HasColumnAnnotation(IndexAnnotation.AnnotationName, new IndexAnnotation(new IndexAttribute()));
Property(static token => token.Status) Property(static token => token.Status)
.HasMaxLength(50); .HasMaxLength(50);

7
src/OpenIddict.EntityFramework/Stores/OpenIddictEntityFrameworkAuthorizationStore.cs

@ -309,12 +309,7 @@ public class OpenIddictEntityFrameworkAuthorizationStore<
{ {
ArgumentNullException.ThrowIfNull(authorization); ArgumentNullException.ThrowIfNull(authorization);
if (authorization.CreationDate is null) return new(authorization.CreationDate is DateTime date ? DateTime.SpecifyKind(date, DateTimeKind.Utc) : null);
{
return new(result: null);
}
return new(DateTime.SpecifyKind(authorization.CreationDate.Value, DateTimeKind.Utc));
} }
/// <inheritdoc/> /// <inheritdoc/>

21
src/OpenIddict.EntityFramework/Stores/OpenIddictEntityFrameworkTokenStore.cs

@ -353,12 +353,7 @@ public class OpenIddictEntityFrameworkTokenStore<
{ {
ArgumentNullException.ThrowIfNull(token); ArgumentNullException.ThrowIfNull(token);
if (token.CreationDate is null) return new(token.CreationDate is DateTime date ? DateTime.SpecifyKind(date, DateTimeKind.Utc) : null);
{
return new(result: null);
}
return new(DateTime.SpecifyKind(token.CreationDate.Value, DateTimeKind.Utc));
} }
/// <inheritdoc/> /// <inheritdoc/>
@ -366,12 +361,7 @@ public class OpenIddictEntityFrameworkTokenStore<
{ {
ArgumentNullException.ThrowIfNull(token); ArgumentNullException.ThrowIfNull(token);
if (token.ExpirationDate is null) return new(token.ExpirationDate is DateTime date ? DateTime.SpecifyKind(date, DateTimeKind.Utc) : null);
{
return new(result: null);
}
return new(DateTime.SpecifyKind(token.ExpirationDate.Value, DateTimeKind.Utc));
} }
/// <inheritdoc/> /// <inheritdoc/>
@ -427,12 +417,7 @@ public class OpenIddictEntityFrameworkTokenStore<
{ {
ArgumentNullException.ThrowIfNull(token); ArgumentNullException.ThrowIfNull(token);
if (token.RedemptionDate is null) return new(token.RedemptionDate is DateTime date ? DateTime.SpecifyKind(date, DateTimeKind.Utc) : null);
{
return new(result: null);
}
return new(DateTime.SpecifyKind(token.RedemptionDate.Value, DateTimeKind.Utc));
} }
/// <inheritdoc/> /// <inheritdoc/>

4
src/OpenIddict.EntityFrameworkCore.Models/OpenIddict.EntityFrameworkCore.Models.csproj

@ -10,4 +10,8 @@
<PackageTags>$(PackageTags);entityframeworkcore;models</PackageTags> <PackageTags>$(PackageTags);entityframeworkcore;models</PackageTags>
</PropertyGroup> </PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.IdentityModel.Tokens" />
</ItemGroup>
</Project> </Project>

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

@ -5,7 +5,8 @@
*/ */
using System.Diagnostics; using System.Diagnostics;
using System.Diagnostics.CodeAnalysis; using System.Text.Json;
using Microsoft.IdentityModel.Tokens;
namespace OpenIddict.EntityFrameworkCore.Models; namespace OpenIddict.EntityFrameworkCore.Models;
@ -21,9 +22,7 @@ public class OpenIddictEntityFrameworkCoreApplication : OpenIddictEntityFramewor
/// Represents an OpenIddict application. /// Represents an OpenIddict application.
/// </summary> /// </summary>
public class OpenIddictEntityFrameworkCoreApplication<TKey> : OpenIddictEntityFrameworkCoreApplication<TKey, OpenIddictEntityFrameworkCoreAuthorization<TKey>, OpenIddictEntityFrameworkCoreToken<TKey>> public class OpenIddictEntityFrameworkCoreApplication<TKey> : OpenIddictEntityFrameworkCoreApplication<TKey, OpenIddictEntityFrameworkCoreAuthorization<TKey>, OpenIddictEntityFrameworkCoreToken<TKey>>
where TKey : notnull, IEquatable<TKey> where TKey : notnull, IEquatable<TKey>;
{
}
/// <summary> /// <summary>
/// Represents an OpenIddict application. /// Represents an OpenIddict application.
@ -35,116 +34,104 @@ public class OpenIddictEntityFrameworkCoreApplication<TKey, TAuthorization, TTok
where TToken : class where TToken : class
{ {
/// <summary> /// <summary>
/// Gets or sets the application type associated with the current application. /// Gets or sets the application type of the application.
/// </summary> /// </summary>
public virtual string? ApplicationType { get; set; } public virtual string? ApplicationType { get; set; }
/// <summary> /// <summary>
/// Gets the list of the authorizations associated with this application. /// Gets the list of the authorizations associated with the application.
/// </summary> /// </summary>
public virtual ICollection<TAuthorization> Authorizations { get; } = new HashSet<TAuthorization>(); public virtual ICollection<TAuthorization> Authorizations { get; } = new HashSet<TAuthorization>();
/// <summary> /// <summary>
/// Gets or sets the client identifier associated with the current application. /// Gets or sets the client identifier of the application.
/// </summary> /// </summary>
public virtual string? ClientId { get; set; } public virtual string? ClientId { get; set; }
/// <summary> /// <summary>
/// Gets or sets the client secret associated with the current application. /// Gets or sets the client secret of the application.
/// Note: depending on the application manager used to create this instance,
/// this property may be hashed or encrypted for security reasons.
/// </summary> /// </summary>
/// <remarks> /// <remarks>
/// <para>
/// Note: depending on the application manager used to create this instance,
/// this property may be hashed or encrypted for security reasons.
/// </para>
/// <para>
/// Note: client authentication based on shared secrets is not recommended and should /// Note: client authentication based on shared secrets is not recommended and should
/// only be used for backward compatibility with legacy applications that only support /// only be used for backward compatibility with legacy applications that only support
/// client secrets. When possible, consider using public/private key pairs or TLS client /// client secrets. When possible, consider using public/private key pairs or TLS client
/// certificates instead, as these client authentication methods are significantly safer. /// certificates instead, as these client authentication methods are significantly safer.
/// </para>
/// </remarks> /// </remarks>
public virtual string? ClientSecret { get; set; } public virtual string? ClientSecret { get; set; }
/// <summary> /// <summary>
/// Gets or sets the client type associated with the current application. /// Gets or sets the client type of the application.
/// </summary> /// </summary>
public virtual string? ClientType { get; set; } public virtual string? ClientType { get; set; }
/// <summary> /// <summary>
/// Gets or sets the concurrency token. /// Gets or sets the concurrency token of the application.
/// </summary> /// </summary>
public virtual string? ConcurrencyToken { get; set; } = Guid.NewGuid().ToString(); public virtual string? ConcurrencyToken { get; set; } = Guid.NewGuid().ToString();
/// <summary> /// <summary>
/// Gets or sets the consent type associated with the current application. /// Gets or sets the consent type of the application.
/// </summary> /// </summary>
public virtual string? ConsentType { get; set; } public virtual string? ConsentType { get; set; }
/// <summary> /// <summary>
/// Gets or sets the display name associated with the current application. /// Gets or sets the display name of the application.
/// </summary> /// </summary>
public virtual string? DisplayName { get; set; } public virtual string? DisplayName { get; set; }
/// <summary> /// <summary>
/// Gets or sets the localized display names /// Gets or sets the localized display names of the application.
/// associated with the current application,
/// serialized as a JSON object.
/// </summary> /// </summary>
[StringSyntax(StringSyntaxAttribute.Json)] public virtual IDictionary<string, string>? DisplayNames { get; set; }
public virtual string? DisplayNames { get; set; }
/// <summary> /// <summary>
/// Gets or sets the unique identifier associated with the current application. /// Gets or sets the unique identifier of the application.
/// </summary> /// </summary>
public virtual TKey? Id { get; set; } public virtual TKey? Id { get; set; }
/// <summary> /// <summary>
/// Gets or sets the JSON Web Key Set associated with /// Gets or sets the JSON Web Key Set of the application.
/// the application, serialized as a JSON object.
/// </summary> /// </summary>
[StringSyntax(StringSyntaxAttribute.Json)] public virtual JsonWebKeySet? JsonWebKeySet { get; set; }
public virtual string? JsonWebKeySet { get; set; }
/// <summary> /// <summary>
/// Gets or sets the permissions associated with the /// Gets or sets the permissions of the application.
/// current application, serialized as a JSON array.
/// </summary> /// </summary>
[StringSyntax(StringSyntaxAttribute.Json)] public virtual string[]? Permissions { get; set; }
public virtual string? Permissions { get; set; }
/// <summary> /// <summary>
/// Gets or sets the post-logout redirect URIs associated with /// Gets or sets the post-logout redirect URIs of the application.
/// the current application, serialized as a JSON array.
/// </summary> /// </summary>
[StringSyntax(StringSyntaxAttribute.Json)] public virtual string[]? PostLogoutRedirectUris { get; set; }
public virtual string? PostLogoutRedirectUris { get; set; }
/// <summary> /// <summary>
/// Gets or sets the additional properties serialized as a JSON object, /// Gets or sets the additional properties of the application.
/// or <see langword="null"/> if no bag was associated with the current application.
/// </summary> /// </summary>
[StringSyntax(StringSyntaxAttribute.Json)] public virtual IDictionary<string, JsonElement>? Properties { get; set; }
public virtual string? Properties { get; set; }
/// <summary> /// <summary>
/// Gets or sets the redirect URIs associated with the /// Gets or sets the redirect URIs of the application.
/// current application, serialized as a JSON array.
/// </summary> /// </summary>
[StringSyntax(StringSyntaxAttribute.Json)] public virtual string[]? RedirectUris { get; set; }
public virtual string? RedirectUris { get; set; }
/// <summary> /// <summary>
/// Gets or sets the requirements associated with the /// Gets or sets the requirements of the application.
/// current application, serialized as a JSON array.
/// </summary> /// </summary>
[StringSyntax(StringSyntaxAttribute.Json)] public virtual string[]? Requirements { get; set; }
public virtual string? Requirements { get; set; }
/// <summary> /// <summary>
/// Gets or sets the settings serialized as a JSON object. /// Gets or sets the settings of the application.
/// </summary> /// </summary>
[StringSyntax(StringSyntaxAttribute.Json)] public virtual IDictionary<string, string>? Settings { get; set; }
public virtual string? Settings { get; set; }
/// <summary> /// <summary>
/// Gets the list of the tokens associated with this application. /// Gets the list of the tokens associated with the application.
/// </summary> /// </summary>
public virtual ICollection<TToken> Tokens { get; } = new HashSet<TToken>(); public virtual ICollection<TToken> Tokens { get; } = new HashSet<TToken>();
} }

33
src/OpenIddict.EntityFrameworkCore.Models/OpenIddictEntityFrameworkCoreAuthorization.cs

@ -5,7 +5,7 @@
*/ */
using System.Diagnostics; using System.Diagnostics;
using System.Diagnostics.CodeAnalysis; using System.Text.Json;
namespace OpenIddict.EntityFrameworkCore.Models; namespace OpenIddict.EntityFrameworkCore.Models;
@ -21,9 +21,7 @@ public class OpenIddictEntityFrameworkCoreAuthorization : OpenIddictEntityFramew
/// Represents an OpenIddict authorization. /// Represents an OpenIddict authorization.
/// </summary> /// </summary>
public class OpenIddictEntityFrameworkCoreAuthorization<TKey> : OpenIddictEntityFrameworkCoreAuthorization<TKey, OpenIddictEntityFrameworkCoreApplication<TKey>, OpenIddictEntityFrameworkCoreToken<TKey>> public class OpenIddictEntityFrameworkCoreAuthorization<TKey> : OpenIddictEntityFrameworkCoreAuthorization<TKey, OpenIddictEntityFrameworkCoreApplication<TKey>, OpenIddictEntityFrameworkCoreToken<TKey>>
where TKey : notnull, IEquatable<TKey> where TKey : notnull, IEquatable<TKey>;
{
}
/// <summary> /// <summary>
/// Represents an OpenIddict authorization. /// Represents an OpenIddict authorization.
@ -35,55 +33,52 @@ public class OpenIddictEntityFrameworkCoreAuthorization<TKey, TApplication, TTok
where TToken : class where TToken : class
{ {
/// <summary> /// <summary>
/// Gets or sets the application associated with the current authorization. /// Gets or sets the application of the authorization.
/// </summary> /// </summary>
public virtual TApplication? Application { get; set; } public virtual TApplication? Application { get; set; }
/// <summary> /// <summary>
/// Gets or sets the concurrency token. /// Gets or sets the concurrency token of the authorization.
/// </summary> /// </summary>
public virtual string? ConcurrencyToken { get; set; } = Guid.NewGuid().ToString(); public virtual string? ConcurrencyToken { get; set; } = Guid.NewGuid().ToString();
/// <summary> /// <summary>
/// Gets or sets the UTC creation date of the current authorization. /// Gets or sets the UTC creation date of the authorization.
/// </summary> /// </summary>
public virtual DateTime? CreationDate { get; set; } public virtual DateTime? CreationDate { get; set; }
/// <summary> /// <summary>
/// Gets or sets the unique identifier associated with the current authorization. /// Gets or sets the unique identifier of the authorization.
/// </summary> /// </summary>
public virtual TKey? Id { get; set; } public virtual TKey? Id { get; set; }
/// <summary> /// <summary>
/// Gets or sets the additional properties serialized as a JSON object, /// Gets or sets the additional properties of the authorization.
/// or <see langword="null"/> if no bag was associated with the current authorization.
/// </summary> /// </summary>
[StringSyntax(StringSyntaxAttribute.Json)] public virtual IDictionary<string, JsonElement>? Properties { get; set; }
public virtual string? Properties { get; set; }
/// <summary> /// <summary>
/// Gets or sets the scopes associated with the current /// Gets or sets the scopes of the authorization.
/// authorization, serialized as a JSON array.
/// </summary> /// </summary>
public virtual string? Scopes { get; set; } public virtual string[]? Scopes { get; set; }
/// <summary> /// <summary>
/// Gets or sets the status of the current authorization. /// Gets or sets the status of the authorization.
/// </summary> /// </summary>
public virtual string? Status { get; set; } public virtual string? Status { get; set; }
/// <summary> /// <summary>
/// Gets or sets the subject associated with the current authorization. /// Gets or sets the subject of the authorization.
/// </summary> /// </summary>
public virtual string? Subject { get; set; } public virtual string? Subject { get; set; }
/// <summary> /// <summary>
/// Gets the list of tokens associated with the current authorization. /// Gets the list of tokens associated with the authorization.
/// </summary> /// </summary>
public virtual ICollection<TToken> Tokens { get; } = new HashSet<TToken>(); public virtual ICollection<TToken> Tokens { get; } = new HashSet<TToken>();
/// <summary> /// <summary>
/// Gets or sets the type of the current authorization. /// Gets or sets the type of the authorization.
/// </summary> /// </summary>
public virtual string? Type { get; set; } public virtual string? Type { get; set; }
} }

31
src/OpenIddict.EntityFrameworkCore.Models/OpenIddictEntityFrameworkCoreResource.cs

@ -5,7 +5,7 @@
*/ */
using System.Diagnostics; using System.Diagnostics;
using System.Diagnostics.CodeAnalysis; using System.Text.Json;
namespace OpenIddict.EntityFrameworkCore.Models; namespace OpenIddict.EntityFrameworkCore.Models;
@ -24,49 +24,42 @@ public class OpenIddictEntityFrameworkCoreResource : OpenIddictEntityFrameworkCo
public class OpenIddictEntityFrameworkCoreResource<TKey> where TKey : notnull, IEquatable<TKey> public class OpenIddictEntityFrameworkCoreResource<TKey> where TKey : notnull, IEquatable<TKey>
{ {
/// <summary> /// <summary>
/// Gets or sets the concurrency token. /// Gets or sets the concurrency token of the resource.
/// </summary> /// </summary>
public virtual string? ConcurrencyToken { get; set; } = Guid.NewGuid().ToString(); public virtual string? ConcurrencyToken { get; set; } = Guid.NewGuid().ToString();
/// <summary> /// <summary>
/// Gets or sets the public description associated with the current resource. /// Gets or sets the public description of the resource.
/// </summary> /// </summary>
public virtual string? Description { get; set; } public virtual string? Description { get; set; }
/// <summary> /// <summary>
/// Gets or sets the localized public descriptions associated /// Gets or sets the localized public descriptions of the resource.
/// with the current resource, serialized as a JSON object.
/// </summary> /// </summary>
[StringSyntax(StringSyntaxAttribute.Json)] public virtual IDictionary<string, string>? Descriptions { get; set; }
public virtual string? Descriptions { get; set; }
/// <summary> /// <summary>
/// Gets or sets the display name associated with the current resource. /// Gets or sets the display name of the resource.
/// </summary> /// </summary>
public virtual string? DisplayName { get; set; } public virtual string? DisplayName { get; set; }
/// <summary> /// <summary>
/// Gets or sets the localized display names /// Gets or sets the localized display names of the resource.
/// associated with the current application,
/// serialized as a JSON object.
/// </summary> /// </summary>
[StringSyntax(StringSyntaxAttribute.Json)] public virtual IDictionary<string, string>? DisplayNames { get; set; }
public virtual string? DisplayNames { get; set; }
/// <summary> /// <summary>
/// Gets or sets the unique identifier associated with the current resource. /// Gets or sets the unique identifier of the resource.
/// </summary> /// </summary>
public virtual TKey? Id { get; set; } public virtual TKey? Id { get; set; }
/// <summary> /// <summary>
/// Gets or sets the unique name associated with the current resource. /// Gets or sets the unique name of the resource.
/// </summary> /// </summary>
public virtual string? Name { get; set; } public virtual string? Name { get; set; }
/// <summary> /// <summary>
/// Gets or sets the additional properties serialized as a JSON object, /// Gets or sets the additional properties of the resource.
/// or <see langword="null"/> if no bag was associated with the current resource.
/// </summary> /// </summary>
[StringSyntax(StringSyntaxAttribute.Json)] public virtual IDictionary<string, JsonElement>? Properties { get; set; }
public virtual string? Properties { get; set; }
} }

37
src/OpenIddict.EntityFrameworkCore.Models/OpenIddictEntityFrameworkCoreScope.cs

@ -5,7 +5,7 @@
*/ */
using System.Diagnostics; using System.Diagnostics;
using System.Diagnostics.CodeAnalysis; using System.Text.Json;
namespace OpenIddict.EntityFrameworkCore.Models; namespace OpenIddict.EntityFrameworkCore.Models;
@ -24,56 +24,47 @@ public class OpenIddictEntityFrameworkCoreScope : OpenIddictEntityFrameworkCoreS
public class OpenIddictEntityFrameworkCoreScope<TKey> where TKey : notnull, IEquatable<TKey> public class OpenIddictEntityFrameworkCoreScope<TKey> where TKey : notnull, IEquatable<TKey>
{ {
/// <summary> /// <summary>
/// Gets or sets the concurrency token. /// Gets or sets the concurrency token of the scope.
/// </summary> /// </summary>
public virtual string? ConcurrencyToken { get; set; } = Guid.NewGuid().ToString(); public virtual string? ConcurrencyToken { get; set; } = Guid.NewGuid().ToString();
/// <summary> /// <summary>
/// Gets or sets the public description associated with the current scope. /// Gets or sets the public description of the scope.
/// </summary> /// </summary>
public virtual string? Description { get; set; } public virtual string? Description { get; set; }
/// <summary> /// <summary>
/// Gets or sets the localized public descriptions associated /// Gets or sets the localized public descriptions of the scope.
/// with the current scope, serialized as a JSON object.
/// </summary> /// </summary>
[StringSyntax(StringSyntaxAttribute.Json)] public virtual IDictionary<string, string>? Descriptions { get; set; }
public virtual string? Descriptions { get; set; }
/// <summary> /// <summary>
/// Gets or sets the display name associated with the current scope. /// Gets or sets the display name of the scope.
/// </summary> /// </summary>
public virtual string? DisplayName { get; set; } public virtual string? DisplayName { get; set; }
/// <summary> /// <summary>
/// Gets or sets the localized display names /// Gets or sets the localized display names of the scope.
/// associated with the current application,
/// serialized as a JSON object.
/// </summary> /// </summary>
[StringSyntax(StringSyntaxAttribute.Json)] public virtual IDictionary<string, string>? DisplayNames { get; set; }
public virtual string? DisplayNames { get; set; }
/// <summary> /// <summary>
/// Gets or sets the unique identifier associated with the current scope. /// Gets or sets the unique identifier of the scope.
/// </summary> /// </summary>
public virtual TKey? Id { get; set; } public virtual TKey? Id { get; set; }
/// <summary> /// <summary>
/// Gets or sets the unique name associated with the current scope. /// Gets or sets the unique name of the scope.
/// </summary> /// </summary>
public virtual string? Name { get; set; } public virtual string? Name { get; set; }
/// <summary> /// <summary>
/// Gets or sets the additional properties serialized as a JSON object, /// Gets or sets the additional properties of the scope.
/// or <see langword="null"/> if no bag was associated with the current scope.
/// </summary> /// </summary>
[StringSyntax(StringSyntaxAttribute.Json)] public virtual IDictionary<string, JsonElement>? Properties { get; set; }
public virtual string? Properties { get; set; }
/// <summary> /// <summary>
/// Gets or sets the resources associated with the /// Gets or sets the resources of the scope.
/// current scope, serialized as a JSON array.
/// </summary> /// </summary>
[StringSyntax(StringSyntaxAttribute.Json)] public virtual string[]? Resources { get; set; }
public virtual string? Resources { get; set; }
} }

43
src/OpenIddict.EntityFrameworkCore.Models/OpenIddictEntityFrameworkCoreToken.cs

@ -5,7 +5,7 @@
*/ */
using System.Diagnostics; using System.Diagnostics;
using System.Diagnostics.CodeAnalysis; using System.Text.Json;
namespace OpenIddict.EntityFrameworkCore.Models; namespace OpenIddict.EntityFrameworkCore.Models;
@ -21,9 +21,7 @@ public class OpenIddictEntityFrameworkCoreToken : OpenIddictEntityFrameworkCoreT
/// Represents an OpenIddict token. /// Represents an OpenIddict token.
/// </summary> /// </summary>
public class OpenIddictEntityFrameworkCoreToken<TKey> : OpenIddictEntityFrameworkCoreToken<TKey, OpenIddictEntityFrameworkCoreApplication<TKey>, OpenIddictEntityFrameworkCoreAuthorization<TKey>> public class OpenIddictEntityFrameworkCoreToken<TKey> : OpenIddictEntityFrameworkCoreToken<TKey, OpenIddictEntityFrameworkCoreApplication<TKey>, OpenIddictEntityFrameworkCoreAuthorization<TKey>>
where TKey : notnull, IEquatable<TKey> where TKey : notnull, IEquatable<TKey>;
{
}
/// <summary> /// <summary>
/// Represents an OpenIddict token. /// Represents an OpenIddict token.
@ -45,64 +43,65 @@ public class OpenIddictEntityFrameworkCoreToken<TKey, TApplication, TAuthorizati
public virtual TAuthorization? Authorization { get; set; } public virtual TAuthorization? Authorization { get; set; }
/// <summary> /// <summary>
/// Gets or sets the concurrency token. /// Gets or sets the concurrency token of the token.
/// </summary> /// </summary>
public virtual string? ConcurrencyToken { get; set; } = Guid.NewGuid().ToString(); public virtual string? ConcurrencyToken { get; set; } = Guid.NewGuid().ToString();
/// <summary> /// <summary>
/// Gets or sets the UTC creation date of the current token. /// Gets or sets the UTC creation date of the token.
/// </summary> /// </summary>
public virtual DateTime? CreationDate { get; set; } public virtual DateTime? CreationDate { get; set; }
/// <summary> /// <summary>
/// Gets or sets the UTC expiration date of the current token. /// Gets or sets the UTC expiration date of the token.
/// </summary> /// </summary>
public virtual DateTime? ExpirationDate { get; set; } public virtual DateTime? ExpirationDate { get; set; }
/// <summary> /// <summary>
/// Gets or sets the unique identifier associated with the current token. /// Gets or sets the unique identifier of the token.
/// </summary> /// </summary>
public virtual TKey? Id { get; set; } public virtual TKey? Id { get; set; }
/// <summary> /// <summary>
/// Gets or sets the payload of the current token, if applicable. /// Gets or sets the payload of the token.
/// Note: this property is only used for reference tokens
/// and may be encrypted for security reasons.
/// </summary> /// </summary>
/// <remarks>
/// Note: this property is only used for reference tokens
/// and may be hashed or encrypted for security reasons.
/// </remarks>
public virtual string? Payload { get; set; } public virtual string? Payload { get; set; }
/// <summary> /// <summary>
/// Gets or sets the additional properties serialized as a JSON object, /// Gets or sets the additional properties of the token.
/// or <see langword="null"/> if no bag was associated with the current token.
/// </summary> /// </summary>
[StringSyntax(StringSyntaxAttribute.Json)] public virtual IDictionary<string, JsonElement>? Properties { get; set; }
public virtual string? Properties { get; set; }
/// <summary> /// <summary>
/// Gets or sets the UTC redemption date of the current token. /// Gets or sets the UTC redemption date of the token.
/// </summary> /// </summary>
public virtual DateTime? RedemptionDate { get; set; } public virtual DateTime? RedemptionDate { get; set; }
/// <summary> /// <summary>
/// Gets or sets the reference identifier associated /// Gets or sets the reference identifier of the token.
/// with the current token, if applicable. /// </summary>
/// <remarks>
/// Note: this property is only used for reference tokens /// Note: this property is only used for reference tokens
/// and may be hashed or encrypted for security reasons. /// and may be hashed or encrypted for security reasons.
/// </summary> /// </remarks>
public virtual string? ReferenceId { get; set; } public virtual string? ReferenceId { get; set; }
/// <summary> /// <summary>
/// Gets or sets the status of the current token. /// Gets or sets the status of the token.
/// </summary> /// </summary>
public virtual string? Status { get; set; } public virtual string? Status { get; set; }
/// <summary> /// <summary>
/// Gets or sets the subject associated with the current token. /// Gets or sets the subject of the token.
/// </summary> /// </summary>
public virtual string? Subject { get; set; } public virtual string? Subject { get; set; }
/// <summary> /// <summary>
/// Gets or sets the type of the current token. /// Gets or sets the type of the token.
/// </summary> /// </summary>
public virtual string? Type { get; set; } public virtual string? Type { get; set; }
} }

36
src/OpenIddict.EntityFrameworkCore/Configurations/OpenIddictEntityFrameworkCoreApplicationConfiguration.cs

@ -6,7 +6,9 @@
using System.ComponentModel; using System.ComponentModel;
using System.Diagnostics.CodeAnalysis; using System.Diagnostics.CodeAnalysis;
using System.Text.Json;
using Microsoft.EntityFrameworkCore.Metadata.Builders; using Microsoft.EntityFrameworkCore.Metadata.Builders;
using Microsoft.IdentityModel.Tokens;
using OpenIddict.EntityFrameworkCore.Models; using OpenIddict.EntityFrameworkCore.Models;
namespace OpenIddict.EntityFrameworkCore; namespace OpenIddict.EntityFrameworkCore;
@ -37,11 +39,15 @@ public sealed class OpenIddictEntityFrameworkCoreApplicationConfiguration<
// Entity Framework would throw an exception due to the TKey generic parameter // Entity Framework would throw an exception due to the TKey generic parameter
// being non-nullable when using value types like short, int, long or Guid. // being non-nullable when using value types like short, int, long or Guid.
builder.HasKey(static application => application.Id);
builder.Property(static application => application.ApplicationType) builder.Property(static application => application.ApplicationType)
.HasMaxLength(50); .HasMaxLength(50);
builder.HasMany(static application => application.Authorizations)
.WithOne(static authorization => authorization.Application!)
.HasForeignKey(nameof(OpenIddictEntityFrameworkCoreAuthorization.Application) +
nameof(OpenIddictEntityFrameworkCoreApplication.Id))
.IsRequired(required: false);
builder.HasIndex(static application => application.ClientId) builder.HasIndex(static application => application.ClientId)
.IsUnique(); .IsUnique();
@ -58,6 +64,13 @@ public sealed class OpenIddictEntityFrameworkCoreApplicationConfiguration<
builder.Property(static application => application.ConsentType) builder.Property(static application => application.ConsentType)
.HasMaxLength(50); .HasMaxLength(50);
builder.Property(static application => application.DisplayNames)
.HasConversion(
static value => JsonSerializer.Serialize(value, OpenIddictSerializer.Default.IDictionaryStringString),
static value => JsonSerializer.Deserialize(value, OpenIddictSerializer.Default.IDictionaryStringString));
builder.HasKey(static application => application.Id);
builder.Property(static application => application.Id) builder.Property(static application => application.Id)
.ValueGeneratedOnAdd(); .ValueGeneratedOnAdd();
@ -67,11 +80,20 @@ public sealed class OpenIddictEntityFrameworkCoreApplicationConfiguration<
.HasMaxLength(100); .HasMaxLength(100);
} }
builder.HasMany(static application => application.Authorizations) builder.Property(static application => application.JsonWebKeySet)
.WithOne(static authorization => authorization.Application!) .HasConversion(
.HasForeignKey(nameof(OpenIddictEntityFrameworkCoreAuthorization.Application) + static value => JsonSerializer.Serialize(value, OpenIddictSerializer.Default.JsonWebKeySet),
nameof(OpenIddictEntityFrameworkCoreApplication.Id)) static value => JsonWebKeySet.Create(value));
.IsRequired(required: false);
builder.Property(static application => application.Properties)
.HasConversion(
static value => JsonSerializer.Serialize(value, OpenIddictSerializer.Default.IDictionaryStringJsonElement),
static value => JsonSerializer.Deserialize(value, OpenIddictSerializer.Default.IDictionaryStringJsonElement));
builder.Property(static application => application.Settings)
.HasConversion(
static value => JsonSerializer.Serialize(value, OpenIddictSerializer.Default.IDictionaryStringString),
static value => JsonSerializer.Deserialize(value, OpenIddictSerializer.Default.IDictionaryStringString));
builder.HasMany(static application => application.Tokens) builder.HasMany(static application => application.Tokens)
.WithOne(static token => token.Application!) .WithOne(static token => token.Application!)

16
src/OpenIddict.EntityFrameworkCore/Configurations/OpenIddictEntityFrameworkCoreAuthorizationConfiguration.cs

@ -6,6 +6,7 @@
using System.ComponentModel; using System.ComponentModel;
using System.Diagnostics.CodeAnalysis; using System.Diagnostics.CodeAnalysis;
using System.Text.Json;
using Microsoft.EntityFrameworkCore.Metadata.Builders; using Microsoft.EntityFrameworkCore.Metadata.Builders;
using OpenIddict.EntityFrameworkCore.Models; using OpenIddict.EntityFrameworkCore.Models;
@ -37,8 +38,6 @@ public sealed class OpenIddictEntityFrameworkCoreAuthorizationConfiguration<
// Entity Framework would throw an exception due to the TKey generic parameter // Entity Framework would throw an exception due to the TKey generic parameter
// being non-nullable when using value types like short, int, long or Guid. // being non-nullable when using value types like short, int, long or Guid.
builder.HasKey(static authorization => authorization.Id);
builder.HasIndex( builder.HasIndex(
nameof(OpenIddictEntityFrameworkCoreAuthorization.Application) + nameof(OpenIddictEntityFrameworkCoreApplication.Id), nameof(OpenIddictEntityFrameworkCoreAuthorization.Application) + nameof(OpenIddictEntityFrameworkCoreApplication.Id),
nameof(OpenIddictEntityFrameworkCoreAuthorization.Status), nameof(OpenIddictEntityFrameworkCoreAuthorization.Status),
@ -49,6 +48,8 @@ public sealed class OpenIddictEntityFrameworkCoreAuthorizationConfiguration<
.HasMaxLength(50) .HasMaxLength(50)
.IsConcurrencyToken(); .IsConcurrencyToken();
builder.HasKey(static authorization => authorization.Id);
builder.Property(static authorization => authorization.Id) builder.Property(static authorization => authorization.Id)
.ValueGeneratedOnAdd(); .ValueGeneratedOnAdd();
@ -58,21 +59,26 @@ public sealed class OpenIddictEntityFrameworkCoreAuthorizationConfiguration<
.HasMaxLength(100); .HasMaxLength(100);
} }
builder.Property(static authorization => authorization.Properties)
.HasConversion(
static value => JsonSerializer.Serialize(value, OpenIddictSerializer.Default.IDictionaryStringJsonElement),
static value => JsonSerializer.Deserialize(value, OpenIddictSerializer.Default.IDictionaryStringJsonElement));
builder.Property(static authorization => authorization.Status) builder.Property(static authorization => authorization.Status)
.HasMaxLength(50); .HasMaxLength(50);
builder.Property(static authorization => authorization.Subject) builder.Property(static authorization => authorization.Subject)
.HasMaxLength(400); .HasMaxLength(400);
builder.Property(static authorization => authorization.Type)
.HasMaxLength(50);
builder.HasMany(static authorization => authorization.Tokens) builder.HasMany(static authorization => authorization.Tokens)
.WithOne(static token => token.Authorization!) .WithOne(static token => token.Authorization!)
.HasForeignKey(nameof(OpenIddictEntityFrameworkCoreToken.Authorization) + .HasForeignKey(nameof(OpenIddictEntityFrameworkCoreToken.Authorization) +
nameof(OpenIddictEntityFrameworkCoreAuthorization.Id)) nameof(OpenIddictEntityFrameworkCoreAuthorization.Id))
.IsRequired(required: false); .IsRequired(required: false);
builder.Property(static authorization => authorization.Type)
.HasMaxLength(50);
builder.ToTable("OpenIddictAuthorizations"); builder.ToTable("OpenIddictAuthorizations");
} }
} }

26
src/OpenIddict.EntityFrameworkCore/Configurations/OpenIddictEntityFrameworkCoreResourceConfiguration.cs

@ -6,6 +6,7 @@
using System.ComponentModel; using System.ComponentModel;
using System.Diagnostics.CodeAnalysis; using System.Diagnostics.CodeAnalysis;
using System.Text.Json;
using Microsoft.EntityFrameworkCore.Metadata.Builders; using Microsoft.EntityFrameworkCore.Metadata.Builders;
using OpenIddict.EntityFrameworkCore.Models; using OpenIddict.EntityFrameworkCore.Models;
@ -31,15 +32,22 @@ public sealed class OpenIddictEntityFrameworkCoreResourceConfiguration<
// Entity Framework would throw an exception due to the TKey generic parameter // Entity Framework would throw an exception due to the TKey generic parameter
// being non-nullable when using value types like short, int, long or Guid. // being non-nullable when using value types like short, int, long or Guid.
builder.HasKey(static resource => resource.Id);
builder.HasIndex(static resource => resource.Name)
.IsUnique();
builder.Property(static resource => resource.ConcurrencyToken) builder.Property(static resource => resource.ConcurrencyToken)
.HasMaxLength(50) .HasMaxLength(50)
.IsConcurrencyToken(); .IsConcurrencyToken();
builder.Property(static resource => resource.Descriptions)
.HasConversion(
static value => JsonSerializer.Serialize(value, OpenIddictSerializer.Default.IDictionaryStringString),
static value => JsonSerializer.Deserialize(value, OpenIddictSerializer.Default.IDictionaryStringString));
builder.Property(static resource => resource.DisplayNames)
.HasConversion(
static value => JsonSerializer.Serialize(value, OpenIddictSerializer.Default.IDictionaryStringString),
static value => JsonSerializer.Deserialize(value, OpenIddictSerializer.Default.IDictionaryStringString));
builder.HasKey(static resource => resource.Id);
builder.Property(static resource => resource.Id) builder.Property(static resource => resource.Id)
.ValueGeneratedOnAdd(); .ValueGeneratedOnAdd();
@ -52,6 +60,14 @@ public sealed class OpenIddictEntityFrameworkCoreResourceConfiguration<
builder.Property(static resource => resource.Name) builder.Property(static resource => resource.Name)
.HasMaxLength(200); .HasMaxLength(200);
builder.HasIndex(static resource => resource.Name)
.IsUnique();
builder.Property(static resource => resource.Properties)
.HasConversion(
static value => JsonSerializer.Serialize(value, OpenIddictSerializer.Default.IDictionaryStringJsonElement),
static value => JsonSerializer.Deserialize(value, OpenIddictSerializer.Default.IDictionaryStringJsonElement));
builder.ToTable("OpenIddictResources"); builder.ToTable("OpenIddictResources");
} }
} }

26
src/OpenIddict.EntityFrameworkCore/Configurations/OpenIddictEntityFrameworkCoreScopeConfiguration.cs

@ -6,6 +6,7 @@
using System.ComponentModel; using System.ComponentModel;
using System.Diagnostics.CodeAnalysis; using System.Diagnostics.CodeAnalysis;
using System.Text.Json;
using Microsoft.EntityFrameworkCore.Metadata.Builders; using Microsoft.EntityFrameworkCore.Metadata.Builders;
using OpenIddict.EntityFrameworkCore.Models; using OpenIddict.EntityFrameworkCore.Models;
@ -31,15 +32,22 @@ public sealed class OpenIddictEntityFrameworkCoreScopeConfiguration<
// Entity Framework would throw an exception due to the TKey generic parameter // Entity Framework would throw an exception due to the TKey generic parameter
// being non-nullable when using value types like short, int, long or Guid. // being non-nullable when using value types like short, int, long or Guid.
builder.HasKey(static scope => scope.Id);
builder.HasIndex(static scope => scope.Name)
.IsUnique();
builder.Property(static scope => scope.ConcurrencyToken) builder.Property(static scope => scope.ConcurrencyToken)
.HasMaxLength(50) .HasMaxLength(50)
.IsConcurrencyToken(); .IsConcurrencyToken();
builder.Property(static scope => scope.Descriptions)
.HasConversion(
static value => JsonSerializer.Serialize(value, OpenIddictSerializer.Default.IDictionaryStringString),
static value => JsonSerializer.Deserialize(value, OpenIddictSerializer.Default.IDictionaryStringString));
builder.Property(static scope => scope.DisplayNames)
.HasConversion(
static value => JsonSerializer.Serialize(value, OpenIddictSerializer.Default.IDictionaryStringString),
static value => JsonSerializer.Deserialize(value, OpenIddictSerializer.Default.IDictionaryStringString));
builder.HasKey(static scope => scope.Id);
builder.Property(static scope => scope.Id) builder.Property(static scope => scope.Id)
.ValueGeneratedOnAdd(); .ValueGeneratedOnAdd();
@ -52,6 +60,14 @@ public sealed class OpenIddictEntityFrameworkCoreScopeConfiguration<
builder.Property(static scope => scope.Name) builder.Property(static scope => scope.Name)
.HasMaxLength(200); .HasMaxLength(200);
builder.HasIndex(static scope => scope.Name)
.IsUnique();
builder.Property(static scope => scope.Properties)
.HasConversion(
static value => JsonSerializer.Serialize(value, OpenIddictSerializer.Default.IDictionaryStringJsonElement),
static value => JsonSerializer.Deserialize(value, OpenIddictSerializer.Default.IDictionaryStringJsonElement));
builder.ToTable("OpenIddictScopes"); builder.ToTable("OpenIddictScopes");
} }
} }

16
src/OpenIddict.EntityFrameworkCore/Configurations/OpenIddictEntityFrameworkCoreTokenConfiguration.cs

@ -6,6 +6,7 @@
using System.ComponentModel; using System.ComponentModel;
using System.Diagnostics.CodeAnalysis; using System.Diagnostics.CodeAnalysis;
using System.Text.Json;
using Microsoft.EntityFrameworkCore.Metadata.Builders; using Microsoft.EntityFrameworkCore.Metadata.Builders;
using OpenIddict.EntityFrameworkCore.Models; using OpenIddict.EntityFrameworkCore.Models;
@ -37,11 +38,6 @@ public sealed class OpenIddictEntityFrameworkCoreTokenConfiguration<
// Entity Framework would throw an exception due to the TKey generic parameter // Entity Framework would throw an exception due to the TKey generic parameter
// being non-nullable when using value types like short, int, long or Guid. // being non-nullable when using value types like short, int, long or Guid.
builder.HasKey(static token => token.Id);
builder.HasIndex(static token => token.ReferenceId)
.IsUnique();
builder.HasIndex( builder.HasIndex(
nameof(OpenIddictEntityFrameworkCoreToken.Application) + nameof(OpenIddictEntityFrameworkCoreApplication.Id), nameof(OpenIddictEntityFrameworkCoreToken.Application) + nameof(OpenIddictEntityFrameworkCoreApplication.Id),
nameof(OpenIddictEntityFrameworkCoreToken.Status), nameof(OpenIddictEntityFrameworkCoreToken.Status),
@ -52,6 +48,8 @@ public sealed class OpenIddictEntityFrameworkCoreTokenConfiguration<
.HasMaxLength(50) .HasMaxLength(50)
.IsConcurrencyToken(); .IsConcurrencyToken();
builder.HasKey(static token => token.Id);
builder.Property(static token => token.Id) builder.Property(static token => token.Id)
.ValueGeneratedOnAdd(); .ValueGeneratedOnAdd();
@ -64,12 +62,20 @@ public sealed class OpenIddictEntityFrameworkCoreTokenConfiguration<
builder.Property(static token => token.ReferenceId) builder.Property(static token => token.ReferenceId)
.HasMaxLength(100); .HasMaxLength(100);
builder.HasIndex(static token => token.ReferenceId)
.IsUnique();
builder.Property(static token => token.Status) builder.Property(static token => token.Status)
.HasMaxLength(50); .HasMaxLength(50);
builder.Property(static token => token.Subject) builder.Property(static token => token.Subject)
.HasMaxLength(400); .HasMaxLength(400);
builder.Property(static token => token.Properties)
.HasConversion(
static value => JsonSerializer.Serialize(value, OpenIddictSerializer.Default.IDictionaryStringJsonElement),
static value => JsonSerializer.Deserialize(value, OpenIddictSerializer.Default.IDictionaryStringJsonElement));
builder.Property(static token => token.Type) builder.Property(static token => token.Type)
.HasMaxLength(150); .HasMaxLength(150);

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

@ -10,10 +10,7 @@ using System.Data;
using System.Diagnostics.CodeAnalysis; using System.Diagnostics.CodeAnalysis;
using System.Globalization; using System.Globalization;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using System.Text;
using System.Text.Encodings.Web;
using System.Text.Json; using System.Text.Json;
using Microsoft.Extensions.Caching.Memory;
using Microsoft.Extensions.Options; using Microsoft.Extensions.Options;
using Microsoft.IdentityModel.Tokens; using Microsoft.IdentityModel.Tokens;
using OpenIddict.EntityFrameworkCore.Models; using OpenIddict.EntityFrameworkCore.Models;
@ -30,10 +27,9 @@ public class OpenIddictEntityFrameworkCoreApplicationStore :
OpenIddictEntityFrameworkCoreToken, string> OpenIddictEntityFrameworkCoreToken, string>
{ {
public OpenIddictEntityFrameworkCoreApplicationStore( public OpenIddictEntityFrameworkCoreApplicationStore(
IMemoryCache cache,
IOpenIddictEntityFrameworkCoreContext context, IOpenIddictEntityFrameworkCoreContext context,
IOptionsMonitor<OpenIddictEntityFrameworkCoreOptions> options) IOptionsMonitor<OpenIddictEntityFrameworkCoreOptions> options)
: base(cache, context, options) : base(context, options)
{ {
} }
} }
@ -50,10 +46,9 @@ public class OpenIddictEntityFrameworkCoreApplicationStore<
where TKey : notnull, IEquatable<TKey> where TKey : notnull, IEquatable<TKey>
{ {
public OpenIddictEntityFrameworkCoreApplicationStore( public OpenIddictEntityFrameworkCoreApplicationStore(
IMemoryCache cache,
IOpenIddictEntityFrameworkCoreContext context, IOpenIddictEntityFrameworkCoreContext context,
IOptionsMonitor<OpenIddictEntityFrameworkCoreOptions> options) IOptionsMonitor<OpenIddictEntityFrameworkCoreOptions> options)
: base(cache, context, options) : base(context, options)
{ {
} }
} }
@ -76,20 +71,13 @@ public class OpenIddictEntityFrameworkCoreApplicationStore<
where TKey : notnull, IEquatable<TKey> where TKey : notnull, IEquatable<TKey>
{ {
public OpenIddictEntityFrameworkCoreApplicationStore( public OpenIddictEntityFrameworkCoreApplicationStore(
IMemoryCache cache,
IOpenIddictEntityFrameworkCoreContext context, IOpenIddictEntityFrameworkCoreContext context,
IOptionsMonitor<OpenIddictEntityFrameworkCoreOptions> options) IOptionsMonitor<OpenIddictEntityFrameworkCoreOptions> options)
{ {
Cache = cache ?? throw new ArgumentNullException(nameof(cache));
Context = context ?? throw new ArgumentNullException(nameof(context)); Context = context ?? throw new ArgumentNullException(nameof(context));
Options = options ?? throw new ArgumentNullException(nameof(options)); Options = options ?? throw new ArgumentNullException(nameof(options));
} }
/// <summary>
/// Gets the memory cache associated with the current store.
/// </summary>
protected IMemoryCache Cache { get; }
/// <summary> /// <summary>
/// Gets the database context associated with the current store. /// Gets the database context associated with the current store.
/// </summary> /// </summary>
@ -314,29 +302,18 @@ public class OpenIddictEntityFrameworkCoreApplicationStore<
{ {
ArgumentException.ThrowIfNullOrEmpty(uri); ArgumentException.ThrowIfNullOrEmpty(uri);
// To optimize the efficiency of the query a bit, only applications whose stringified
// PostLogoutRedirectUris contains the specified URI are returned. Once the applications
// are retrieved, a second pass is made to ensure only valid elements are returned.
// Implementers that use this method in a hot path may want to override this method
// to use SQL Server 2016 functions like JSON_VALUE to make the query more efficient.
return ExecuteAsync(cancellationToken); return ExecuteAsync(cancellationToken);
async IAsyncEnumerable<TApplication> ExecuteAsync([EnumeratorCancellation] CancellationToken cancellationToken) async IAsyncEnumerable<TApplication> ExecuteAsync([EnumeratorCancellation] CancellationToken cancellationToken)
{ {
var context = await Context.GetDbContextAsync(cancellationToken); var context = await Context.GetDbContextAsync(cancellationToken);
var applications = (from application in context.Set<TApplication>().AsTracking() await foreach (var application in
where application.PostLogoutRedirectUris!.Contains(uri) (from application in context.Set<TApplication>().AsTracking()
select application).AsAsyncEnumerable().WithCancellation(cancellationToken); where application.PostLogoutRedirectUris!.Contains(uri)
select application).AsAsyncEnumerable().WithCancellation(cancellationToken))
await foreach (var application in applications)
{ {
var uris = await GetPostLogoutRedirectUrisAsync(application, cancellationToken); yield return application;
if (uris.Contains(uri, StringComparer.Ordinal))
{
yield return application;
}
} }
} }
} }
@ -347,29 +324,18 @@ public class OpenIddictEntityFrameworkCoreApplicationStore<
{ {
ArgumentException.ThrowIfNullOrEmpty(uri); ArgumentException.ThrowIfNullOrEmpty(uri);
// To optimize the efficiency of the query a bit, only applications whose stringified
// RedirectUris property contains the specified URI are returned. Once the applications
// are retrieved, a second pass is made to ensure only valid elements are returned.
// Implementers that use this method in a hot path may want to override this method
// to use SQL Server 2016 functions like JSON_VALUE to make the query more efficient.
return ExecuteAsync(cancellationToken); return ExecuteAsync(cancellationToken);
async IAsyncEnumerable<TApplication> ExecuteAsync([EnumeratorCancellation] CancellationToken cancellationToken) async IAsyncEnumerable<TApplication> ExecuteAsync([EnumeratorCancellation] CancellationToken cancellationToken)
{ {
var context = await Context.GetDbContextAsync(cancellationToken); var context = await Context.GetDbContextAsync(cancellationToken);
var applications = (from application in context.Set<TApplication>().AsTracking() await foreach (var application in
where application.RedirectUris!.Contains(uri) (from application in context.Set<TApplication>().AsTracking()
select application).AsAsyncEnumerable().WithCancellation(cancellationToken); where application.RedirectUris!.Contains(uri)
select application).AsAsyncEnumerable().WithCancellation(cancellationToken))
await foreach (var application in applications)
{ {
var uris = await GetRedirectUrisAsync(application, cancellationToken); yield return application;
if (uris.Contains(uri, StringComparer.Ordinal))
{
yield return application;
}
} }
} }
} }
@ -439,37 +405,9 @@ public class OpenIddictEntityFrameworkCoreApplicationStore<
{ {
ArgumentNullException.ThrowIfNull(application); ArgumentNullException.ThrowIfNull(application);
if (string.IsNullOrEmpty(application.DisplayNames)) return new(application.DisplayNames is { Count: > 0 } names
{ ? names.ToImmutableDictionary(static pair => CultureInfo.GetCultureInfo(pair.Key), static pair => pair.Value)
return new(ImmutableDictionary.Create<CultureInfo, string>()); : []);
}
// Note: parsing the stringified display names is an expensive operation.
// To mitigate that, the resulting object is stored in the memory cache.
var key = string.Concat("7762c378-c113-4564-b14b-1402b3949aaa", "\x1e", application.DisplayNames);
var names = Cache.GetOrCreate(key, entry =>
{
entry.SetPriority(CacheItemPriority.High)
.SetSlidingExpiration(TimeSpan.FromMinutes(1));
using var document = JsonDocument.Parse(application.DisplayNames);
var builder = ImmutableDictionary.CreateBuilder<CultureInfo, string>();
foreach (var property in document.RootElement.EnumerateObject())
{
var value = property.Value.GetString();
if (string.IsNullOrEmpty(value))
{
continue;
}
builder[CultureInfo.GetCultureInfo(property.Name)] = value;
}
return builder.ToImmutable();
})!;
return new(names);
} }
/// <inheritdoc/> /// <inheritdoc/>
@ -485,23 +423,7 @@ public class OpenIddictEntityFrameworkCoreApplicationStore<
{ {
ArgumentNullException.ThrowIfNull(application); ArgumentNullException.ThrowIfNull(application);
if (string.IsNullOrEmpty(application.JsonWebKeySet)) return new(application.JsonWebKeySet);
{
return new(result: null);
}
// Note: parsing the stringified JSON Web Key Set is an expensive operation.
// To mitigate that, the resulting object is stored in the memory cache.
var key = string.Concat("1e0a697d-0623-481a-927a-5e6c31458782", "\x1e", application.JsonWebKeySet);
var set = Cache.GetOrCreate(key, entry =>
{
entry.SetPriority(CacheItemPriority.High)
.SetSlidingExpiration(TimeSpan.FromMinutes(1));
return JsonWebKeySet.Create(application.JsonWebKeySet);
})!;
return new(set);
} }
/// <inheritdoc/> /// <inheritdoc/>
@ -509,37 +431,7 @@ public class OpenIddictEntityFrameworkCoreApplicationStore<
{ {
ArgumentNullException.ThrowIfNull(application); ArgumentNullException.ThrowIfNull(application);
if (string.IsNullOrEmpty(application.Permissions)) return new(application.Permissions is { Length: > 0 } permissions ? [.. permissions] : []);
{
return new([]);
}
// Note: parsing the stringified permissions is an expensive operation.
// To mitigate that, the resulting array is stored in the memory cache.
var key = string.Concat("0347e0aa-3a26-410a-97e8-a83bdeb21a1f", "\x1e", application.Permissions);
var permissions = Cache.GetOrCreate(key, entry =>
{
entry.SetPriority(CacheItemPriority.High)
.SetSlidingExpiration(TimeSpan.FromMinutes(1));
using var document = JsonDocument.Parse(application.Permissions);
var builder = ImmutableArray.CreateBuilder<string>(document.RootElement.GetArrayLength());
foreach (var element in document.RootElement.EnumerateArray())
{
var value = element.GetString();
if (string.IsNullOrEmpty(value))
{
continue;
}
builder.Add(value);
}
return builder.ToImmutable();
});
return new(permissions);
} }
/// <inheritdoc/> /// <inheritdoc/>
@ -547,37 +439,7 @@ public class OpenIddictEntityFrameworkCoreApplicationStore<
{ {
ArgumentNullException.ThrowIfNull(application); ArgumentNullException.ThrowIfNull(application);
if (string.IsNullOrEmpty(application.PostLogoutRedirectUris)) return new(application.PostLogoutRedirectUris is { Length: > 0 } uris ? [.. uris] : []);
{
return new([]);
}
// Note: parsing the stringified URIs is an expensive operation.
// To mitigate that, the resulting array is stored in the memory cache.
var key = string.Concat("fb14dfb9-9216-4b77-bfa9-7e85f8201ff4", "\x1e", application.PostLogoutRedirectUris);
var uris = Cache.GetOrCreate(key, entry =>
{
entry.SetPriority(CacheItemPriority.High)
.SetSlidingExpiration(TimeSpan.FromMinutes(1));
using var document = JsonDocument.Parse(application.PostLogoutRedirectUris);
var builder = ImmutableArray.CreateBuilder<string>(document.RootElement.GetArrayLength());
foreach (var element in document.RootElement.EnumerateArray())
{
var value = element.GetString();
if (string.IsNullOrEmpty(value))
{
continue;
}
builder.Add(value);
}
return builder.ToImmutable();
});
return new(uris);
} }
/// <inheritdoc/> /// <inheritdoc/>
@ -585,31 +447,7 @@ public class OpenIddictEntityFrameworkCoreApplicationStore<
{ {
ArgumentNullException.ThrowIfNull(application); ArgumentNullException.ThrowIfNull(application);
if (string.IsNullOrEmpty(application.Properties)) return new(application.Properties is { Count: > 0 } properties ? [.. properties] : []);
{
return new(ImmutableDictionary.Create<string, JsonElement>());
}
// Note: parsing the stringified properties is an expensive operation.
// To mitigate that, the resulting object is stored in the memory cache.
var key = string.Concat("2e3e9680-5654-48d8-a27d-b8bb4f0f1d50", "\x1e", application.Properties);
var properties = Cache.GetOrCreate(key, entry =>
{
entry.SetPriority(CacheItemPriority.High)
.SetSlidingExpiration(TimeSpan.FromMinutes(1));
using var document = JsonDocument.Parse(application.Properties);
var builder = ImmutableDictionary.CreateBuilder<string, JsonElement>();
foreach (var property in document.RootElement.EnumerateObject())
{
builder[property.Name] = property.Value.Clone();
}
return builder.ToImmutable();
})!;
return new(properties);
} }
/// <inheritdoc/> /// <inheritdoc/>
@ -617,37 +455,7 @@ public class OpenIddictEntityFrameworkCoreApplicationStore<
{ {
ArgumentNullException.ThrowIfNull(application); ArgumentNullException.ThrowIfNull(application);
if (string.IsNullOrEmpty(application.RedirectUris)) return new(application.RedirectUris is { Length: > 0 } uris ? [.. uris] : []);
{
return new([]);
}
// Note: parsing the stringified URIs is an expensive operation.
// To mitigate that, the resulting array is stored in the memory cache.
var key = string.Concat("851d6f08-2ee0-4452-bbe5-ab864611ecaa", "\x1e", application.RedirectUris);
var uris = Cache.GetOrCreate(key, entry =>
{
entry.SetPriority(CacheItemPriority.High)
.SetSlidingExpiration(TimeSpan.FromMinutes(1));
using var document = JsonDocument.Parse(application.RedirectUris);
var builder = ImmutableArray.CreateBuilder<string>(document.RootElement.GetArrayLength());
foreach (var element in document.RootElement.EnumerateArray())
{
var value = element.GetString();
if (string.IsNullOrEmpty(value))
{
continue;
}
builder.Add(value);
}
return builder.ToImmutable();
});
return new(uris);
} }
/// <inheritdoc/> /// <inheritdoc/>
@ -655,37 +463,7 @@ public class OpenIddictEntityFrameworkCoreApplicationStore<
{ {
ArgumentNullException.ThrowIfNull(application); ArgumentNullException.ThrowIfNull(application);
if (string.IsNullOrEmpty(application.Requirements)) return new(application.Requirements is { Length: > 0 } requirements ? [.. requirements] : []);
{
return new([]);
}
// Note: parsing the stringified requirements is an expensive operation.
// To mitigate that, the resulting array is stored in the memory cache.
var key = string.Concat("b4808a89-8969-4512-895f-a909c62a8995", "\x1e", application.Requirements);
var requirements = Cache.GetOrCreate(key, entry =>
{
entry.SetPriority(CacheItemPriority.High)
.SetSlidingExpiration(TimeSpan.FromMinutes(1));
using var document = JsonDocument.Parse(application.Requirements);
var builder = ImmutableArray.CreateBuilder<string>(document.RootElement.GetArrayLength());
foreach (var element in document.RootElement.EnumerateArray())
{
var value = element.GetString();
if (string.IsNullOrEmpty(value))
{
continue;
}
builder.Add(value);
}
return builder.ToImmutable();
});
return new(requirements);
} }
/// <inheritdoc/> /// <inheritdoc/>
@ -693,37 +471,7 @@ public class OpenIddictEntityFrameworkCoreApplicationStore<
{ {
ArgumentNullException.ThrowIfNull(application); ArgumentNullException.ThrowIfNull(application);
if (string.IsNullOrEmpty(application.Settings)) return new(application.Settings is { Count: > 0 } settings ? [.. 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/> /// <inheritdoc/>
@ -852,32 +600,9 @@ public class OpenIddictEntityFrameworkCoreApplicationStore<
{ {
ArgumentNullException.ThrowIfNull(application); ArgumentNullException.ThrowIfNull(application);
if (names is not { Count: > 0 }) application.DisplayNames = names is { Count: > 0 }
{ ? names.ToImmutableDictionary(static pair => pair.Key.Name, static pair => pair.Value)
application.DisplayNames = null; : null;
return ValueTask.CompletedTask;
}
using var stream = new MemoryStream();
using var writer = new Utf8JsonWriter(stream, new JsonWriterOptions
{
Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping,
Indented = false
});
writer.WriteStartObject();
foreach (var pair in names)
{
writer.WritePropertyName(pair.Key.Name);
writer.WriteStringValue(pair.Value);
}
writer.WriteEndObject();
writer.Flush();
application.DisplayNames = Encoding.UTF8.GetString(stream.ToArray());
return ValueTask.CompletedTask; return ValueTask.CompletedTask;
} }
@ -887,7 +612,7 @@ public class OpenIddictEntityFrameworkCoreApplicationStore<
{ {
ArgumentNullException.ThrowIfNull(application); ArgumentNullException.ThrowIfNull(application);
application.JsonWebKeySet = set is not null ? JsonSerializer.Serialize(set, OpenIddictSerializer.Default.JsonWebKeySet) : null; application.JsonWebKeySet = set;
return ValueTask.CompletedTask; return ValueTask.CompletedTask;
} }
@ -904,24 +629,7 @@ public class OpenIddictEntityFrameworkCoreApplicationStore<
return ValueTask.CompletedTask; return ValueTask.CompletedTask;
} }
using var stream = new MemoryStream(); application.Permissions = permissions.ToArray();
using var writer = new Utf8JsonWriter(stream, new JsonWriterOptions
{
Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping,
Indented = false
});
writer.WriteStartArray();
foreach (var permission in permissions)
{
writer.WriteStringValue(permission);
}
writer.WriteEndArray();
writer.Flush();
application.Permissions = Encoding.UTF8.GetString(stream.ToArray());
return ValueTask.CompletedTask; return ValueTask.CompletedTask;
} }
@ -939,24 +647,7 @@ public class OpenIddictEntityFrameworkCoreApplicationStore<
return ValueTask.CompletedTask; return ValueTask.CompletedTask;
} }
using var stream = new MemoryStream(); application.PostLogoutRedirectUris = uris.ToArray();
using var writer = new Utf8JsonWriter(stream, new JsonWriterOptions
{
Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping,
Indented = false
});
writer.WriteStartArray();
foreach (var uri in uris)
{
writer.WriteStringValue(uri);
}
writer.WriteEndArray();
writer.Flush();
application.PostLogoutRedirectUris = Encoding.UTF8.GetString(stream.ToArray());
return ValueTask.CompletedTask; return ValueTask.CompletedTask;
} }
@ -967,32 +658,7 @@ public class OpenIddictEntityFrameworkCoreApplicationStore<
{ {
ArgumentNullException.ThrowIfNull(application); ArgumentNullException.ThrowIfNull(application);
if (properties is not { Count: > 0 }) application.Properties = properties;
{
application.Properties = null;
return ValueTask.CompletedTask;
}
using var stream = new MemoryStream();
using var writer = new Utf8JsonWriter(stream, new JsonWriterOptions
{
Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping,
Indented = false
});
writer.WriteStartObject();
foreach (var property in properties)
{
writer.WritePropertyName(property.Key);
property.Value.WriteTo(writer);
}
writer.WriteEndObject();
writer.Flush();
application.Properties = Encoding.UTF8.GetString(stream.ToArray());
return ValueTask.CompletedTask; return ValueTask.CompletedTask;
} }
@ -1010,24 +676,7 @@ public class OpenIddictEntityFrameworkCoreApplicationStore<
return ValueTask.CompletedTask; return ValueTask.CompletedTask;
} }
using var stream = new MemoryStream(); application.RedirectUris = uris.ToArray();
using var writer = new Utf8JsonWriter(stream, new JsonWriterOptions
{
Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping,
Indented = false
});
writer.WriteStartArray();
foreach (var uri in uris)
{
writer.WriteStringValue(uri);
}
writer.WriteEndArray();
writer.Flush();
application.RedirectUris = Encoding.UTF8.GetString(stream.ToArray());
return ValueTask.CompletedTask; return ValueTask.CompletedTask;
} }
@ -1044,24 +693,7 @@ public class OpenIddictEntityFrameworkCoreApplicationStore<
return ValueTask.CompletedTask; return ValueTask.CompletedTask;
} }
using var stream = new MemoryStream(); application.Requirements = requirements.ToArray();
using var writer = new Utf8JsonWriter(stream, new JsonWriterOptions
{
Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping,
Indented = false
});
writer.WriteStartArray();
foreach (var requirement in requirements)
{
writer.WriteStringValue(requirement);
}
writer.WriteEndArray();
writer.Flush();
application.Requirements = Encoding.UTF8.GetString(stream.ToArray());
return ValueTask.CompletedTask; return ValueTask.CompletedTask;
} }
@ -1072,32 +704,7 @@ public class OpenIddictEntityFrameworkCoreApplicationStore<
{ {
ArgumentNullException.ThrowIfNull(application); ArgumentNullException.ThrowIfNull(application);
if (settings is not { Count: > 0 }) application.Settings = settings;
{
application.Settings = null;
return ValueTask.CompletedTask;
}
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 ValueTask.CompletedTask; return ValueTask.CompletedTask;
} }

127
src/OpenIddict.EntityFrameworkCore/Stores/OpenIddictEntityFrameworkCoreAuthorizationStore.cs

@ -9,10 +9,7 @@ using System.ComponentModel;
using System.Data; using System.Data;
using System.Diagnostics.CodeAnalysis; using System.Diagnostics.CodeAnalysis;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using System.Text;
using System.Text.Encodings.Web;
using System.Text.Json; using System.Text.Json;
using Microsoft.Extensions.Caching.Memory;
using Microsoft.Extensions.Options; using Microsoft.Extensions.Options;
using OpenIddict.EntityFrameworkCore.Models; using OpenIddict.EntityFrameworkCore.Models;
using static OpenIddict.Abstractions.OpenIddictExceptions; using static OpenIddict.Abstractions.OpenIddictExceptions;
@ -28,10 +25,9 @@ public class OpenIddictEntityFrameworkCoreAuthorizationStore :
OpenIddictEntityFrameworkCoreToken, string> OpenIddictEntityFrameworkCoreToken, string>
{ {
public OpenIddictEntityFrameworkCoreAuthorizationStore( public OpenIddictEntityFrameworkCoreAuthorizationStore(
IMemoryCache cache,
IOpenIddictEntityFrameworkCoreContext context, IOpenIddictEntityFrameworkCoreContext context,
IOptionsMonitor<OpenIddictEntityFrameworkCoreOptions> options) IOptionsMonitor<OpenIddictEntityFrameworkCoreOptions> options)
: base(cache, context, options) : base(context, options)
{ {
} }
} }
@ -48,10 +44,9 @@ public class OpenIddictEntityFrameworkCoreAuthorizationStore<
where TKey : notnull, IEquatable<TKey> where TKey : notnull, IEquatable<TKey>
{ {
public OpenIddictEntityFrameworkCoreAuthorizationStore( public OpenIddictEntityFrameworkCoreAuthorizationStore(
IMemoryCache cache,
IOpenIddictEntityFrameworkCoreContext context, IOpenIddictEntityFrameworkCoreContext context,
IOptionsMonitor<OpenIddictEntityFrameworkCoreOptions> options) IOptionsMonitor<OpenIddictEntityFrameworkCoreOptions> options)
: base(cache, context, options) : base(context, options)
{ {
} }
} }
@ -74,20 +69,13 @@ public class OpenIddictEntityFrameworkCoreAuthorizationStore<
where TKey : notnull, IEquatable<TKey> where TKey : notnull, IEquatable<TKey>
{ {
public OpenIddictEntityFrameworkCoreAuthorizationStore( public OpenIddictEntityFrameworkCoreAuthorizationStore(
IMemoryCache cache,
IOpenIddictEntityFrameworkCoreContext context, IOpenIddictEntityFrameworkCoreContext context,
IOptionsMonitor<OpenIddictEntityFrameworkCoreOptions> options) IOptionsMonitor<OpenIddictEntityFrameworkCoreOptions> options)
{ {
Cache = cache ?? throw new ArgumentNullException(nameof(cache));
Context = context ?? throw new ArgumentNullException(nameof(context)); Context = context ?? throw new ArgumentNullException(nameof(context));
Options = options ?? throw new ArgumentNullException(nameof(options)); Options = options ?? throw new ArgumentNullException(nameof(options));
} }
/// <summary>
/// Gets the memory cache associated with the current store.
/// </summary>
protected IMemoryCache Cache { get; }
/// <summary> /// <summary>
/// Gets the database context associated with the current store. /// Gets the database context associated with the current store.
/// </summary> /// </summary>
@ -393,12 +381,7 @@ public class OpenIddictEntityFrameworkCoreAuthorizationStore<
{ {
ArgumentNullException.ThrowIfNull(authorization); ArgumentNullException.ThrowIfNull(authorization);
if (authorization.CreationDate is null) return new(authorization.CreationDate is DateTime date ? DateTime.SpecifyKind(date, DateTimeKind.Utc) : null);
{
return new(result: null);
}
return new(DateTime.SpecifyKind(authorization.CreationDate.Value, DateTimeKind.Utc));
} }
/// <inheritdoc/> /// <inheritdoc/>
@ -414,31 +397,7 @@ public class OpenIddictEntityFrameworkCoreAuthorizationStore<
{ {
ArgumentNullException.ThrowIfNull(authorization); ArgumentNullException.ThrowIfNull(authorization);
if (string.IsNullOrEmpty(authorization.Properties)) return new(authorization.Properties is { Count: > 0 } properties ? [.. properties] : []);
{
return new(ImmutableDictionary.Create<string, JsonElement>());
}
// Note: parsing the stringified properties is an expensive operation.
// To mitigate that, the resulting object is stored in the memory cache.
var key = string.Concat("68056e1a-dbcf-412b-9a6a-d791c7dbe726", "\x1e", authorization.Properties);
var properties = Cache.GetOrCreate(key, entry =>
{
entry.SetPriority(CacheItemPriority.High)
.SetSlidingExpiration(TimeSpan.FromMinutes(1));
using var document = JsonDocument.Parse(authorization.Properties);
var builder = ImmutableDictionary.CreateBuilder<string, JsonElement>();
foreach (var property in document.RootElement.EnumerateObject())
{
builder[property.Name] = property.Value.Clone();
}
return builder.ToImmutable();
})!;
return new(properties);
} }
/// <inheritdoc/> /// <inheritdoc/>
@ -446,37 +405,7 @@ public class OpenIddictEntityFrameworkCoreAuthorizationStore<
{ {
ArgumentNullException.ThrowIfNull(authorization); ArgumentNullException.ThrowIfNull(authorization);
if (string.IsNullOrEmpty(authorization.Scopes)) return new(authorization.Scopes is { Length: > 0 } scopes ? [.. scopes] : []);
{
return new([]);
}
// Note: parsing the stringified scopes is an expensive operation.
// To mitigate that, the resulting array is stored in the memory cache.
var key = string.Concat("2ba4ab0f-e2ec-4d48-b3bd-28e2bb660c75", "\x1e", authorization.Scopes);
var scopes = Cache.GetOrCreate(key, entry =>
{
entry.SetPriority(CacheItemPriority.High)
.SetSlidingExpiration(TimeSpan.FromMinutes(1));
using var document = JsonDocument.Parse(authorization.Scopes);
var builder = ImmutableArray.CreateBuilder<string>(document.RootElement.GetArrayLength());
foreach (var element in document.RootElement.EnumerateArray())
{
var value = element.GetString();
if (string.IsNullOrEmpty(value))
{
continue;
}
builder.Add(value);
}
return builder.ToImmutable();
});
return new(scopes);
} }
/// <inheritdoc/> /// <inheritdoc/>
@ -939,32 +868,7 @@ public class OpenIddictEntityFrameworkCoreAuthorizationStore<
{ {
ArgumentNullException.ThrowIfNull(authorization); ArgumentNullException.ThrowIfNull(authorization);
if (properties is not { Count: > 0 }) authorization.Properties = properties;
{
authorization.Properties = null;
return ValueTask.CompletedTask;
}
using var stream = new MemoryStream();
using var writer = new Utf8JsonWriter(stream, new JsonWriterOptions
{
Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping,
Indented = false
});
writer.WriteStartObject();
foreach (var property in properties)
{
writer.WritePropertyName(property.Key);
property.Value.WriteTo(writer);
}
writer.WriteEndObject();
writer.Flush();
authorization.Properties = Encoding.UTF8.GetString(stream.ToArray());
return ValueTask.CompletedTask; return ValueTask.CompletedTask;
} }
@ -982,24 +886,7 @@ public class OpenIddictEntityFrameworkCoreAuthorizationStore<
return ValueTask.CompletedTask; return ValueTask.CompletedTask;
} }
using var stream = new MemoryStream(); authorization.Scopes = scopes.ToArray();
using var writer = new Utf8JsonWriter(stream, new JsonWriterOptions
{
Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping,
Indented = false
});
writer.WriteStartArray();
foreach (var scope in scopes)
{
writer.WriteStringValue(scope);
}
writer.WriteEndArray();
writer.Flush();
authorization.Scopes = Encoding.UTF8.GetString(stream.ToArray());
return ValueTask.CompletedTask; return ValueTask.CompletedTask;
} }

195
src/OpenIddict.EntityFrameworkCore/Stores/OpenIddictEntityFrameworkCoreResourceStore.cs

@ -9,10 +9,7 @@ using System.ComponentModel;
using System.Diagnostics.CodeAnalysis; using System.Diagnostics.CodeAnalysis;
using System.Globalization; using System.Globalization;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using System.Text;
using System.Text.Encodings.Web;
using System.Text.Json; using System.Text.Json;
using Microsoft.Extensions.Caching.Memory;
using Microsoft.Extensions.Options; using Microsoft.Extensions.Options;
using OpenIddict.EntityFrameworkCore.Models; using OpenIddict.EntityFrameworkCore.Models;
using static OpenIddict.Abstractions.OpenIddictExceptions; using static OpenIddict.Abstractions.OpenIddictExceptions;
@ -25,10 +22,9 @@ namespace OpenIddict.EntityFrameworkCore;
public class OpenIddictEntityFrameworkCoreResourceStore : OpenIddictEntityFrameworkCoreResourceStore<OpenIddictEntityFrameworkCoreResource, string> public class OpenIddictEntityFrameworkCoreResourceStore : OpenIddictEntityFrameworkCoreResourceStore<OpenIddictEntityFrameworkCoreResource, string>
{ {
public OpenIddictEntityFrameworkCoreResourceStore( public OpenIddictEntityFrameworkCoreResourceStore(
IMemoryCache cache,
IOpenIddictEntityFrameworkCoreContext context, IOpenIddictEntityFrameworkCoreContext context,
IOptionsMonitor<OpenIddictEntityFrameworkCoreOptions> options) IOptionsMonitor<OpenIddictEntityFrameworkCoreOptions> options)
: base(cache, context, options) : base(context, options)
{ {
} }
} }
@ -42,10 +38,9 @@ public class OpenIddictEntityFrameworkCoreResourceStore<
where TKey : notnull, IEquatable<TKey> where TKey : notnull, IEquatable<TKey>
{ {
public OpenIddictEntityFrameworkCoreResourceStore( public OpenIddictEntityFrameworkCoreResourceStore(
IMemoryCache cache,
IOpenIddictEntityFrameworkCoreContext context, IOpenIddictEntityFrameworkCoreContext context,
IOptionsMonitor<OpenIddictEntityFrameworkCoreOptions> options) IOptionsMonitor<OpenIddictEntityFrameworkCoreOptions> options)
: base(cache, context, options) : base(context, options)
{ {
} }
} }
@ -62,20 +57,13 @@ public class OpenIddictEntityFrameworkCoreResourceStore<
where TKey : notnull, IEquatable<TKey> where TKey : notnull, IEquatable<TKey>
{ {
public OpenIddictEntityFrameworkCoreResourceStore( public OpenIddictEntityFrameworkCoreResourceStore(
IMemoryCache cache,
IOpenIddictEntityFrameworkCoreContext context, IOpenIddictEntityFrameworkCoreContext context,
IOptionsMonitor<OpenIddictEntityFrameworkCoreOptions> options) IOptionsMonitor<OpenIddictEntityFrameworkCoreOptions> options)
{ {
Cache = cache ?? throw new ArgumentNullException(nameof(cache));
Context = context ?? throw new ArgumentNullException(nameof(context)); Context = context ?? throw new ArgumentNullException(nameof(context));
Options = options ?? throw new ArgumentNullException(nameof(options)); Options = options ?? throw new ArgumentNullException(nameof(options));
} }
/// <summary>
/// Gets the memory cache associated with the current store.
/// </summary>
protected IMemoryCache Cache { get; }
/// <summary> /// <summary>
/// Gets the database context associated with the current store. /// Gets the database context associated with the current store.
/// </summary> /// </summary>
@ -232,37 +220,9 @@ public class OpenIddictEntityFrameworkCoreResourceStore<
{ {
ArgumentNullException.ThrowIfNull(resource); ArgumentNullException.ThrowIfNull(resource);
if (string.IsNullOrEmpty(resource.Descriptions)) return new(resource.Descriptions is { Count: > 0 } descriptions
{ ? descriptions.ToImmutableDictionary(static pair => CultureInfo.GetCultureInfo(pair.Key), static pair => pair.Value)
return new(ImmutableDictionary.Create<CultureInfo, string>()); : []);
}
// Note: parsing the stringified descriptions is an expensive operation.
// To mitigate that, the resulting object is stored in the memory cache.
var key = string.Concat("20e1ab51-b505-40b0-9a10-d0596b9f2143", "\x1e", resource.Descriptions);
var descriptions = Cache.GetOrCreate(key, entry =>
{
entry.SetPriority(CacheItemPriority.High)
.SetSlidingExpiration(TimeSpan.FromMinutes(1));
using var document = JsonDocument.Parse(resource.Descriptions);
var builder = ImmutableDictionary.CreateBuilder<CultureInfo, string>();
foreach (var property in document.RootElement.EnumerateObject())
{
var value = property.Value.GetString();
if (string.IsNullOrEmpty(value))
{
continue;
}
builder[CultureInfo.GetCultureInfo(property.Name)] = value;
}
return builder.ToImmutable();
})!;
return new(descriptions);
} }
/// <inheritdoc/> /// <inheritdoc/>
@ -278,37 +238,9 @@ public class OpenIddictEntityFrameworkCoreResourceStore<
{ {
ArgumentNullException.ThrowIfNull(resource); ArgumentNullException.ThrowIfNull(resource);
if (string.IsNullOrEmpty(resource.DisplayNames)) return new(resource.DisplayNames is { Count: > 0 } names
{ ? names.ToImmutableDictionary(static pair => CultureInfo.GetCultureInfo(pair.Key), static pair => pair.Value)
return new(ImmutableDictionary.Create<CultureInfo, string>()); : []);
}
// Note: parsing the stringified display names is an expensive operation.
// To mitigate that, the resulting object is stored in the memory cache.
var key = string.Concat("65c3ea08-ded7-488f-b001-5098de04172b", "\x1e", resource.DisplayNames);
var names = Cache.GetOrCreate(key, entry =>
{
entry.SetPriority(CacheItemPriority.High)
.SetSlidingExpiration(TimeSpan.FromMinutes(1));
using var document = JsonDocument.Parse(resource.DisplayNames);
var builder = ImmutableDictionary.CreateBuilder<CultureInfo, string>();
foreach (var property in document.RootElement.EnumerateObject())
{
var value = property.Value.GetString();
if (string.IsNullOrEmpty(value))
{
continue;
}
builder[CultureInfo.GetCultureInfo(property.Name)] = value;
}
return builder.ToImmutable();
})!;
return new(names);
} }
/// <inheritdoc/> /// <inheritdoc/>
@ -332,31 +264,7 @@ public class OpenIddictEntityFrameworkCoreResourceStore<
{ {
ArgumentNullException.ThrowIfNull(resource); ArgumentNullException.ThrowIfNull(resource);
if (string.IsNullOrEmpty(resource.Properties)) return new(resource.Properties is { Count: > 0 } properties ? [.. properties] : []);
{
return new(ImmutableDictionary.Create<string, JsonElement>());
}
// Note: parsing the stringified properties is an expensive operation.
// To mitigate that, the resulting object is stored in the memory cache.
var key = string.Concat("1f414494-e5aa-4cad-9c5f-4f98688e3623", "\x1e", resource.Properties);
var properties = Cache.GetOrCreate(key, entry =>
{
entry.SetPriority(CacheItemPriority.High)
.SetSlidingExpiration(TimeSpan.FromMinutes(1));
using var document = JsonDocument.Parse(resource.Properties);
var builder = ImmutableDictionary.CreateBuilder<string, JsonElement>();
foreach (var property in document.RootElement.EnumerateObject())
{
builder[property.Name] = property.Value.Clone();
}
return builder.ToImmutable();
})!;
return new(properties);
} }
/// <inheritdoc/> /// <inheritdoc/>
@ -434,32 +342,9 @@ public class OpenIddictEntityFrameworkCoreResourceStore<
{ {
ArgumentNullException.ThrowIfNull(resource); ArgumentNullException.ThrowIfNull(resource);
if (descriptions is not { Count: > 0 }) resource.Descriptions = descriptions is { Count: > 0 }
{ ? descriptions.ToImmutableDictionary(static pair => pair.Key.Name, static pair => pair.Value)
resource.Descriptions = null; : null;
return ValueTask.CompletedTask;
}
using var stream = new MemoryStream();
using var writer = new Utf8JsonWriter(stream, new JsonWriterOptions
{
Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping,
Indented = false
});
writer.WriteStartObject();
foreach (var description in descriptions)
{
writer.WritePropertyName(description.Key.Name);
writer.WriteStringValue(description.Value);
}
writer.WriteEndObject();
writer.Flush();
resource.Descriptions = Encoding.UTF8.GetString(stream.ToArray());
return ValueTask.CompletedTask; return ValueTask.CompletedTask;
} }
@ -480,32 +365,9 @@ public class OpenIddictEntityFrameworkCoreResourceStore<
{ {
ArgumentNullException.ThrowIfNull(resource); ArgumentNullException.ThrowIfNull(resource);
if (names is not { Count: > 0 }) resource.DisplayNames = names is { Count: > 0 }
{ ? names.ToImmutableDictionary(static pair => pair.Key.Name, static pair => pair.Value)
resource.DisplayNames = null; : null;
return ValueTask.CompletedTask;
}
using var stream = new MemoryStream();
using var writer = new Utf8JsonWriter(stream, new JsonWriterOptions
{
Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping,
Indented = false
});
writer.WriteStartObject();
foreach (var name in names)
{
writer.WritePropertyName(name.Key.Name);
writer.WriteStringValue(name.Value);
}
writer.WriteEndObject();
writer.Flush();
resource.DisplayNames = Encoding.UTF8.GetString(stream.ToArray());
return ValueTask.CompletedTask; return ValueTask.CompletedTask;
} }
@ -526,32 +388,7 @@ public class OpenIddictEntityFrameworkCoreResourceStore<
{ {
ArgumentNullException.ThrowIfNull(resource); ArgumentNullException.ThrowIfNull(resource);
if (properties is not { Count: > 0 }) resource.Properties = properties;
{
resource.Properties = null;
return ValueTask.CompletedTask;
}
using var stream = new MemoryStream();
using var writer = new Utf8JsonWriter(stream, new JsonWriterOptions
{
Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping,
Indented = false
});
writer.WriteStartObject();
foreach (var property in properties)
{
writer.WritePropertyName(property.Key);
property.Value.WriteTo(writer);
}
writer.WriteEndObject();
writer.Flush();
resource.Properties = Encoding.UTF8.GetString(stream.ToArray());
return ValueTask.CompletedTask; return ValueTask.CompletedTask;
} }

266
src/OpenIddict.EntityFrameworkCore/Stores/OpenIddictEntityFrameworkCoreScopeStore.cs

@ -9,10 +9,7 @@ using System.ComponentModel;
using System.Diagnostics.CodeAnalysis; using System.Diagnostics.CodeAnalysis;
using System.Globalization; using System.Globalization;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using System.Text;
using System.Text.Encodings.Web;
using System.Text.Json; using System.Text.Json;
using Microsoft.Extensions.Caching.Memory;
using Microsoft.Extensions.Options; using Microsoft.Extensions.Options;
using OpenIddict.EntityFrameworkCore.Models; using OpenIddict.EntityFrameworkCore.Models;
using static OpenIddict.Abstractions.OpenIddictExceptions; using static OpenIddict.Abstractions.OpenIddictExceptions;
@ -25,10 +22,9 @@ namespace OpenIddict.EntityFrameworkCore;
public class OpenIddictEntityFrameworkCoreScopeStore : OpenIddictEntityFrameworkCoreScopeStore<OpenIddictEntityFrameworkCoreScope, string> public class OpenIddictEntityFrameworkCoreScopeStore : OpenIddictEntityFrameworkCoreScopeStore<OpenIddictEntityFrameworkCoreScope, string>
{ {
public OpenIddictEntityFrameworkCoreScopeStore( public OpenIddictEntityFrameworkCoreScopeStore(
IMemoryCache cache,
IOpenIddictEntityFrameworkCoreContext context, IOpenIddictEntityFrameworkCoreContext context,
IOptionsMonitor<OpenIddictEntityFrameworkCoreOptions> options) IOptionsMonitor<OpenIddictEntityFrameworkCoreOptions> options)
: base(cache, context, options) : base(context, options)
{ {
} }
} }
@ -42,10 +38,9 @@ public class OpenIddictEntityFrameworkCoreScopeStore<
where TKey : notnull, IEquatable<TKey> where TKey : notnull, IEquatable<TKey>
{ {
public OpenIddictEntityFrameworkCoreScopeStore( public OpenIddictEntityFrameworkCoreScopeStore(
IMemoryCache cache,
IOpenIddictEntityFrameworkCoreContext context, IOpenIddictEntityFrameworkCoreContext context,
IOptionsMonitor<OpenIddictEntityFrameworkCoreOptions> options) IOptionsMonitor<OpenIddictEntityFrameworkCoreOptions> options)
: base(cache, context, options) : base(context, options)
{ {
} }
} }
@ -62,20 +57,13 @@ public class OpenIddictEntityFrameworkCoreScopeStore<
where TKey : notnull, IEquatable<TKey> where TKey : notnull, IEquatable<TKey>
{ {
public OpenIddictEntityFrameworkCoreScopeStore( public OpenIddictEntityFrameworkCoreScopeStore(
IMemoryCache cache,
IOpenIddictEntityFrameworkCoreContext context, IOpenIddictEntityFrameworkCoreContext context,
IOptionsMonitor<OpenIddictEntityFrameworkCoreOptions> options) IOptionsMonitor<OpenIddictEntityFrameworkCoreOptions> options)
{ {
Cache = cache ?? throw new ArgumentNullException(nameof(cache));
Context = context ?? throw new ArgumentNullException(nameof(context)); Context = context ?? throw new ArgumentNullException(nameof(context));
Options = options ?? throw new ArgumentNullException(nameof(options)); Options = options ?? throw new ArgumentNullException(nameof(options));
} }
/// <summary>
/// Gets the memory cache associated with the current store.
/// </summary>
protected IMemoryCache Cache { get; }
/// <summary> /// <summary>
/// Gets the database context associated with the current store. /// Gets the database context associated with the current store.
/// </summary> /// </summary>
@ -212,29 +200,17 @@ public class OpenIddictEntityFrameworkCoreScopeStore<
{ {
ArgumentException.ThrowIfNullOrEmpty(resource); ArgumentException.ThrowIfNullOrEmpty(resource);
// To optimize the efficiency of the query a bit, only scopes whose stringified
// Resources column contains the specified resource are returned. Once the scopes
// are retrieved, a second pass is made to ensure only valid elements are returned.
// Implementers that use this method in a hot path may want to override this method
// to use SQL Server 2016 functions like JSON_VALUE to make the query more efficient.
return ExecuteAsync(cancellationToken); return ExecuteAsync(cancellationToken);
async IAsyncEnumerable<TScope> ExecuteAsync([EnumeratorCancellation] CancellationToken cancellationToken) async IAsyncEnumerable<TScope> ExecuteAsync([EnumeratorCancellation] CancellationToken cancellationToken)
{ {
var context = await Context.GetDbContextAsync(cancellationToken); var context = await Context.GetDbContextAsync(cancellationToken);
var scopes = (from scope in context.Set<TScope>().AsTracking() await foreach (var scope in (from scope in context.Set<TScope>().AsTracking()
where scope.Resources!.Contains(resource) where scope.Resources!.Contains(resource)
select scope).AsAsyncEnumerable().WithCancellation(cancellationToken); select scope).AsAsyncEnumerable().WithCancellation(cancellationToken))
await foreach (var scope in scopes)
{ {
var resources = await GetResourcesAsync(scope, cancellationToken); yield return scope;
if (resources.Contains(resource, StringComparer.Ordinal))
{
yield return scope;
}
} }
} }
} }
@ -264,37 +240,9 @@ public class OpenIddictEntityFrameworkCoreScopeStore<
{ {
ArgumentNullException.ThrowIfNull(scope); ArgumentNullException.ThrowIfNull(scope);
if (string.IsNullOrEmpty(scope.Descriptions)) return new(scope.Descriptions is { Count: > 0 } descriptions
{ ? descriptions.ToImmutableDictionary(static pair => CultureInfo.GetCultureInfo(pair.Key), static pair => pair.Value)
return new(ImmutableDictionary.Create<CultureInfo, string>()); : ImmutableDictionary.Create<CultureInfo, string>());
}
// Note: parsing the stringified descriptions is an expensive operation.
// To mitigate that, the resulting object is stored in the memory cache.
var key = string.Concat("42891062-8f69-43ba-9111-db7e8ded2553", "\x1e", scope.Descriptions);
var descriptions = Cache.GetOrCreate(key, entry =>
{
entry.SetPriority(CacheItemPriority.High)
.SetSlidingExpiration(TimeSpan.FromMinutes(1));
using var document = JsonDocument.Parse(scope.Descriptions);
var builder = ImmutableDictionary.CreateBuilder<CultureInfo, string>();
foreach (var property in document.RootElement.EnumerateObject())
{
var value = property.Value.GetString();
if (string.IsNullOrEmpty(value))
{
continue;
}
builder[CultureInfo.GetCultureInfo(property.Name)] = value;
}
return builder.ToImmutable();
})!;
return new(descriptions);
} }
/// <inheritdoc/> /// <inheritdoc/>
@ -310,37 +258,9 @@ public class OpenIddictEntityFrameworkCoreScopeStore<
{ {
ArgumentNullException.ThrowIfNull(scope); ArgumentNullException.ThrowIfNull(scope);
if (string.IsNullOrEmpty(scope.DisplayNames)) return new(scope.DisplayNames is { Count: > 0 } names
{ ? names.ToImmutableDictionary(static pair => CultureInfo.GetCultureInfo(pair.Key), static pair => pair.Value)
return new(ImmutableDictionary.Create<CultureInfo, string>()); : []);
}
// Note: parsing the stringified display names is an expensive operation.
// To mitigate that, the resulting object is stored in the memory cache.
var key = string.Concat("e17d437b-bdd2-43f3-974e-46d524f4bae1", "\x1e", scope.DisplayNames);
var names = Cache.GetOrCreate(key, entry =>
{
entry.SetPriority(CacheItemPriority.High)
.SetSlidingExpiration(TimeSpan.FromMinutes(1));
using var document = JsonDocument.Parse(scope.DisplayNames);
var builder = ImmutableDictionary.CreateBuilder<CultureInfo, string>();
foreach (var property in document.RootElement.EnumerateObject())
{
var value = property.Value.GetString();
if (string.IsNullOrEmpty(value))
{
continue;
}
builder[CultureInfo.GetCultureInfo(property.Name)] = value;
}
return builder.ToImmutable();
})!;
return new(names);
} }
/// <inheritdoc/> /// <inheritdoc/>
@ -364,31 +284,7 @@ public class OpenIddictEntityFrameworkCoreScopeStore<
{ {
ArgumentNullException.ThrowIfNull(scope); ArgumentNullException.ThrowIfNull(scope);
if (string.IsNullOrEmpty(scope.Properties)) return new(scope.Properties is { Count: > 0 } properties ? [.. properties] : []);
{
return new(ImmutableDictionary.Create<string, JsonElement>());
}
// Note: parsing the stringified properties is an expensive operation.
// To mitigate that, the resulting object is stored in the memory cache.
var key = string.Concat("78d8dfdd-3870-442e-b62e-dc9bf6eaeff7", "\x1e", scope.Properties);
var properties = Cache.GetOrCreate(key, entry =>
{
entry.SetPriority(CacheItemPriority.High)
.SetSlidingExpiration(TimeSpan.FromMinutes(1));
using var document = JsonDocument.Parse(scope.Properties);
var builder = ImmutableDictionary.CreateBuilder<string, JsonElement>();
foreach (var property in document.RootElement.EnumerateObject())
{
builder[property.Name] = property.Value.Clone();
}
return builder.ToImmutable();
})!;
return new(properties);
} }
/// <inheritdoc/> /// <inheritdoc/>
@ -396,37 +292,7 @@ public class OpenIddictEntityFrameworkCoreScopeStore<
{ {
ArgumentNullException.ThrowIfNull(scope); ArgumentNullException.ThrowIfNull(scope);
if (string.IsNullOrEmpty(scope.Resources)) return new(scope.Resources is { Length: > 0 } resources ? [.. resources] : []);
{
return new([]);
}
// Note: parsing the stringified resources is an expensive operation.
// To mitigate that, the resulting array is stored in the memory cache.
var key = string.Concat("b6148250-aede-4fb9-a621-07c9bcf238c3", "\x1e", scope.Resources);
var resources = Cache.GetOrCreate(key, entry =>
{
entry.SetPriority(CacheItemPriority.High)
.SetSlidingExpiration(TimeSpan.FromMinutes(1));
using var document = JsonDocument.Parse(scope.Resources);
var builder = ImmutableArray.CreateBuilder<string>(document.RootElement.GetArrayLength());
foreach (var element in document.RootElement.EnumerateArray())
{
var value = element.GetString();
if (string.IsNullOrEmpty(value))
{
continue;
}
builder.Add(value);
}
return builder.ToImmutable();
});
return new(resources);
} }
/// <inheritdoc/> /// <inheritdoc/>
@ -504,32 +370,9 @@ public class OpenIddictEntityFrameworkCoreScopeStore<
{ {
ArgumentNullException.ThrowIfNull(scope); ArgumentNullException.ThrowIfNull(scope);
if (descriptions is not { Count: > 0 }) scope.Descriptions = descriptions is { Count: > 0 }
{ ? descriptions.ToImmutableDictionary(static pair => pair.Key.Name, static pair => pair.Value)
scope.Descriptions = null; : null;
return ValueTask.CompletedTask;
}
using var stream = new MemoryStream();
using var writer = new Utf8JsonWriter(stream, new JsonWriterOptions
{
Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping,
Indented = false
});
writer.WriteStartObject();
foreach (var description in descriptions)
{
writer.WritePropertyName(description.Key.Name);
writer.WriteStringValue(description.Value);
}
writer.WriteEndObject();
writer.Flush();
scope.Descriptions = Encoding.UTF8.GetString(stream.ToArray());
return ValueTask.CompletedTask; return ValueTask.CompletedTask;
} }
@ -550,32 +393,9 @@ public class OpenIddictEntityFrameworkCoreScopeStore<
{ {
ArgumentNullException.ThrowIfNull(scope); ArgumentNullException.ThrowIfNull(scope);
if (names is not { Count: > 0 }) scope.DisplayNames = names is { Count: > 0 }
{ ? names.ToImmutableDictionary(static pair => pair.Key.Name, static pair => pair.Value)
scope.DisplayNames = null; : null;
return ValueTask.CompletedTask;
}
using var stream = new MemoryStream();
using var writer = new Utf8JsonWriter(stream, new JsonWriterOptions
{
Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping,
Indented = false
});
writer.WriteStartObject();
foreach (var name in names)
{
writer.WritePropertyName(name.Key.Name);
writer.WriteStringValue(name.Value);
}
writer.WriteEndObject();
writer.Flush();
scope.DisplayNames = Encoding.UTF8.GetString(stream.ToArray());
return ValueTask.CompletedTask; return ValueTask.CompletedTask;
} }
@ -596,32 +416,7 @@ public class OpenIddictEntityFrameworkCoreScopeStore<
{ {
ArgumentNullException.ThrowIfNull(scope); ArgumentNullException.ThrowIfNull(scope);
if (properties is not { Count: > 0 }) scope.Properties = properties;
{
scope.Properties = null;
return ValueTask.CompletedTask;
}
using var stream = new MemoryStream();
using var writer = new Utf8JsonWriter(stream, new JsonWriterOptions
{
Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping,
Indented = false
});
writer.WriteStartObject();
foreach (var property in properties)
{
writer.WritePropertyName(property.Key);
property.Value.WriteTo(writer);
}
writer.WriteEndObject();
writer.Flush();
scope.Properties = Encoding.UTF8.GetString(stream.ToArray());
return ValueTask.CompletedTask; return ValueTask.CompletedTask;
} }
@ -638,24 +433,7 @@ public class OpenIddictEntityFrameworkCoreScopeStore<
return ValueTask.CompletedTask; return ValueTask.CompletedTask;
} }
using var stream = new MemoryStream(); scope.Resources = resources.ToArray();
using var writer = new Utf8JsonWriter(stream, new JsonWriterOptions
{
Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping,
Indented = false
});
writer.WriteStartArray();
foreach (var resource in resources)
{
writer.WriteStringValue(resource);
}
writer.WriteEndArray();
writer.Flush();
scope.Resources = Encoding.UTF8.GetString(stream.ToArray());
return ValueTask.CompletedTask; return ValueTask.CompletedTask;
} }

90
src/OpenIddict.EntityFrameworkCore/Stores/OpenIddictEntityFrameworkCoreTokenStore.cs

@ -9,10 +9,7 @@ using System.ComponentModel;
using System.Data; using System.Data;
using System.Diagnostics.CodeAnalysis; using System.Diagnostics.CodeAnalysis;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using System.Text;
using System.Text.Encodings.Web;
using System.Text.Json; using System.Text.Json;
using Microsoft.Extensions.Caching.Memory;
using Microsoft.Extensions.Options; using Microsoft.Extensions.Options;
using OpenIddict.EntityFrameworkCore.Models; using OpenIddict.EntityFrameworkCore.Models;
using static OpenIddict.Abstractions.OpenIddictExceptions; using static OpenIddict.Abstractions.OpenIddictExceptions;
@ -28,10 +25,9 @@ public class OpenIddictEntityFrameworkCoreTokenStore :
OpenIddictEntityFrameworkCoreAuthorization, string> OpenIddictEntityFrameworkCoreAuthorization, string>
{ {
public OpenIddictEntityFrameworkCoreTokenStore( public OpenIddictEntityFrameworkCoreTokenStore(
IMemoryCache cache,
IOpenIddictEntityFrameworkCoreContext context, IOpenIddictEntityFrameworkCoreContext context,
IOptionsMonitor<OpenIddictEntityFrameworkCoreOptions> options) IOptionsMonitor<OpenIddictEntityFrameworkCoreOptions> options)
: base(cache, context, options) : base(context, options)
{ {
} }
} }
@ -48,10 +44,9 @@ public class OpenIddictEntityFrameworkCoreTokenStore<
where TKey : notnull, IEquatable<TKey> where TKey : notnull, IEquatable<TKey>
{ {
public OpenIddictEntityFrameworkCoreTokenStore( public OpenIddictEntityFrameworkCoreTokenStore(
IMemoryCache cache,
IOpenIddictEntityFrameworkCoreContext context, IOpenIddictEntityFrameworkCoreContext context,
IOptionsMonitor<OpenIddictEntityFrameworkCoreOptions> options) IOptionsMonitor<OpenIddictEntityFrameworkCoreOptions> options)
: base(cache, context, options) : base(context, options)
{ {
} }
} }
@ -74,20 +69,13 @@ public class OpenIddictEntityFrameworkCoreTokenStore<
where TKey : notnull, IEquatable<TKey> where TKey : notnull, IEquatable<TKey>
{ {
public OpenIddictEntityFrameworkCoreTokenStore( public OpenIddictEntityFrameworkCoreTokenStore(
IMemoryCache cache,
IOpenIddictEntityFrameworkCoreContext context, IOpenIddictEntityFrameworkCoreContext context,
IOptionsMonitor<OpenIddictEntityFrameworkCoreOptions> options) IOptionsMonitor<OpenIddictEntityFrameworkCoreOptions> options)
{ {
Cache = cache ?? throw new ArgumentNullException(nameof(cache));
Context = context ?? throw new ArgumentNullException(nameof(context)); Context = context ?? throw new ArgumentNullException(nameof(context));
Options = options ?? throw new ArgumentNullException(nameof(options)); Options = options ?? throw new ArgumentNullException(nameof(options));
} }
/// <summary>
/// Gets the memory cache associated with the current store.
/// </summary>
protected IMemoryCache Cache { get; }
/// <summary> /// <summary>
/// Gets the database context associated with the current store. /// Gets the database context associated with the current store.
/// </summary> /// </summary>
@ -400,12 +388,7 @@ public class OpenIddictEntityFrameworkCoreTokenStore<
{ {
ArgumentNullException.ThrowIfNull(token); ArgumentNullException.ThrowIfNull(token);
if (token.CreationDate is null) return new(token.CreationDate is DateTime date ? DateTime.SpecifyKind(date, DateTimeKind.Utc) : null);
{
return new(result: null);
}
return new(DateTime.SpecifyKind(token.CreationDate.Value, DateTimeKind.Utc));
} }
/// <inheritdoc/> /// <inheritdoc/>
@ -413,12 +396,7 @@ public class OpenIddictEntityFrameworkCoreTokenStore<
{ {
ArgumentNullException.ThrowIfNull(token); ArgumentNullException.ThrowIfNull(token);
if (token.ExpirationDate is null) return new(token.ExpirationDate is DateTime date ? DateTime.SpecifyKind(date, DateTimeKind.Utc) : null);
{
return new(result: null);
}
return new(DateTime.SpecifyKind(token.ExpirationDate.Value, DateTimeKind.Utc));
} }
/// <inheritdoc/> /// <inheritdoc/>
@ -442,31 +420,7 @@ public class OpenIddictEntityFrameworkCoreTokenStore<
{ {
ArgumentNullException.ThrowIfNull(token); ArgumentNullException.ThrowIfNull(token);
if (string.IsNullOrEmpty(token.Properties)) return new(token.Properties is { Count: > 0 } properties ? [.. properties] : []);
{
return new(ImmutableDictionary.Create<string, JsonElement>());
}
// Note: parsing the stringified properties is an expensive operation.
// To mitigate that, the resulting object is stored in the memory cache.
var key = string.Concat("d0509397-1bbf-40e7-97e1-5e6d7bc2536c", "\x1e", token.Properties);
var properties = Cache.GetOrCreate(key, entry =>
{
entry.SetPriority(CacheItemPriority.High)
.SetSlidingExpiration(TimeSpan.FromMinutes(1));
using var document = JsonDocument.Parse(token.Properties);
var builder = ImmutableDictionary.CreateBuilder<string, JsonElement>();
foreach (var property in document.RootElement.EnumerateObject())
{
builder[property.Name] = property.Value.Clone();
}
return builder.ToImmutable();
})!;
return new(properties);
} }
/// <inheritdoc/> /// <inheritdoc/>
@ -474,12 +428,7 @@ public class OpenIddictEntityFrameworkCoreTokenStore<
{ {
ArgumentNullException.ThrowIfNull(token); ArgumentNullException.ThrowIfNull(token);
if (token.RedemptionDate is null) return new(token.RedemptionDate is DateTime date ? DateTime.SpecifyKind(date, DateTimeKind.Utc) : null);
{
return new(result: null);
}
return new(DateTime.SpecifyKind(token.RedemptionDate.Value, DateTimeKind.Utc));
} }
/// <inheritdoc/> /// <inheritdoc/>
@ -1070,32 +1019,7 @@ public class OpenIddictEntityFrameworkCoreTokenStore<
{ {
ArgumentNullException.ThrowIfNull(token); ArgumentNullException.ThrowIfNull(token);
if (properties is not { Count: > 0 }) token.Properties = properties;
{
token.Properties = null;
return ValueTask.CompletedTask;
}
using var stream = new MemoryStream();
using var writer = new Utf8JsonWriter(stream, new JsonWriterOptions
{
Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping,
Indented = false
});
writer.WriteStartObject();
foreach (var property in properties)
{
writer.WritePropertyName(property.Key);
property.Value.WriteTo(writer);
}
writer.WriteEndObject();
writer.Flush();
token.Properties = Encoding.UTF8.GetString(stream.ToArray());
return ValueTask.CompletedTask; return ValueTask.CompletedTask;
} }

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

@ -16,105 +16,109 @@ namespace OpenIddict.MongoDb.Models;
public class OpenIddictMongoDbApplication public class OpenIddictMongoDbApplication
{ {
/// <summary> /// <summary>
/// Gets or sets the application type associated with the current application. /// Gets or sets the application type of the application.
/// </summary> /// </summary>
[BsonElement("application_type"), BsonIgnoreIfNull] [BsonElement("application_type"), BsonIgnoreIfNull]
public virtual string? ApplicationType { get; set; } public virtual string? ApplicationType { get; set; }
/// <summary> /// <summary>
/// Gets or sets the client identifier associated with the current application. /// Gets or sets the client identifier of the application.
/// </summary> /// </summary>
[BsonElement("client_id"), BsonIgnoreIfNull] [BsonElement("client_id"), BsonIgnoreIfNull]
public virtual string? ClientId { get; set; } public virtual string? ClientId { get; set; }
/// <summary> /// <summary>
/// Gets or sets the client secret associated with the current application. /// Gets or sets the client secret of the application.
/// Note: depending on the application manager used to create this instance,
/// this property may be hashed or encrypted for security reasons.
/// </summary> /// </summary>
/// <remarks> /// <remarks>
/// <para>
/// Note: depending on the application manager used to create this instance,
/// this property may be hashed or encrypted for security reasons.
/// </para>
/// <para>
/// Note: client authentication based on shared secrets is not recommended and should /// Note: client authentication based on shared secrets is not recommended and should
/// only be used for backward compatibility with legacy applications that only support /// only be used for backward compatibility with legacy applications that only support
/// client secrets. When possible, consider using public/private key pairs or TLS client /// client secrets. When possible, consider using public/private key pairs or TLS client
/// certificates instead, as these client authentication methods are significantly safer. /// certificates instead, as these client authentication methods are significantly safer.
/// </para>
/// </remarks> /// </remarks>
[BsonElement("client_secret"), BsonIgnoreIfNull] [BsonElement("client_secret"), BsonIgnoreIfNull]
public virtual string? ClientSecret { get; set; } public virtual string? ClientSecret { get; set; }
/// <summary> /// <summary>
/// Gets or sets the client type associated with the current application. /// Gets or sets the client type of the application.
/// </summary> /// </summary>
[BsonElement("client_type"), BsonIgnoreIfNull] [BsonElement("client_type"), BsonIgnoreIfNull]
public virtual string? ClientType { get; set; } public virtual string? ClientType { get; set; }
/// <summary> /// <summary>
/// Gets or sets the concurrency token. /// Gets or sets the concurrency token of the application.
/// </summary> /// </summary>
[BsonElement("concurrency_token"), BsonIgnoreIfNull] [BsonElement("concurrency_token"), BsonIgnoreIfNull]
public virtual string? ConcurrencyToken { get; set; } = Guid.NewGuid().ToString(); public virtual string? ConcurrencyToken { get; set; } = Guid.NewGuid().ToString();
/// <summary> /// <summary>
/// Gets or sets the consent type associated with the current application. /// Gets or sets the consent type of the application.
/// </summary> /// </summary>
[BsonElement("consent_type"), BsonIgnoreIfNull] [BsonElement("consent_type"), BsonIgnoreIfNull]
public virtual string? ConsentType { get; set; } public virtual string? ConsentType { get; set; }
/// <summary> /// <summary>
/// Gets or sets the display name associated with the current application. /// Gets or sets the display name of the application.
/// </summary> /// </summary>
[BsonElement("display_name"), BsonIgnoreIfNull] [BsonElement("display_name"), BsonIgnoreIfNull]
public virtual string? DisplayName { get; set; } public virtual string? DisplayName { get; set; }
/// <summary> /// <summary>
/// Gets or sets the localized display names associated with the current application. /// Gets or sets the localized display names of the application.
/// </summary> /// </summary>
[BsonElement("display_names"), BsonIgnoreIfNull] [BsonElement("display_names"), BsonIgnoreIfNull]
public virtual ImmutableDictionary<string, string>? DisplayNames { get; set; } public virtual ImmutableDictionary<string, string>? DisplayNames { get; set; }
/// <summary> /// <summary>
/// Gets or sets the unique identifier associated with the current application. /// Gets or sets the unique identifier of the application.
/// </summary> /// </summary>
[BsonId, BsonRequired] [BsonId, BsonRequired]
public virtual ObjectId Id { get; set; } public virtual ObjectId Id { get; set; }
/// <summary> /// <summary>
/// Gets or sets the JSON Web Key Set associated with the application. /// Gets or sets the JSON Web Key Set of the application.
/// </summary> /// </summary>
[BsonElement("json_web_key_set"), BsonIgnoreIfNull] [BsonElement("json_web_key_set"), BsonIgnoreIfNull]
public virtual BsonDocument? JsonWebKeySet { get; set; } public virtual BsonDocument? JsonWebKeySet { get; set; }
/// <summary> /// <summary>
/// Gets or sets the permissions associated with the current application. /// Gets or sets the permissions of the application.
/// </summary> /// </summary>
[BsonElement("permissions"), BsonIgnoreIfNull] [BsonElement("permissions"), BsonIgnoreIfNull]
public virtual ImmutableArray<string>? Permissions { get; set; } public virtual ImmutableArray<string>? Permissions { get; set; }
/// <summary> /// <summary>
/// Gets or sets the post-logout redirect URIs associated with the current application. /// Gets or sets the post-logout redirect URIs of the application.
/// </summary> /// </summary>
[BsonElement("post_logout_redirect_uris"), BsonIgnoreIfNull] [BsonElement("post_logout_redirect_uris"), BsonIgnoreIfNull]
public virtual ImmutableArray<string>? PostLogoutRedirectUris { get; set; } public virtual ImmutableArray<string>? PostLogoutRedirectUris { get; set; }
/// <summary> /// <summary>
/// Gets or sets the additional properties associated with the current application. /// Gets or sets the additional properties of the application.
/// </summary> /// </summary>
[BsonElement("properties"), BsonIgnoreIfNull] [BsonElement("properties"), BsonIgnoreIfNull]
public virtual BsonDocument? Properties { get; set; } public virtual BsonDocument? Properties { get; set; }
/// <summary> /// <summary>
/// Gets or sets the redirect URIs associated with the current application. /// Gets or sets the redirect URIs of the application.
/// </summary> /// </summary>
[BsonElement("redirect_uris"), BsonIgnoreIfNull] [BsonElement("redirect_uris"), BsonIgnoreIfNull]
public virtual ImmutableArray<string>? RedirectUris { get; set; } public virtual ImmutableArray<string>? RedirectUris { get; set; }
/// <summary> /// <summary>
/// Gets or sets the requirements associated with the current application. /// Gets or sets the requirements of the application.
/// </summary> /// </summary>
[BsonElement("requirements"), BsonIgnoreIfNull] [BsonElement("requirements"), BsonIgnoreIfNull]
public virtual ImmutableArray<string>? Requirements { get; set; } public virtual ImmutableArray<string>? Requirements { get; set; }
/// <summary> /// <summary>
/// Gets or sets the settings associated with the current application. /// Gets or sets the settings of the application.
/// </summary> /// </summary>
[BsonElement("settings"), BsonIgnoreIfNull] [BsonElement("settings"), BsonIgnoreIfNull]
public virtual ImmutableDictionary<string, string>? Settings { get; set; } public virtual ImmutableDictionary<string, string>? Settings { get; set; }

18
src/OpenIddict.MongoDb.Models/OpenIddictMongoDbAuthorization.cs

@ -16,55 +16,55 @@ namespace OpenIddict.MongoDb.Models;
public class OpenIddictMongoDbAuthorization public class OpenIddictMongoDbAuthorization
{ {
/// <summary> /// <summary>
/// Gets or sets the identifier of the application associated with the current authorization. /// Gets or sets the identifier of the application associated with the authorization.
/// </summary> /// </summary>
[BsonElement("application_id"), BsonIgnoreIfDefault] [BsonElement("application_id"), BsonIgnoreIfDefault]
public virtual ObjectId ApplicationId { get; set; } public virtual ObjectId ApplicationId { get; set; }
/// <summary> /// <summary>
/// Gets or sets the concurrency token. /// Gets or sets the concurrency token of the authorization.
/// </summary> /// </summary>
[BsonElement("concurrency_token"), BsonIgnoreIfNull] [BsonElement("concurrency_token"), BsonIgnoreIfNull]
public virtual string? ConcurrencyToken { get; set; } = Guid.NewGuid().ToString(); public virtual string? ConcurrencyToken { get; set; } = Guid.NewGuid().ToString();
/// <summary> /// <summary>
/// Gets or sets the UTC creation date of the current authorization. /// Gets or sets the UTC creation date of the authorization.
/// </summary> /// </summary>
[BsonElement("creation_date"), BsonIgnoreIfNull] [BsonElement("creation_date"), BsonIgnoreIfNull]
public virtual DateTime? CreationDate { get; set; } public virtual DateTime? CreationDate { get; set; }
/// <summary> /// <summary>
/// Gets or sets the unique identifier associated with the current authorization. /// Gets or sets the unique identifier of the authorization.
/// </summary> /// </summary>
[BsonId, BsonRequired] [BsonId, BsonRequired]
public virtual ObjectId Id { get; set; } public virtual ObjectId Id { get; set; }
/// <summary> /// <summary>
/// Gets or sets the additional properties associated with the current authorization. /// Gets or sets the additional properties of the authorization.
/// </summary> /// </summary>
[BsonElement("properties"), BsonIgnoreIfNull] [BsonElement("properties"), BsonIgnoreIfNull]
public virtual BsonDocument? Properties { get; set; } public virtual BsonDocument? Properties { get; set; }
/// <summary> /// <summary>
/// Gets or sets the scopes associated with the current authorization. /// Gets or sets the scopes of the authorization.
/// </summary> /// </summary>
[BsonElement("scopes"), BsonIgnoreIfNull] [BsonElement("scopes"), BsonIgnoreIfNull]
public virtual ImmutableArray<string>? Scopes { get; set; } public virtual ImmutableArray<string>? Scopes { get; set; }
/// <summary> /// <summary>
/// Gets or sets the status of the current authorization. /// Gets or sets the status of the authorization.
/// </summary> /// </summary>
[BsonElement("status"), BsonIgnoreIfNull] [BsonElement("status"), BsonIgnoreIfNull]
public virtual string? Status { get; set; } public virtual string? Status { get; set; }
/// <summary> /// <summary>
/// Gets or sets the subject associated with the current authorization. /// Gets or sets the subject associated with the authorization.
/// </summary> /// </summary>
[BsonElement("subject"), BsonIgnoreIfNull] [BsonElement("subject"), BsonIgnoreIfNull]
public virtual string? Subject { get; set; } public virtual string? Subject { get; set; }
/// <summary> /// <summary>
/// Gets or sets the type of the current authorization. /// Gets or sets the type of the authorization.
/// </summary> /// </summary>
[BsonElement("type"), BsonIgnoreIfNull] [BsonElement("type"), BsonIgnoreIfNull]
public virtual string? Type { get; set; } public virtual string? Type { get; set; }

16
src/OpenIddict.MongoDb.Models/OpenIddictMongoDbResource.cs

@ -16,49 +16,49 @@ namespace OpenIddict.MongoDb.Models;
public class OpenIddictMongoDbResource public class OpenIddictMongoDbResource
{ {
/// <summary> /// <summary>
/// Gets or sets the concurrency token. /// Gets or sets the concurrency token of the resource.
/// </summary> /// </summary>
[BsonElement("concurrency_token"), BsonIgnoreIfNull] [BsonElement("concurrency_token"), BsonIgnoreIfNull]
public virtual string? ConcurrencyToken { get; set; } = Guid.NewGuid().ToString(); public virtual string? ConcurrencyToken { get; set; } = Guid.NewGuid().ToString();
/// <summary> /// <summary>
/// Gets or sets the public description associated with the current resource. /// Gets or sets the public description of the resource.
/// </summary> /// </summary>
[BsonElement("description"), BsonIgnoreIfNull] [BsonElement("description"), BsonIgnoreIfNull]
public virtual string? Description { get; set; } public virtual string? Description { get; set; }
/// <summary> /// <summary>
/// Gets or sets the localized public descriptions associated with the current resource. /// Gets or sets the localized public descriptions of the resource.
/// </summary> /// </summary>
[BsonElement("descriptions"), BsonIgnoreIfNull] [BsonElement("descriptions"), BsonIgnoreIfNull]
public virtual ImmutableDictionary<string, string>? Descriptions { get; set; } public virtual ImmutableDictionary<string, string>? Descriptions { get; set; }
/// <summary> /// <summary>
/// Gets or sets the display name associated with the current resource. /// Gets or sets the display name of the resource.
/// </summary> /// </summary>
[BsonElement("display_name"), BsonIgnoreIfNull] [BsonElement("display_name"), BsonIgnoreIfNull]
public virtual string? DisplayName { get; set; } public virtual string? DisplayName { get; set; }
/// <summary> /// <summary>
/// Gets or sets the localized display names associated with the current resource. /// Gets or sets the localized display names of the resource.
/// </summary> /// </summary>
[BsonElement("display_names"), BsonIgnoreIfNull] [BsonElement("display_names"), BsonIgnoreIfNull]
public virtual ImmutableDictionary<string, string>? DisplayNames { get; set; } public virtual ImmutableDictionary<string, string>? DisplayNames { get; set; }
/// <summary> /// <summary>
/// Gets or sets the unique identifier associated with the current resource. /// Gets or sets the unique identifier of the resource.
/// </summary> /// </summary>
[BsonId, BsonRequired] [BsonId, BsonRequired]
public virtual ObjectId Id { get; set; } public virtual ObjectId Id { get; set; }
/// <summary> /// <summary>
/// Gets or sets the unique name associated with the current resource. /// Gets or sets the unique name of the resource.
/// </summary> /// </summary>
[BsonElement("name"), BsonIgnoreIfNull] [BsonElement("name"), BsonIgnoreIfNull]
public virtual string? Name { get; set; } public virtual string? Name { get; set; }
/// <summary> /// <summary>
/// Gets or sets the additional properties associated with the current resource. /// Gets or sets the additional properties of the resource.
/// </summary> /// </summary>
[BsonElement("properties"), BsonIgnoreIfNull] [BsonElement("properties"), BsonIgnoreIfNull]
public virtual BsonDocument? Properties { get; set; } public virtual BsonDocument? Properties { get; set; }

18
src/OpenIddict.MongoDb.Models/OpenIddictMongoDbScope.cs

@ -16,55 +16,55 @@ namespace OpenIddict.MongoDb.Models;
public class OpenIddictMongoDbScope public class OpenIddictMongoDbScope
{ {
/// <summary> /// <summary>
/// Gets or sets the concurrency token. /// Gets or sets the concurrency token of the scope.
/// </summary> /// </summary>
[BsonElement("concurrency_token"), BsonIgnoreIfNull] [BsonElement("concurrency_token"), BsonIgnoreIfNull]
public virtual string? ConcurrencyToken { get; set; } = Guid.NewGuid().ToString(); public virtual string? ConcurrencyToken { get; set; } = Guid.NewGuid().ToString();
/// <summary> /// <summary>
/// Gets or sets the public description associated with the current scope. /// Gets or sets the public description of the scope.
/// </summary> /// </summary>
[BsonElement("description"), BsonIgnoreIfNull] [BsonElement("description"), BsonIgnoreIfNull]
public virtual string? Description { get; set; } public virtual string? Description { get; set; }
/// <summary> /// <summary>
/// Gets or sets the localized public descriptions associated with the current scope. /// Gets or sets the localized public descriptions of the scope.
/// </summary> /// </summary>
[BsonElement("descriptions"), BsonIgnoreIfNull] [BsonElement("descriptions"), BsonIgnoreIfNull]
public virtual ImmutableDictionary<string, string>? Descriptions { get; set; } public virtual ImmutableDictionary<string, string>? Descriptions { get; set; }
/// <summary> /// <summary>
/// Gets or sets the display name associated with the current scope. /// Gets or sets the display name of the scope.
/// </summary> /// </summary>
[BsonElement("display_name"), BsonIgnoreIfNull] [BsonElement("display_name"), BsonIgnoreIfNull]
public virtual string? DisplayName { get; set; } public virtual string? DisplayName { get; set; }
/// <summary> /// <summary>
/// Gets or sets the localized display names associated with the current scope. /// Gets or sets the localized display names of the scope.
/// </summary> /// </summary>
[BsonElement("display_names"), BsonIgnoreIfNull] [BsonElement("display_names"), BsonIgnoreIfNull]
public virtual ImmutableDictionary<string, string>? DisplayNames { get; set; } public virtual ImmutableDictionary<string, string>? DisplayNames { get; set; }
/// <summary> /// <summary>
/// Gets or sets the unique identifier associated with the current scope. /// Gets or sets the unique identifier of the scope.
/// </summary> /// </summary>
[BsonId, BsonRequired] [BsonId, BsonRequired]
public virtual ObjectId Id { get; set; } public virtual ObjectId Id { get; set; }
/// <summary> /// <summary>
/// Gets or sets the unique name associated with the current scope. /// Gets or sets the unique name of the scope.
/// </summary> /// </summary>
[BsonElement("name"), BsonIgnoreIfNull] [BsonElement("name"), BsonIgnoreIfNull]
public virtual string? Name { get; set; } public virtual string? Name { get; set; }
/// <summary> /// <summary>
/// Gets or sets the additional properties associated with the current scope. /// Gets or sets the additional properties of the scope.
/// </summary> /// </summary>
[BsonElement("properties"), BsonIgnoreIfNull] [BsonElement("properties"), BsonIgnoreIfNull]
public virtual BsonDocument? Properties { get; set; } public virtual BsonDocument? Properties { get; set; }
/// <summary> /// <summary>
/// Gets or sets the resources associated with the current scope. /// Gets or sets the resources associated with the scope.
/// </summary> /// </summary>
[BsonElement("resources"), BsonIgnoreIfNull] [BsonElement("resources"), BsonIgnoreIfNull]
public virtual ImmutableArray<string>? Resources { get; set; } public virtual ImmutableArray<string>? Resources { get; set; }

37
src/OpenIddict.MongoDb.Models/OpenIddictMongoDbToken.cs

@ -15,84 +15,87 @@ namespace OpenIddict.MongoDb.Models;
public class OpenIddictMongoDbToken public class OpenIddictMongoDbToken
{ {
/// <summary> /// <summary>
/// Gets or sets the identifier of the application associated with the current token. /// Gets or sets the identifier of the application associated with the token.
/// </summary> /// </summary>
[BsonElement("application_id"), BsonIgnoreIfDefault] [BsonElement("application_id"), BsonIgnoreIfDefault]
public virtual ObjectId ApplicationId { get; set; } public virtual ObjectId ApplicationId { get; set; }
/// <summary> /// <summary>
/// Gets or sets the identifier of the authorization associated with the current token. /// Gets or sets the identifier of the authorization associated with the token.
/// </summary> /// </summary>
[BsonElement("authorization_id"), BsonIgnoreIfDefault] [BsonElement("authorization_id"), BsonIgnoreIfDefault]
public virtual ObjectId AuthorizationId { get; set; } public virtual ObjectId AuthorizationId { get; set; }
/// <summary> /// <summary>
/// Gets or sets the concurrency token. /// Gets or sets the concurrency token of the token.
/// </summary> /// </summary>
[BsonElement("concurrency_token"), BsonIgnoreIfNull] [BsonElement("concurrency_token"), BsonIgnoreIfNull]
public virtual string? ConcurrencyToken { get; set; } = Guid.NewGuid().ToString(); public virtual string? ConcurrencyToken { get; set; } = Guid.NewGuid().ToString();
/// <summary> /// <summary>
/// Gets or sets the UTC creation date of the current token. /// Gets or sets the UTC creation date of the token.
/// </summary> /// </summary>
[BsonElement("creation_date"), BsonIgnoreIfNull] [BsonElement("creation_date"), BsonIgnoreIfNull]
public virtual DateTime? CreationDate { get; set; } public virtual DateTime? CreationDate { get; set; }
/// <summary> /// <summary>
/// Gets or sets the UTC expiration date of the current token. /// Gets or sets the UTC expiration date of the token.
/// </summary> /// </summary>
[BsonElement("expiration_date"), BsonIgnoreIfNull] [BsonElement("expiration_date"), BsonIgnoreIfNull]
public virtual DateTime? ExpirationDate { get; set; } public virtual DateTime? ExpirationDate { get; set; }
/// <summary> /// <summary>
/// Gets or sets the unique identifier associated with the current token. /// Gets or sets the unique identifier of the token.
/// </summary> /// </summary>
[BsonId, BsonRequired] [BsonId, BsonRequired]
public virtual ObjectId Id { get; set; } public virtual ObjectId Id { get; set; }
/// <summary> /// <summary>
/// Gets or sets the payload of the current token, if applicable. /// Gets or sets the payload of the token.
/// Note: this property is only used for reference tokens
/// and may be encrypted for security reasons.
/// </summary> /// </summary>
/// <remarks>
/// Note: this property is only used for reference tokens
/// and may be hashed or encrypted for security reasons.
/// </remarks>
[BsonElement("payload"), BsonIgnoreIfNull] [BsonElement("payload"), BsonIgnoreIfNull]
public virtual string? Payload { get; set; } public virtual string? Payload { get; set; }
/// <summary> /// <summary>
/// Gets or sets the additional properties associated with the current token. /// Gets or sets the additional properties associated with the token.
/// </summary> /// </summary>
[BsonElement("properties"), BsonIgnoreIfNull] [BsonElement("properties"), BsonIgnoreIfNull]
public virtual BsonDocument? Properties { get; set; } public virtual BsonDocument? Properties { get; set; }
/// <summary> /// <summary>
/// Gets or sets the UTC redemption date of the current token. /// Gets or sets the UTC redemption date of the token.
/// </summary> /// </summary>
[BsonElement("redemption_date"), BsonIgnoreIfNull] [BsonElement("redemption_date"), BsonIgnoreIfNull]
public virtual DateTime? RedemptionDate { get; set; } public virtual DateTime? RedemptionDate { get; set; }
/// <summary> /// <summary>
/// Gets or sets the reference identifier associated /// Gets or sets the reference identifier associated with the token.
/// with the current token, if applicable. /// </summary>
/// <remarks>
/// Note: this property is only used for reference tokens /// Note: this property is only used for reference tokens
/// and may be hashed or encrypted for security reasons. /// and may be hashed or encrypted for security reasons.
/// </summary> /// </remarks>
[BsonElement("reference_id"), BsonIgnoreIfNull] [BsonElement("reference_id"), BsonIgnoreIfNull]
public virtual string? ReferenceId { get; set; } public virtual string? ReferenceId { get; set; }
/// <summary> /// <summary>
/// Gets or sets the status of the current token. /// Gets or sets the status of the token.
/// </summary> /// </summary>
[BsonElement("status"), BsonIgnoreIfNull] [BsonElement("status"), BsonIgnoreIfNull]
public virtual string? Status { get; set; } public virtual string? Status { get; set; }
/// <summary> /// <summary>
/// Gets or sets the subject associated with the current token. /// Gets or sets the subject associated with the token.
/// </summary> /// </summary>
[BsonElement("subject"), BsonIgnoreIfNull] [BsonElement("subject"), BsonIgnoreIfNull]
public virtual string? Subject { get; set; } public virtual string? Subject { get; set; }
/// <summary> /// <summary>
/// Gets or sets the type of the current token. /// Gets or sets the type of the token.
/// </summary> /// </summary>
[BsonElement("type"), BsonIgnoreIfNull] [BsonElement("type"), BsonIgnoreIfNull]
public virtual string? Type { get; set; } public virtual string? Type { get; set; }

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

@ -263,12 +263,7 @@ public class OpenIddictMongoDbApplicationStore<
{ {
ArgumentNullException.ThrowIfNull(application); ArgumentNullException.ThrowIfNull(application);
if (application.JsonWebKeySet is null) return new(application.JsonWebKeySet is BsonDocument set ? JsonWebKeySet.Create(set.ToJson()) : null);
{
return new(result: null);
}
return new(JsonWebKeySet.Create(application.JsonWebKeySet.ToJson()));
} }
/// <inheritdoc/> /// <inheritdoc/>

14
src/OpenIddict.MongoDb/Stores/OpenIddictMongoDbAuthorizationStore.cs

@ -211,12 +211,7 @@ public class OpenIddictMongoDbAuthorizationStore<
{ {
ArgumentNullException.ThrowIfNull(authorization); ArgumentNullException.ThrowIfNull(authorization);
if (authorization.ApplicationId == ObjectId.Empty) return new(authorization.ApplicationId != ObjectId.Empty ? authorization.ApplicationId.ToString() : null);
{
return new(result: null);
}
return new(authorization.ApplicationId.ToString());
} }
/// <inheritdoc/> /// <inheritdoc/>
@ -237,12 +232,7 @@ public class OpenIddictMongoDbAuthorizationStore<
{ {
ArgumentNullException.ThrowIfNull(authorization); ArgumentNullException.ThrowIfNull(authorization);
if (authorization.CreationDate is null) return new(authorization.CreationDate is DateTime date ? DateTime.SpecifyKind(date, DateTimeKind.Utc) : null);
{
return new(result: null);
}
return new(DateTime.SpecifyKind(authorization.CreationDate.Value, DateTimeKind.Utc));
} }
/// <inheritdoc/> /// <inheritdoc/>

35
src/OpenIddict.MongoDb/Stores/OpenIddictMongoDbTokenStore.cs

@ -226,12 +226,7 @@ public class OpenIddictMongoDbTokenStore<
{ {
ArgumentNullException.ThrowIfNull(token); ArgumentNullException.ThrowIfNull(token);
if (token.ApplicationId == ObjectId.Empty) return new(token.ApplicationId != ObjectId.Empty ? token.ApplicationId.ToString() : null);
{
return new(result: null);
}
return new(token.ApplicationId.ToString());
} }
/// <inheritdoc/> /// <inheritdoc/>
@ -252,12 +247,7 @@ public class OpenIddictMongoDbTokenStore<
{ {
ArgumentNullException.ThrowIfNull(token); ArgumentNullException.ThrowIfNull(token);
if (token.AuthorizationId == ObjectId.Empty) return new(token.AuthorizationId != ObjectId.Empty ? token.AuthorizationId.ToString() : null);
{
return new(result: null);
}
return new(token.AuthorizationId.ToString());
} }
/// <inheritdoc/> /// <inheritdoc/>
@ -265,12 +255,7 @@ public class OpenIddictMongoDbTokenStore<
{ {
ArgumentNullException.ThrowIfNull(token); ArgumentNullException.ThrowIfNull(token);
if (token.CreationDate is null) return new(token.CreationDate is DateTime date ? DateTime.SpecifyKind(date, DateTimeKind.Utc) : null);
{
return new(result: null);
}
return new(DateTime.SpecifyKind(token.CreationDate.Value, DateTimeKind.Utc));
} }
/// <inheritdoc/> /// <inheritdoc/>
@ -278,12 +263,7 @@ public class OpenIddictMongoDbTokenStore<
{ {
ArgumentNullException.ThrowIfNull(token); ArgumentNullException.ThrowIfNull(token);
if (token.ExpirationDate is null) return new(token.ExpirationDate is DateTime date ? DateTime.SpecifyKind(date, DateTimeKind.Utc) : null);
{
return new(result: null);
}
return new(DateTime.SpecifyKind(token.ExpirationDate.Value, DateTimeKind.Utc));
} }
/// <inheritdoc/> /// <inheritdoc/>
@ -328,12 +308,7 @@ public class OpenIddictMongoDbTokenStore<
{ {
ArgumentNullException.ThrowIfNull(token); ArgumentNullException.ThrowIfNull(token);
if (token.RedemptionDate is null) return new(token.RedemptionDate is DateTime date ? DateTime.SpecifyKind(date, DateTimeKind.Utc) : null);
{
return new(result: null);
}
return new(DateTime.SpecifyKind(token.RedemptionDate.Value, DateTimeKind.Utc));
} }
/// <inheritdoc/> /// <inheritdoc/>

Loading…
Cancel
Save