Browse Source

Revamp the authorizations/tokens pruning mechanism

pull/1072/head
Kévin Chalet 6 years ago
parent
commit
f84a10270e
  1. 1
      samples/Mvc.Server/Mvc.Server.csproj
  2. 5
      src/OpenIddict.Abstractions/Descriptors/OpenIddictAuthorizationDescriptor.cs
  3. 21
      src/OpenIddict.Abstractions/Managers/IOpenIddictAuthorizationManager.cs
  4. 6
      src/OpenIddict.Abstractions/Managers/IOpenIddictTokenManager.cs
  5. 16
      src/OpenIddict.Abstractions/OpenIddictBuilder.cs
  6. 4
      src/OpenIddict.Abstractions/Resources/OpenIddictResources.resx
  7. 2
      src/OpenIddict.Abstractions/Resources/xlf/OpenIddictResources.ar.xlf
  8. 2
      src/OpenIddict.Abstractions/Resources/xlf/OpenIddictResources.es.xlf
  9. 4
      src/OpenIddict.Abstractions/Resources/xlf/OpenIddictResources.tr.xlf
  10. 238
      src/OpenIddict.Abstractions/Resources/xlf/OpenIddictResources.zh-Hans.xlf
  11. 238
      src/OpenIddict.Abstractions/Resources/xlf/OpenIddictResources.zh-Hant.xlf
  12. 30
      src/OpenIddict.Abstractions/Stores/IOpenIddictAuthorizationStore.cs
  13. 6
      src/OpenIddict.Abstractions/Stores/IOpenIddictTokenStore.cs
  14. 43
      src/OpenIddict.Core/Managers/OpenIddictAuthorizationManager.cs
  15. 12
      src/OpenIddict.Core/Managers/OpenIddictTokenManager.cs
  16. 16
      src/OpenIddict.Core/OpenIddictCoreBuilder.cs
  17. 5
      src/OpenIddict.EntityFramework.Models/OpenIddictEntityFrameworkAuthorization.cs
  18. 4
      src/OpenIddict.EntityFramework.Models/OpenIddictEntityFrameworkToken.cs
  19. 16
      src/OpenIddict.EntityFramework/OpenIddictEntityFrameworkBuilder.cs
  20. 39
      src/OpenIddict.EntityFramework/Stores/OpenIddictEntityFrameworkAuthorizationStore.cs
  21. 22
      src/OpenIddict.EntityFramework/Stores/OpenIddictEntityFrameworkTokenStore.cs
  22. 5
      src/OpenIddict.EntityFrameworkCore.Models/OpenIddictEntityFrameworkCoreAuthorization.cs
  23. 4
      src/OpenIddict.EntityFrameworkCore.Models/OpenIddictEntityFrameworkCoreToken.cs
  24. 16
      src/OpenIddict.EntityFrameworkCore/OpenIddictEntityFrameworkCoreBuilder.cs
  25. 39
      src/OpenIddict.EntityFrameworkCore/Stores/OpenIddictEntityFrameworkCoreAuthorizationStore.cs
  26. 22
      src/OpenIddict.EntityFrameworkCore/Stores/OpenIddictEntityFrameworkCoreTokenStore.cs
  27. 5
      src/OpenIddict.MongoDb.Models/OpenIddictMongoDbAuthorization.cs
  28. 4
      src/OpenIddict.MongoDb.Models/OpenIddictMongoDbToken.cs
  29. 16
      src/OpenIddict.MongoDb/OpenIddictMongoDbBuilder.cs
  30. 42
      src/OpenIddict.MongoDb/Stores/OpenIddictMongoDbAuthorizationStore.cs
  31. 48
      src/OpenIddict.MongoDb/Stores/OpenIddictMongoDbTokenStore.cs
  32. 16
      src/OpenIddict.Server.AspNetCore/OpenIddictServerAspNetCoreBuilder.cs
  33. 16
      src/OpenIddict.Server.DataProtection/OpenIddictServerDataProtectionBuilder.cs
  34. 16
      src/OpenIddict.Server.Owin/OpenIddictServerOwinBuilder.cs
  35. 38
      src/OpenIddict.Server.Quartz/OpenIddictServerQuartzBuilder.cs
  36. 27
      src/OpenIddict.Server.Quartz/OpenIddictServerQuartzJob.cs
  37. 18
      src/OpenIddict.Server.Quartz/OpenIddictServerQuartzOptions.cs
  38. 16
      src/OpenIddict.Server/OpenIddictServerBuilder.cs
  39. 1
      src/OpenIddict.Server/OpenIddictServerHandlers.cs
  40. 16
      src/OpenIddict.Validation.AspNetCore/OpenIddictValidationAspNetCoreBuilder.cs
  41. 16
      src/OpenIddict.Validation.DataProtection/OpenIddictValidationDataProtectionBuilder.cs
  42. 16
      src/OpenIddict.Validation.Owin/OpenIddictValidationOwinBuilder.cs
  43. 16
      src/OpenIddict.Validation.ServerIntegration/OpenIddictValidationServerIntegrationBuilder.cs
  44. 16
      src/OpenIddict.Validation.SystemNetHttp/OpenIddictValidationSystemNetHttpBuilder.cs
  45. 16
      src/OpenIddict.Validation/OpenIddictValidationBuilder.cs
  46. 1
      test/OpenIddict.Server.IntegrationTests/OpenIddictServerIntegrationTests.cs
  47. 72
      test/OpenIddict.Server.Quartz.Tests/OpenIddictServerQuartzBuilderTests.cs
  48. 84
      test/OpenIddict.Server.Quartz.Tests/OpenIddictServerQuartzJobTests.cs

1
samples/Mvc.Server/Mvc.Server.csproj

@ -3,6 +3,7 @@
<PropertyGroup>
<TargetFramework>netcoreapp3.1</TargetFramework>
<IsShipping>false</IsShipping>
<SignAssembly>false</SignAssembly>
<TypeScriptEnabled>false</TypeScriptEnabled>
</PropertyGroup>

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

@ -14,6 +14,11 @@ namespace OpenIddict.Abstractions
/// </summary>
public string? ApplicationId { get; set; }
/// <summary>
/// Gets or sets the creation date associated with the authorization.
/// </summary>
public DateTimeOffset? CreationDate { get; set; }
/// <summary>
/// Gets or sets the optional principal associated with the authorization.
/// Note: this property is not stored by the default authorization stores.

21
src/OpenIddict.Abstractions/Managers/IOpenIddictAuthorizationManager.cs

@ -212,6 +212,17 @@ namespace OpenIddict.Abstractions
Func<IQueryable<object>, TState, IQueryable<TResult>> query,
TState state, CancellationToken cancellationToken = default);
/// <summary>
/// Retrieves the creation date associated with an authorization.
/// </summary>
/// <param name="authorization">The authorization.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="ValueTask{TResult}"/> that can be used to monitor the asynchronous operation,
/// whose result returns the creation date associated with the specified authorization.
/// </returns>
ValueTask<DateTimeOffset?> GetCreationDateAsync(object authorization, CancellationToken cancellationToken = default);
/// <summary>
/// Retrieves the unique identifier associated with an authorization.
/// </summary>
@ -350,13 +361,19 @@ namespace OpenIddict.Abstractions
ValueTask PopulateAsync(object authorization, OpenIddictAuthorizationDescriptor descriptor, CancellationToken cancellationToken = default);
/// <summary>
/// Removes the authorizations that are marked as invalid and the ad-hoc ones that have no valid/nonexpired token attached.
/// Removes the authorizations that are marked as invalid and the ad-hoc ones that have no token attached.
/// Only authorizations created before the specified <paramref name="threshold"/> are removed.
/// </summary>
/// <remarks>
/// To ensure ad-hoc authorizations that no longer have any valid/non-expired token
/// attached are correctly removed, the tokens should always be pruned first.
/// </remarks>
/// <param name="threshold">The date before which authorizations are not pruned.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="ValueTask"/> that can be used to monitor the asynchronous operation.
/// </returns>
ValueTask PruneAsync(CancellationToken cancellationToken = default);
ValueTask PruneAsync(DateTimeOffset threshold, CancellationToken cancellationToken = default);
/// <summary>
/// Sets the application identifier associated with an authorization.

6
src/OpenIddict.Abstractions/Managers/IOpenIddictTokenManager.cs

@ -375,13 +375,15 @@ namespace OpenIddict.Abstractions
ValueTask PopulateAsync(object token, OpenIddictTokenDescriptor descriptor, CancellationToken cancellationToken = default);
/// <summary>
/// Removes the tokens that are marked as expired or invalid.
/// Removes the tokens that are marked as invalid or whose attached authorization is no longer valid.
/// Only tokens created before the specified <paramref name="threshold"/> are removed.
/// </summary>
/// <param name="threshold">The date before which tokens are not pruned.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="ValueTask"/> that can be used to monitor the asynchronous operation.
/// </returns>
ValueTask PruneAsync(CancellationToken cancellationToken = default);
ValueTask PruneAsync(DateTimeOffset threshold, CancellationToken cancellationToken = default);
/// <summary>
/// Sets the application identifier associated with a token.

16
src/OpenIddict.Abstractions/OpenIddictBuilder.cs

@ -27,25 +27,15 @@ namespace Microsoft.Extensions.DependencyInjection
[EditorBrowsable(EditorBrowsableState.Never)]
public IServiceCollection Services { get; }
/// <summary>
/// Determines whether the specified object is equal to the current object.
/// </summary>
/// <param name="obj">The object to compare with the current object.</param>
/// <returns><c>true</c> if the specified object is equal to the current object; otherwise, false.</returns>
/// <inheritdoc/>
[EditorBrowsable(EditorBrowsableState.Never)]
public override bool Equals(object? obj) => base.Equals(obj);
/// <summary>
/// Serves as the default hash function.
/// </summary>
/// <returns>A hash code for the current object.</returns>
/// <inheritdoc/>
[EditorBrowsable(EditorBrowsableState.Never)]
public override int GetHashCode() => base.GetHashCode();
/// <summary>
/// Returns a string that represents the current object.
/// </summary>
/// <returns>A string that represents the current object.</returns>
/// <inheritdoc/>
[EditorBrowsable(EditorBrowsableState.Never)]
public override string? ToString() => base.ToString();
}

4
src/OpenIddict.Abstractions/Resources/OpenIddictResources.resx

@ -1377,6 +1377,10 @@ To register the OpenIddict core services, reference the 'OpenIddict.Core' packag
<value>The maximum refire count cannot be negative.</value>
<comment>{Locked}</comment>
</data>
<data name="ID1279" xml:space="preserve">
<value>The duration cannot be less than 10 minutes.</value>
<comment>{Locked}</comment>
</data>
<data name="ID3000" xml:space="preserve">
<value>The security token is missing.</value>
</data>

2
src/OpenIddict.Abstractions/Resources/xlf/OpenIddictResources.ar.xlf

@ -599,4 +599,4 @@
</trans-unit>
</body>
</file>
</xliff>
</xliff>

2
src/OpenIddict.Abstractions/Resources/xlf/OpenIddictResources.es.xlf

@ -599,4 +599,4 @@
</trans-unit>
</body>
</file>
</xliff>
</xliff>

4
src/OpenIddict.Abstractions/Resources/xlf/OpenIddictResources.tr.xlf

@ -519,7 +519,7 @@
</trans-unit>
<trans-unit id="ID3107">
<source>The '{0}' claim is malformed or isn't of the expected type.</source>
<target state="translated">'{0}' hakkı kusurlu veya beklenen tipte değil.</target>
<target state="translated">'{0}' hakkı kusurlu veya beklenen tipte değil.</target>
<note />
</trans-unit>
<trans-unit id="ID3108">
@ -599,4 +599,4 @@
</trans-unit>
</body>
</file>
</xliff>
</xliff>

238
src/OpenIddict.Abstractions/Resources/xlf/OpenIddictResources.zh-Hans.xlf

@ -5,597 +5,597 @@
<trans-unit id="ID3000">
<source>The security token is missing.</source>
<target state="translated">缺少安全令牌.</target>
<note/>
<note />
</trans-unit>
<trans-unit id="ID3001">
<source>The specified authorization code is invalid.</source>
<target state="translated">指定的授权码无效.</target>
<note/>
<note />
</trans-unit>
<trans-unit id="ID3002">
<source>The specified device code is invalid.</source>
<target state="translated">指定的设备码无效.</target>
<note/>
<note />
</trans-unit>
<trans-unit id="ID3003">
<source>The specified refresh token is invalid.</source>
<target state="translated">指定的刷新令牌无效.</target>
<note/>
<note />
</trans-unit>
<trans-unit id="ID3004">
<source>The specified token is invalid.</source>
<target state="translated">指定的令牌无效.</target>
<note/>
<note />
</trans-unit>
<trans-unit id="ID3005">
<source>The specified token is not an authorization code.</source>
<target state="translated">指定的令牌不是授权码.</target>
<note/>
<note />
</trans-unit>
<trans-unit id="ID3006">
<source>The specified token is not an device code.</source>
<target state="translated">指定的令牌不是设备码.</target>
<note/>
<note />
</trans-unit>
<trans-unit id="ID3007">
<source>The specified token is not a refresh token.</source>
<target state="translated">指定的令牌不是刷新令牌.</target>
<note/>
<note />
</trans-unit>
<trans-unit id="ID3008">
<source>The specified token is not an access token.</source>
<target state="translated">指定的令牌不是访问令牌.</target>
<note/>
<note />
</trans-unit>
<trans-unit id="ID3009">
<source>The specified identity token is invalid.</source>
<target state="translated">指定的身份令牌无效.</target>
<note/>
<note />
</trans-unit>
<trans-unit id="ID3010">
<source>The specified authorization code has already been redeemed.</source>
<target state="translated">指定的授权码已兑换.</target>
<note/>
<note />
</trans-unit>
<trans-unit id="ID3011">
<source>The specified device code has already been redeemed.</source>
<target state="translated">指定的设备码已兑换.</target>
<note/>
<note />
</trans-unit>
<trans-unit id="ID3012">
<source>The specified refresh token has already been redeemed.</source>
<target state="translated">指定的刷新令牌已兑换.</target>
<note/>
<note />
</trans-unit>
<trans-unit id="ID3013">
<source>The specified token has already been redeemed.</source>
<target state="translated">指定的令牌已兑换.</target>
<note/>
<note />
</trans-unit>
<trans-unit id="ID3014">
<source>The authorization has not been granted yet by the end user.</source>
<target state="translated">最终用户尚未授权.</target>
<note/>
<note />
</trans-unit>
<trans-unit id="ID3015">
<source>The authorization was denied by the end user.</source>
<target state="translated">最终用户拒绝授权.</target>
<note/>
<note />
</trans-unit>
<trans-unit id="ID3016">
<source>The specified authorization code is no longer valid.</source>
<target state="translated">指定的授权码不再有效.</target>
<note/>
<note />
</trans-unit>
<trans-unit id="ID3017">
<source>The specified device code is no longer valid.</source>
<target state="translated">指定的设备码不再有效.</target>
<note/>
<note />
</trans-unit>
<trans-unit id="ID3018">
<source>The specified refresh token is no longer valid.</source>
<target state="translated">指定的刷新令牌不再有效.</target>
<note/>
<note />
</trans-unit>
<trans-unit id="ID3019">
<source>The specified token is no longer valid.</source>
<target state="translated">指定的令牌不再有效.</target>
<note/>
<note />
</trans-unit>
<trans-unit id="ID3020">
<source>The authorization associated with the authorization code is no longer valid.</source>
<target state="translated">与授权码关联的授权不再有效.</target>
<note/>
<note />
</trans-unit>
<trans-unit id="ID3021">
<source>The authorization associated with the device code is no longer valid.</source>
<target state="translated">与设备码关联的授权不再有效.</target>
<note/>
<note />
</trans-unit>
<trans-unit id="ID3022">
<source>The authorization associated with the refresh token is no longer valid.</source>
<target state="translated">与刷新令牌关联的授权不再有效.</target>
<note/>
<note />
</trans-unit>
<trans-unit id="ID3023">
<source>The authorization associated with the token is no longer valid.</source>
<target state="translated">与令牌关联的授权不再有效.</target>
<note/>
<note />
</trans-unit>
<trans-unit id="ID3024">
<source>The token request was rejected by the authentication server.</source>
<target state="translated">身份验证服务器拒绝了令牌请求.</target>
<note/>
<note />
</trans-unit>
<trans-unit id="ID3025">
<source>The user information access demand was rejected by the authentication server.</source>
<target state="translated">身份验证服务器拒绝了用户信息访问请求.</target>
<note/>
<note />
</trans-unit>
<trans-unit id="ID3026">
<source>The specified user code is no longer valid.</source>
<target state="translated">指定的用户码不再有效.</target>
<note/>
<note />
</trans-unit>
<trans-unit id="ID3028">
<source>The '{0}' parameter is not supported.</source>
<target state="translated">不支持参数 ‘{0}’.</target>
<note/>
<note />
</trans-unit>
<trans-unit id="ID3029">
<source>The mandatory '{0}' parameter is missing.</source>
<target state="translated">缺少必需参数 ‘{0}’.</target>
<note/>
<note />
</trans-unit>
<trans-unit id="ID3030">
<source>The '{0}' parameter must be a valid absolute URL.</source>
<target state="translated">参数 ’{0}’ 必须是有效的绝对路径网址.</target>
<note/>
<note />
</trans-unit>
<trans-unit id="ID3031">
<source>The '{0}' parameter must not include a fragment.</source>
<target state="translated">参数 ‘{0}’ 不能包含 fragment.</target>
<note/>
<note />
</trans-unit>
<trans-unit id="ID3032">
<source>The specified '{0}' is not supported.</source>
<target state="translated">不支持指定的 ‘{0}’.</target>
<note/>
<note />
</trans-unit>
<trans-unit id="ID3033">
<source>The specified '{0}'/'{1}' combination is invalid.</source>
<target state="translated">指定的 ‘{0}’ / ‘{1}’ 组合无效.</target>
<note/>
<note />
</trans-unit>
<trans-unit id="ID3034">
<source>The mandatory '{0}' scope is missing.</source>
<target state="translated">缺少必需 Scope ‘{0}’.</target>
<note/>
<note />
</trans-unit>
<trans-unit id="ID3035">
<source>The '{0}' scope is not allowed.</source>
<target state="translated">不允许 Scope ‘{0}’.</target>
<note/>
<note />
</trans-unit>
<trans-unit id="ID3036">
<source>The client identifier cannot be null or empty.</source>
<target state="translated">客户端 ID 不能为空.</target>
<note/>
<note />
</trans-unit>
<trans-unit id="ID3037">
<source>The '{0}' parameter cannot be used without '{1}'.</source>
<target state="translated">参数 ‘{0}’ 要求 ‘{1}’.</target>
<note/>
<note />
</trans-unit>
<trans-unit id="ID3038">
<source>The status cannot be null or empty.</source>
<target state="translated">状态不能为空.</target>
<note/>
<note />
</trans-unit>
<trans-unit id="ID3039">
<source>Scopes cannot be null or empty.</source>
<target state="translated">Scope 不能为空.</target>
<note/>
<note />
</trans-unit>
<trans-unit id="ID3040">
<source>The '{0}' and '{1}' parameters can only be used with a response type containing '{2}'.</source>
<target state="translated">参数 ‘{0}’ 和 ‘{1}’ 只能在响应类型包含 ‘{2}’ 时使用.</target>
<note/>
<note />
</trans-unit>
<trans-unit id="ID3041">
<source>The specified '{0}' is not allowed when using PKCE.</source>
<target state="translated">使用 PKCE 时不允许指定的 ‘{0}’.</target>
<note/>
<note />
</trans-unit>
<trans-unit id="ID3042">
<source>Scopes cannot contain spaces.</source>
<target state="translated">Scope 不能包含空格.</target>
<note/>
<note />
</trans-unit>
<trans-unit id="ID3043">
<source>The specified '{0}' is not valid for this client application.</source>
<target state="translated">指定的 ‘{0}’ 对此客户端无效.</target>
<note/>
<note />
</trans-unit>
<trans-unit id="ID3044">
<source>The scope name cannot be null or empty.</source>
<target state="translated">Scope 不能为空.</target>
<note/>
<note />
</trans-unit>
<trans-unit id="ID3045">
<source>The scope name cannot contain spaces.</source>
<target state="translated">Scope 不能包含空格.</target>
<note/>
<note />
</trans-unit>
<trans-unit id="ID3046">
<source>This client application is not allowed to use the authorization endpoint.</source>
<target state="translated">不允许此客户端使用授权端点.</target>
<note/>
<note />
</trans-unit>
<trans-unit id="ID3047">
<source>The client application is not allowed to use the authorization code flow.</source>
<target state="translated">不允许此客户端使用授权代码流程.</target>
<note/>
<note />
</trans-unit>
<trans-unit id="ID3048">
<source>The client application is not allowed to use the implicit flow.</source>
<target state="translated">不允许此客户端使用隐式流程.</target>
<note/>
<note />
</trans-unit>
<trans-unit id="ID3049">
<source>The client application is not allowed to use the hybrid flow.</source>
<target state="translated">不允许此客户端使用混合流程.</target>
<note/>
<note />
</trans-unit>
<trans-unit id="ID3051">
<source>This client application is not allowed to use the specified scope.</source>
<target state="translated">不允许此客户端使用指定的 Scope.</target>
<note/>
<note />
</trans-unit>
<trans-unit id="ID3052">
<source>The specified '{0}' is invalid.</source>
<target state="translated">指定的 ‘{0}’ 无效.</target>
<note/>
<note />
</trans-unit>
<trans-unit id="ID3053">
<source>The '{0}' parameter is not valid for this client application.</source>
<target state="translated">参数 ‘{0}’ 对此客户端无效.</target>
<note/>
<note />
</trans-unit>
<trans-unit id="ID3054">
<source>The '{0}' parameter required for this client application is missing.</source>
<target state="translated">未提供此客户端必需参数 ‘{0}’.</target>
<note/>
<note />
</trans-unit>
<trans-unit id="ID3055">
<source>The specified client credentials are invalid.</source>
<target state="translated">指定的客户端凭据无效.</target>
<note/>
<note />
</trans-unit>
<trans-unit id="ID3056">
<source>This client application is not allowed to use the device endpoint.</source>
<target state="translated">不允许此客户端使用设备端点.</target>
<note/>
<note />
</trans-unit>
<trans-unit id="ID3057">
<source>The '{0}' and '{1}' parameters are required when using the client credentials grant.</source>
<target state="translated">使用客户端凭据授权时, 必须提供参数 ‘{0}’ 和 ‘{1}’.</target>
<note/>
<note />
</trans-unit>
<trans-unit id="ID3058">
<source>The '{0}' parameter is required when using the device code grant.</source>
<target state="translated">使用设备码授权时, 必须提供参数 ‘{0}’.</target>
<note/>
<note />
</trans-unit>
<trans-unit id="ID3059">
<source>The mandatory '{0}' and/or '{1}' parameters are missing.</source>
<target state="translated">未提供必需参数 ‘{0}’ 和/或 ‘{1}’.</target>
<note/>
<note />
</trans-unit>
<trans-unit id="ID3060">
<source>A scope with the same name already exists.</source>
<target state="translated">已存在具有相同名称的 Scope.</target>
<note/>
<note />
</trans-unit>
<trans-unit id="ID3063">
<source>This client application is not allowed to use the token endpoint.</source>
<target state="translated">不允许此客户端使用令牌端点.</target>
<note/>
<note />
</trans-unit>
<trans-unit id="ID3064">
<source>This client application is not allowed to use the specified grant type.</source>
<target state="translated">不允许此客户端使用指定的授予类型.</target>
<note/>
<note />
</trans-unit>
<trans-unit id="ID3065">
<source>The client application is not allowed to use the '{0}' scope.</source>
<target state="translated">不允许此客户端使用 Scope ‘{0}’.</target>
<note/>
<note />
</trans-unit>
<trans-unit id="ID3066">
<source>The specified authorization code cannot be used without sending a client identifier.</source>
<target state="translated">若不发送客户端 ID, 则不能使用指定的授权码.</target>
<note/>
<note />
</trans-unit>
<trans-unit id="ID3067">
<source>The specified device code cannot be used without sending a client identifier.</source>
<target state="translated">若不发送客户端 ID, 则不能使用指定的设备码.</target>
<note/>
<note />
</trans-unit>
<trans-unit id="ID3068">
<source>The specified refresh token cannot be used without sending a client identifier.</source>
<target state="translated">若不发送客户端 ID, 则不能使用指定的刷新令牌.</target>
<note/>
<note />
</trans-unit>
<trans-unit id="ID3069">
<source>The specified authorization code cannot be used by this client application.</source>
<target state="translated">此客户端不能使用指定的授权码.</target>
<note/>
<note />
</trans-unit>
<trans-unit id="ID3070">
<source>The specified device code cannot be used by this client application.</source>
<target state="translated">此客户端不能使用指定的设备码.</target>
<note/>
<note />
</trans-unit>
<trans-unit id="ID3071">
<source>The specified refresh token cannot be used by this client application.</source>
<target state="translated">此客户端不能使用指定的刷新令牌.</target>
<note/>
<note />
</trans-unit>
<trans-unit id="ID3072">
<source>The specified '{0}' parameter doesn't match the client redirection address the authorization code was initially sent to.</source>
<target state="translated">指定的参数 ‘{0}’ 与最初发送的客户端重定向地址不匹配.</target>
<note/>
<note />
</trans-unit>
<trans-unit id="ID3073">
<source>The '{0}' parameter cannot be used when no '{1}' was specified in the authorization request.</source>
<target state="translated">若授权请求中未指定 ‘{1}’, 则不能使用指定的参数 ‘{0}’.</target>
<note/>
<note />
</trans-unit>
<trans-unit id="ID3074">
<source>The '{0}' parameter is not valid in this context.</source>
<target state="translated">参数 ‘{0}’ 在此上下文中无效.</target>
<note/>
<note />
</trans-unit>
<trans-unit id="ID3075">
<source>This client application is not allowed to use the introspection endpoint.</source>
<target state="translated">不允许此客户端使用自省端点.</target>
<note/>
<note />
</trans-unit>
<trans-unit id="ID3076">
<source>The specified token cannot be introspected.</source>
<target state="translated">指定的令牌不能自省.</target>
<note/>
<note />
</trans-unit>
<trans-unit id="ID3077">
<source>The client application is not allowed to introspect the specified token.</source>
<target state="translated">不允许此客户端自省指定的令牌.</target>
<note/>
<note />
</trans-unit>
<trans-unit id="ID3078">
<source>This client application is not allowed to use the revocation endpoint.</source>
<target state="translated">此客户端不允许使用吊销端点.</target>
<note/>
<note />
</trans-unit>
<trans-unit id="ID3079">
<source>This token cannot be revoked.</source>
<target state="translated">无法吊销此令牌.</target>
<note/>
<note />
</trans-unit>
<trans-unit id="ID3080">
<source>The client application is not allowed to revoke the specified token.</source>
<target state="translated">不允许此客户端吊销指定的令牌.</target>
<note/>
<note />
</trans-unit>
<trans-unit id="ID3081">
<source>The mandatory '{0}' header is missing.</source>
<target state="translated">缺少必需标头 ‘{0}’.</target>
<note/>
<note />
</trans-unit>
<trans-unit id="ID3082">
<source>The specified '{0}' header is invalid.</source>
<target state="translated">指定的标头 ’{0}’ 无效.</target>
<note/>
<note />
</trans-unit>
<trans-unit id="ID3083">
<source>This server only accepts HTTPS requests.</source>
<target state="translated">此服务器仅接受 HTTPS 请求.</target>
<note/>
<note />
</trans-unit>
<trans-unit id="ID3084">
<source>The specified HTTP method is not valid.</source>
<target state="translated">指定的 HTTP 方法无效.</target>
<note/>
<note />
</trans-unit>
<trans-unit id="ID3085">
<source>A token with the same reference identifier already exists.</source>
<target state="translated">已存在具有相同引用 ID 的令牌.</target>
<note/>
<note />
</trans-unit>
<trans-unit id="ID3086">
<source>The token type cannot be null or empty.</source>
<target state="translated">令牌类型不能为空.</target>
<note/>
<note />
</trans-unit>
<trans-unit id="ID3087">
<source>Multiple client credentials cannot be specified.</source>
<target state="translated">无法指定多个客户端凭据.</target>
<note/>
<note />
</trans-unit>
<trans-unit id="ID3088">
<source>The issuer associated to the specified token is not valid.</source>
<target state="translated">与指定令牌关联的颁发者无效.</target>
<note/>
<note />
</trans-unit>
<trans-unit id="ID3089">
<source>The specified token is not of the expected type.</source>
<target state="translated">指定的令牌不是预期类型.</target>
<note/>
<note />
</trans-unit>
<trans-unit id="ID3090">
<source>The signing key associated to the specified token was not found.</source>
<target state="translated">未找到与指定令牌关联的签名密钥.</target>
<note/>
<note />
</trans-unit>
<trans-unit id="ID3091">
<source>The signature associated to the specified token is not valid.</source>
<target state="translated">与指定令牌关联的签名无效.</target>
<note/>
<note />
</trans-unit>
<trans-unit id="ID3092">
<source>This resource server is currently unavailable.</source>
<target state="translated">此资源服务器当前不可用.</target>
<note/>
<note />
</trans-unit>
<trans-unit id="ID3093">
<source>The specified token doesn't contain any audience.</source>
<target state="translated">指定的令牌不包含任何目标受众.</target>
<note/>
<note />
</trans-unit>
<trans-unit id="ID3094">
<source>The specified token cannot be used with this resource server.</source>
<target state="translated">指定的令牌不能用于此资源服务器.</target>
<note/>
<note />
</trans-unit>
<trans-unit id="ID3095">
<source>The user represented by the token is not allowed to perform the requested action.</source>
<target state="translated">不允许此令牌代表的用户执行请求的操作.</target>
<note/>
<note />
</trans-unit>
<trans-unit id="ID3096">
<source>No issuer could be found in the server configuration.</source>
<target state="translated">在服务器配置中找不到颁发者.</target>
<note/>
<note />
</trans-unit>
<trans-unit id="ID3097">
<source>A server configuration containing an invalid issuer was returned.</source>
<target state="translated">返回了包含无效颁发者的服务器配置.</target>
<note/>
<note />
</trans-unit>
<trans-unit id="ID3098">
<source>The issuer returned in the server configuration is not valid.</source>
<target state="translated">服务器配置中返回的颁发者无效.</target>
<note/>
<note />
</trans-unit>
<trans-unit id="ID3099">
<source>No JWKS endpoint could be found in the server configuration.</source>
<target state="translated">在服务器配置中找不到 JWKS 端点.</target>
<note/>
<note />
</trans-unit>
<trans-unit id="ID3100">
<source>A server configuration containing an invalid JWKS endpoint URL was returned.</source>
<target state="translated">返回了包含无效 JWKS 端点的服务器配置.</target>
<note/>
<note />
</trans-unit>
<trans-unit id="ID3101">
<source>A server configuration containing an invalid introspection endpoint URL was returned.</source>
<target state="translated">返回了包含无效自省端点的服务器配置.</target>
<note/>
<note />
</trans-unit>
<trans-unit id="ID3102">
<source>The JWKS document didn't contain a valid '{0}' node with at least one key.</source>
<target state="translated">JWKS 文档中没有 ‘{0}’ 节点.</target>
<note/>
<note />
</trans-unit>
<trans-unit id="ID3103">
<source>A JWKS response containing an unsupported key was returned.</source>
<target state="translated">返回了包含不受支持的密钥的 JWKS 响应.</target>
<note/>
<note />
</trans-unit>
<trans-unit id="ID3104">
<source>A JWKS response containing an invalid key was returned.</source>
<target state="translated">返回了包含无效密钥的 JWKS 响应.</target>
<note/>
<note />
</trans-unit>
<trans-unit id="ID3105">
<source>The mandatory '{0}' parameter couldn't be found in the introspection response.</source>
<target state="translated">自省响应中找不到必需参数 ‘{0}’.</target>
<note/>
<note />
</trans-unit>
<trans-unit id="ID3106">
<source>The token was rejected by the remote authentication server.</source>
<target state="translated">令牌被远程身份验证服务器拒绝.</target>
<note/>
<note />
</trans-unit>
<trans-unit id="ID3107">
<source>The '{0}' claim is malformed or isn't of the expected type.</source>
<target state="translated">Claim ‘{0}’ 格式错误或不是预期类型.</target>
<note/>
<note />
</trans-unit>
<trans-unit id="ID3108">
<source>An introspection response containing a malformed issuer was returned.</source>
<target state="translated">返回了包含错误格式颁发者的自省响应.</target>
<note/>
<note />
</trans-unit>
<trans-unit id="ID3109">
<source>The issuer returned in the introspection response is not valid.</source>
<target state="translated">自省响应返回的颁发者无效.</target>
<note/>
<note />
</trans-unit>
<trans-unit id="ID3110">
<source>The type of the introspected token doesn't match the expected type.</source>
<target state="translated">自省令牌的类型与预期类型不匹配.</target>
<note/>
<note />
</trans-unit>
<trans-unit id="ID3111">
<source>An application with the same client identifier already exists.</source>
<target state="translated">已存在具有相同客户端 ID 的应用程序.</target>
<note/>
<note />
</trans-unit>
<trans-unit id="ID3112">
<source>Only confidential, hybrid or public applications are supported by the default application manager.</source>
<target state="translated">默认应用程序管理器仅支持机密、混合或公共应用程序.</target>
<note/>
<note />
</trans-unit>
<trans-unit id="ID3113">
<source>The client secret cannot be null or empty for a confidential application.</source>
<target state="translated">对于机密应用程序, 客户端密钥不能为空.</target>
<note/>
<note />
</trans-unit>
<trans-unit id="ID3114">
<source>A client secret cannot be associated with a public application.</source>
<target state="translated">客户端密钥不能与公共应用程序关联.</target>
<note/>
<note />
</trans-unit>
<trans-unit id="ID3115">
<source>Callback URLs cannot contain a fragment.</source>
<target state="translated">回调网址不能包含 fragment.</target>
<note/>
<note />
</trans-unit>
<trans-unit id="ID3116">
<source>The authorization type cannot be null or empty.</source>
<target state="translated">授权类型不能为空.</target>
<note/>
<note />
</trans-unit>
<trans-unit id="ID3117">
<source>The specified authorization type is not supported by the default token manager.</source>
<target state="translated">默认令牌管理器不支持指定的授权类型.</target>
<note/>
<note />
</trans-unit>
<trans-unit id="ID3118">
<source>The client type cannot be null or empty.</source>
<target state="translated">客户端类型不能为空.</target>
<note/>
<note />
</trans-unit>
<trans-unit id="ID3119">
<source>Callback URLs cannot be null or empty.</source>
<target state="translated">回调网址不能为空.</target>
<note/>
<note />
</trans-unit>
<trans-unit id="ID3120">
<source>Callback URLs must be valid absolute URLs.</source>
<target state="translated">回调网址必须是有效的绝对路径网址.</target>
<note/>
<note />
</trans-unit>
<trans-unit id="ID9000">
<source>Removes orphaned tokens and authorizations from the database.</source>
<target state="translated">从数据库中删除孤立的令牌和授权.</target>
<note/>
<note />
</trans-unit>
<trans-unit id="ID9001">
<source>Starts the scheduled task at regular intervals.</source>
<target state="translated">定期启动计划的任务.</target>
<note/>
<note />
</trans-unit>
</body>
</file>

238
src/OpenIddict.Abstractions/Resources/xlf/OpenIddictResources.zh-Hant.xlf

@ -5,597 +5,597 @@
<trans-unit id="ID3000">
<source>The security token is missing.</source>
<target state="translated">缺少安全權杖.</target>
<note/>
<note />
</trans-unit>
<trans-unit id="ID3001">
<source>The specified authorization code is invalid.</source>
<target state="translated">指定的授權代碼無效.</target>
<note/>
<note />
</trans-unit>
<trans-unit id="ID3002">
<source>The specified device code is invalid.</source>
<target state="translated">指定的裝置代碼無效.</target>
<note/>
<note />
</trans-unit>
<trans-unit id="ID3003">
<source>The specified refresh token is invalid.</source>
<target state="translated">指定的重新整理權杖無效.</target>
<note/>
<note />
</trans-unit>
<trans-unit id="ID3004">
<source>The specified token is invalid.</source>
<target state="translated">指定的權杖无效.</target>
<note/>
<note />
</trans-unit>
<trans-unit id="ID3005">
<source>The specified token is not an authorization code.</source>
<target state="translated">指定的權杖不是授權代碼.</target>
<note/>
<note />
</trans-unit>
<trans-unit id="ID3006">
<source>The specified token is not an device code.</source>
<target state="translated">指定的權杖不是裝置代碼.</target>
<note/>
<note />
</trans-unit>
<trans-unit id="ID3007">
<source>The specified token is not a refresh token.</source>
<target state="translated">指定的權杖不是重新整理權杖.</target>
<note/>
<note />
</trans-unit>
<trans-unit id="ID3008">
<source>The specified token is not an access token.</source>
<target state="translated">指定的權杖不是存取權杖.</target>
<note/>
<note />
</trans-unit>
<trans-unit id="ID3009">
<source>The specified identity token is invalid.</source>
<target state="translated">指定的識別碼權杖無效.</target>
<note/>
<note />
</trans-unit>
<trans-unit id="ID3010">
<source>The specified authorization code has already been redeemed.</source>
<target state="translated">指定的授權代碼已兌換.</target>
<note/>
<note />
</trans-unit>
<trans-unit id="ID3011">
<source>The specified device code has already been redeemed.</source>
<target state="translated">指定的设备代碼已兌換.</target>
<note/>
<note />
</trans-unit>
<trans-unit id="ID3012">
<source>The specified refresh token has already been redeemed.</source>
<target state="translated">指定的重新整理權杖已兌換.</target>
<note/>
<note />
</trans-unit>
<trans-unit id="ID3013">
<source>The specified token has already been redeemed.</source>
<target state="translated">指定的權杖已兌換.</target>
<note/>
<note />
</trans-unit>
<trans-unit id="ID3014">
<source>The authorization has not been granted yet by the end user.</source>
<target state="translated">使用者尚未批准授權.</target>
<note/>
<note />
</trans-unit>
<trans-unit id="ID3015">
<source>The authorization was denied by the end user.</source>
<target state="translated">使用者拒絕授權.</target>
<note/>
<note />
</trans-unit>
<trans-unit id="ID3016">
<source>The specified authorization code is no longer valid.</source>
<target state="translated">指定的授權代碼不再有效.</target>
<note/>
<note />
</trans-unit>
<trans-unit id="ID3017">
<source>The specified device code is no longer valid.</source>
<target state="translated">指定的裝置代碼不再有效.</target>
<note/>
<note />
</trans-unit>
<trans-unit id="ID3018">
<source>The specified refresh token is no longer valid.</source>
<target state="translated">指定的重新整理權杖不再有效.</target>
<note/>
<note />
</trans-unit>
<trans-unit id="ID3019">
<source>The specified token is no longer valid.</source>
<target state="translated">指定的權杖不再有效.</target>
<note/>
<note />
</trans-unit>
<trans-unit id="ID3020">
<source>The authorization associated with the authorization code is no longer valid.</source>
<target state="translated">與授權代碼關聯的授權不再有效.</target>
<note/>
<note />
</trans-unit>
<trans-unit id="ID3021">
<source>The authorization associated with the device code is no longer valid.</source>
<target state="translated">與裝置代碼關聯的授權不再有效.</target>
<note/>
<note />
</trans-unit>
<trans-unit id="ID3022">
<source>The authorization associated with the refresh token is no longer valid.</source>
<target state="translated">與重新整理權杖關聯的授權不再有效.</target>
<note/>
<note />
</trans-unit>
<trans-unit id="ID3023">
<source>The authorization associated with the token is no longer valid.</source>
<target state="translated">與權杖關聯的授權不再有效.</target>
<note/>
<note />
</trans-unit>
<trans-unit id="ID3024">
<source>The token request was rejected by the authentication server.</source>
<target state="translated">身份驗證伺服器拒絕了權杖請求.</target>
<note/>
<note />
</trans-unit>
<trans-unit id="ID3025">
<source>The user information access demand was rejected by the authentication server.</source>
<target state="translated">身份驗證伺服器拒絕了使用者資訊存取請求.</target>
<note/>
<note />
</trans-unit>
<trans-unit id="ID3026">
<source>The specified user code is no longer valid.</source>
<target state="translated">指定的使用者代碼不再有效.</target>
<note/>
<note />
</trans-unit>
<trans-unit id="ID3028">
<source>The '{0}' parameter is not supported.</source>
<target state="translated">不支援引數 ‘{0}’.</target>
<note/>
<note />
</trans-unit>
<trans-unit id="ID3029">
<source>The mandatory '{0}' parameter is missing.</source>
<target state="translated">缺少必要引數 ‘{0}’.</target>
<note/>
<note />
</trans-unit>
<trans-unit id="ID3030">
<source>The '{0}' parameter must be a valid absolute URL.</source>
<target state="translated">引數 ’{0}’ 必須是有效的絕對路徑網址.</target>
<note/>
<note />
</trans-unit>
<trans-unit id="ID3031">
<source>The '{0}' parameter must not include a fragment.</source>
<target state="translated">引數 ‘{0}’ 不能包含 fragment.</target>
<note/>
<note />
</trans-unit>
<trans-unit id="ID3032">
<source>The specified '{0}' is not supported.</source>
<target state="translated">不支援指定的 ‘{0}’.</target>
<note/>
<note />
</trans-unit>
<trans-unit id="ID3033">
<source>The specified '{0}'/'{1}' combination is invalid.</source>
<target state="translated">指定的 ‘{0}’ / ‘{1}’ 組合無效.</target>
<note/>
<note />
</trans-unit>
<trans-unit id="ID3034">
<source>The mandatory '{0}' scope is missing.</source>
<target state="translated">缺少必要 Scope ‘{0}’.</target>
<note/>
<note />
</trans-unit>
<trans-unit id="ID3035">
<source>The '{0}' scope is not allowed.</source>
<target state="translated">不允許 Scope ‘{0}’.</target>
<note/>
<note />
</trans-unit>
<trans-unit id="ID3036">
<source>The client identifier cannot be null or empty.</source>
<target state="translated">消費者金鑰不能為空.</target>
<note/>
<note />
</trans-unit>
<trans-unit id="ID3037">
<source>The '{0}' parameter cannot be used without '{1}'.</source>
<target state="translated">引數 ‘{0}’ 要求 ‘{1}’.</target>
<note/>
<note />
</trans-unit>
<trans-unit id="ID3038">
<source>The status cannot be null or empty.</source>
<target state="translated">狀態不能為空.</target>
<note/>
<note />
</trans-unit>
<trans-unit id="ID3039">
<source>Scopes cannot be null or empty.</source>
<target state="translated">Scope 不能為空.</target>
<note/>
<note />
</trans-unit>
<trans-unit id="ID3040">
<source>The '{0}' and '{1}' parameters can only be used with a response type containing '{2}'.</source>
<target state="translated">引數 ‘{0}’ 和 ‘{1}’ 只能在響應型別包含 ‘{2}’ 時使用.</target>
<note/>
<note />
</trans-unit>
<trans-unit id="ID3041">
<source>The specified '{0}' is not allowed when using PKCE.</source>
<target state="translated">使用 PKCE 時不允許指定的 ‘{0}’.</target>
<note/>
<note />
</trans-unit>
<trans-unit id="ID3042">
<source>Scopes cannot contain spaces.</source>
<target state="translated">Scope 不能包含空格.</target>
<note/>
<note />
</trans-unit>
<trans-unit id="ID3043">
<source>The specified '{0}' is not valid for this client application.</source>
<target state="translated">指定的 ‘{0}’ 對此連線應用程式無效.</target>
<note/>
<note />
</trans-unit>
<trans-unit id="ID3044">
<source>The scope name cannot be null or empty.</source>
<target state="translated">Scope 不能為空.</target>
<note/>
<note />
</trans-unit>
<trans-unit id="ID3045">
<source>The scope name cannot contain spaces.</source>
<target state="translated">Scope 不能包含空格.</target>
<note/>
<note />
</trans-unit>
<trans-unit id="ID3046">
<source>This client application is not allowed to use the authorization endpoint.</source>
<target state="translated">不允許此連線應用程式使用授權端點.</target>
<note/>
<note />
</trans-unit>
<trans-unit id="ID3047">
<source>The client application is not allowed to use the authorization code flow.</source>
<target state="translated">不允許此連線應用程式使用授權代碼流程.</target>
<note/>
<note />
</trans-unit>
<trans-unit id="ID3048">
<source>The client application is not allowed to use the implicit flow.</source>
<target state="translated">不允許此連線應用程式使用隱式流程.</target>
<note/>
<note />
</trans-unit>
<trans-unit id="ID3049">
<source>The client application is not allowed to use the hybrid flow.</source>
<target state="translated">不允許此連線應用程式使用混合流程.</target>
<note/>
<note />
</trans-unit>
<trans-unit id="ID3051">
<source>This client application is not allowed to use the specified scope.</source>
<target state="translated">不允許此連線應用程式使用指定的 Scope.</target>
<note/>
<note />
</trans-unit>
<trans-unit id="ID3052">
<source>The specified '{0}' is invalid.</source>
<target state="translated">指定的 ‘{0}’ 無效.</target>
<note/>
<note />
</trans-unit>
<trans-unit id="ID3053">
<source>The '{0}' parameter is not valid for this client application.</source>
<target state="translated">引數 ‘{0}’ 對此連線應用程式無效.</target>
<note/>
<note />
</trans-unit>
<trans-unit id="ID3054">
<source>The '{0}' parameter required for this client application is missing.</source>
<target state="translated">未提供此連線應用程式必要引數 ‘{0}’.</target>
<note/>
<note />
</trans-unit>
<trans-unit id="ID3055">
<source>The specified client credentials are invalid.</source>
<target state="translated">指定的連線應用程式機密無效.</target>
<note/>
<note />
</trans-unit>
<trans-unit id="ID3056">
<source>This client application is not allowed to use the device endpoint.</source>
<target state="translated">不允許此連線應用程式使用裝置端點.</target>
<note/>
<note />
</trans-unit>
<trans-unit id="ID3057">
<source>The '{0}' and '{1}' parameters are required when using the client credentials grant.</source>
<target state="translated">使用連線應用程式機密授權時, 必須提供引數 ‘{0}’ 和 ‘{1}’.</target>
<note/>
<note />
</trans-unit>
<trans-unit id="ID3058">
<source>The '{0}' parameter is required when using the device code grant.</source>
<target state="translated">使用裝置代碼授權時, 必須提供引數 ‘{0}’.</target>
<note/>
<note />
</trans-unit>
<trans-unit id="ID3059">
<source>The mandatory '{0}' and/or '{1}' parameters are missing.</source>
<target state="translated">未提供必要引數 ‘{0}’ 和/或 ‘{1}’.</target>
<note/>
<note />
</trans-unit>
<trans-unit id="ID3060">
<source>A scope with the same name already exists.</source>
<target state="translated">已存在具有相同名稱的 Scope.</target>
<note/>
<note />
</trans-unit>
<trans-unit id="ID3063">
<source>This client application is not allowed to use the token endpoint.</source>
<target state="translated">不允許此連線應用程式使用權杖端點.</target>
<note/>
<note />
</trans-unit>
<trans-unit id="ID3064">
<source>This client application is not allowed to use the specified grant type.</source>
<target state="translated">不允許此連線應用程式使用指定的驗證類型.</target>
<note/>
<note />
</trans-unit>
<trans-unit id="ID3065">
<source>The client application is not allowed to use the '{0}' scope.</source>
<target state="translated">不允許此連線應用程式使用 Scope ‘{0}’.</target>
<note/>
<note />
</trans-unit>
<trans-unit id="ID3066">
<source>The specified authorization code cannot be used without sending a client identifier.</source>
<target state="translated">若不傳送連線應用程式 ID, 則不能使用指定的授權代碼.</target>
<note/>
<note />
</trans-unit>
<trans-unit id="ID3067">
<source>The specified device code cannot be used without sending a client identifier.</source>
<target state="translated">若不傳送連線應用程式 ID, 則不能使用指定的裝置代碼.</target>
<note/>
<note />
</trans-unit>
<trans-unit id="ID3068">
<source>The specified refresh token cannot be used without sending a client identifier.</source>
<target state="translated">若不傳送連線應用程式 ID, 則不能使用指定的重新整理權杖.</target>
<note/>
<note />
</trans-unit>
<trans-unit id="ID3069">
<source>The specified authorization code cannot be used by this client application.</source>
<target state="translated">此連線應用程式不能使用指定的授權代碼.</target>
<note/>
<note />
</trans-unit>
<trans-unit id="ID3070">
<source>The specified device code cannot be used by this client application.</source>
<target state="translated">此連線應用程式不能使用指定的裝置代碼.</target>
<note/>
<note />
</trans-unit>
<trans-unit id="ID3071">
<source>The specified refresh token cannot be used by this client application.</source>
<target state="translated">此連線應用程式不能使用指定的重新整理權杖.</target>
<note/>
<note />
</trans-unit>
<trans-unit id="ID3072">
<source>The specified '{0}' parameter doesn't match the client redirection address the authorization code was initially sent to.</source>
<target state="translated">指定的引數 ‘{0}’ 與最初發送的連線應用程式重新導向位址不匹配.</target>
<note/>
<note />
</trans-unit>
<trans-unit id="ID3073">
<source>The '{0}' parameter cannot be used when no '{1}' was specified in the authorization request.</source>
<target state="translated">若授權請求中未指定 ‘{1}’, 則不能使用指定的引數 ‘{0}’.</target>
<note/>
<note />
</trans-unit>
<trans-unit id="ID3074">
<source>The '{0}' parameter is not valid in this context.</source>
<target state="translated">引數 ‘{0}’ 在此語境中無效.</target>
<note/>
<note />
</trans-unit>
<trans-unit id="ID3075">
<source>This client application is not allowed to use the introspection endpoint.</source>
<target state="translated">不允許此連線應用程式使用自省端點.</target>
<note/>
<note />
</trans-unit>
<trans-unit id="ID3076">
<source>The specified token cannot be introspected.</source>
<target state="translated">指定的權杖不能自省.</target>
<note/>
<note />
</trans-unit>
<trans-unit id="ID3077">
<source>The client application is not allowed to introspect the specified token.</source>
<target state="translated">不允許此連線應用程式自省指定的權杖.</target>
<note/>
<note />
</trans-unit>
<trans-unit id="ID3078">
<source>This client application is not allowed to use the revocation endpoint.</source>
<target state="translated">此連線應用程式不允許使用吊銷端點.</target>
<note/>
<note />
</trans-unit>
<trans-unit id="ID3079">
<source>This token cannot be revoked.</source>
<target state="translated">無法吊銷此權杖.</target>
<note/>
<note />
</trans-unit>
<trans-unit id="ID3080">
<source>The client application is not allowed to revoke the specified token.</source>
<target state="translated">不允許此連線應用程式吊銷指定的權杖.</target>
<note/>
<note />
</trans-unit>
<trans-unit id="ID3081">
<source>The mandatory '{0}' header is missing.</source>
<target state="translated">缺少必要標頭 ‘{0}’.</target>
<note/>
<note />
</trans-unit>
<trans-unit id="ID3082">
<source>The specified '{0}' header is invalid.</source>
<target state="translated">指定的標頭 ’{0}’ 無效.</target>
<note/>
<note />
</trans-unit>
<trans-unit id="ID3083">
<source>This server only accepts HTTPS requests.</source>
<target state="translated">此伺服器僅接受 HTTPS 請求.</target>
<note/>
<note />
</trans-unit>
<trans-unit id="ID3084">
<source>The specified HTTP method is not valid.</source>
<target state="translated">指定的 HTTP 方法無效.</target>
<note/>
<note />
</trans-unit>
<trans-unit id="ID3085">
<source>A token with the same reference identifier already exists.</source>
<target state="translated">已存在具有相同引用 ID 的權杖.</target>
<note/>
<note />
</trans-unit>
<trans-unit id="ID3086">
<source>The token type cannot be null or empty.</source>
<target state="translated">權杖型別不能為空.</target>
<note/>
<note />
</trans-unit>
<trans-unit id="ID3087">
<source>Multiple client credentials cannot be specified.</source>
<target state="translated">無法指定多個連線應用程式機密.</target>
<note/>
<note />
</trans-unit>
<trans-unit id="ID3088">
<source>The issuer associated to the specified token is not valid.</source>
<target state="translated">與指定權杖關聯的頒發者無效.</target>
<note/>
<note />
</trans-unit>
<trans-unit id="ID3089">
<source>The specified token is not of the expected type.</source>
<target state="translated">指定的權杖不是預期型別.</target>
<note/>
<note />
</trans-unit>
<trans-unit id="ID3090">
<source>The signing key associated to the specified token was not found.</source>
<target state="translated">未找到與指定權杖關聯的簽名金鑰.</target>
<note/>
<note />
</trans-unit>
<trans-unit id="ID3091">
<source>The signature associated to the specified token is not valid.</source>
<target state="translated">與指定權杖關聯的簽名無效.</target>
<note/>
<note />
</trans-unit>
<trans-unit id="ID3092">
<source>This resource server is currently unavailable.</source>
<target state="translated">此資源伺服器當前不可用.</target>
<note/>
<note />
</trans-unit>
<trans-unit id="ID3093">
<source>The specified token doesn't contain any audience.</source>
<target state="translated">指定的權杖不包含任何目標受眾.</target>
<note/>
<note />
</trans-unit>
<trans-unit id="ID3094">
<source>The specified token cannot be used with this resource server.</source>
<target state="translated">指定的權杖不能用於此資源伺服器.</target>
<note/>
<note />
</trans-unit>
<trans-unit id="ID3095">
<source>The user represented by the token is not allowed to perform the requested action.</source>
<target state="translated">不允許此權杖代表的使用者執行請求的操作.</target>
<note/>
<note />
</trans-unit>
<trans-unit id="ID3096">
<source>No issuer could be found in the server configuration.</source>
<target state="translated">在伺服器配置中找不到頒發者.</target>
<note/>
<note />
</trans-unit>
<trans-unit id="ID3097">
<source>A server configuration containing an invalid issuer was returned.</source>
<target state="translated">返回了包含無效頒發者的伺服器配置.</target>
<note/>
<note />
</trans-unit>
<trans-unit id="ID3098">
<source>The issuer returned in the server configuration is not valid.</source>
<target state="translated">伺服器配置中返回的頒發者無效.</target>
<note/>
<note />
</trans-unit>
<trans-unit id="ID3099">
<source>No JWKS endpoint could be found in the server configuration.</source>
<target state="translated">在伺服器配置中找不到 JWKS 端點.</target>
<note/>
<note />
</trans-unit>
<trans-unit id="ID3100">
<source>A server configuration containing an invalid JWKS endpoint URL was returned.</source>
<target state="translated">返回了包含無效 JWKS 端點的伺服器配置.</target>
<note/>
<note />
</trans-unit>
<trans-unit id="ID3101">
<source>A server configuration containing an invalid introspection endpoint URL was returned.</source>
<target state="translated">返回了包含無效自省端點的伺服器配置.</target>
<note/>
<note />
</trans-unit>
<trans-unit id="ID3102">
<source>The JWKS document didn't contain a valid '{0}' node with at least one key.</source>
<target state="translated">JWKS 文件中沒有 ‘{0}’ 節點.</target>
<note/>
<note />
</trans-unit>
<trans-unit id="ID3103">
<source>A JWKS response containing an unsupported key was returned.</source>
<target state="translated">返回了包含不受支援的金鑰的 JWKS 響應.</target>
<note/>
<note />
</trans-unit>
<trans-unit id="ID3104">
<source>A JWKS response containing an invalid key was returned.</source>
<target state="translated">返回了包含無效金鑰的 JWKS 響應.</target>
<note/>
<note />
</trans-unit>
<trans-unit id="ID3105">
<source>The mandatory '{0}' parameter couldn't be found in the introspection response.</source>
<target state="translated">自省響應中找不到必要引數 ‘{0}’.</target>
<note/>
<note />
</trans-unit>
<trans-unit id="ID3106">
<source>The token was rejected by the remote authentication server.</source>
<target state="translated">權杖被遠端身份驗證伺服器拒絕.</target>
<note/>
<note />
</trans-unit>
<trans-unit id="ID3107">
<source>The '{0}' claim is malformed or isn't of the expected type.</source>
<target state="translated">Claim ‘{0}’ 格式錯誤或不是預期型別.</target>
<note/>
<note />
</trans-unit>
<trans-unit id="ID3108">
<source>An introspection response containing a malformed issuer was returned.</source>
<target state="translated">返回了包含錯誤格式頒發者的自省響應.</target>
<note/>
<note />
</trans-unit>
<trans-unit id="ID3109">
<source>The issuer returned in the introspection response is not valid.</source>
<target state="translated">自省響應返回的頒發者無效.</target>
<note/>
<note />
</trans-unit>
<trans-unit id="ID3110">
<source>The type of the introspected token doesn't match the expected type.</source>
<target state="translated">自省權杖的型別與預期型別不匹配.</target>
<note/>
<note />
</trans-unit>
<trans-unit id="ID3111">
<source>An application with the same client identifier already exists.</source>
<target state="translated">已存在具有相同消費者金鑰的應用程式.</target>
<note/>
<note />
</trans-unit>
<trans-unit id="ID3112">
<source>Only confidential, hybrid or public applications are supported by the default application manager.</source>
<target state="translated">預設應用程式管理器僅支援機密、混合或公共應用程式.</target>
<note/>
<note />
</trans-unit>
<trans-unit id="ID3113">
<source>The client secret cannot be null or empty for a confidential application.</source>
<target state="translated">對於機密應用程式, 連線應用程式金鑰不能為空.</target>
<note/>
<note />
</trans-unit>
<trans-unit id="ID3114">
<source>A client secret cannot be associated with a public application.</source>
<target state="translated">連線應用程式金鑰不能與公共應用程式關聯.</target>
<note/>
<note />
</trans-unit>
<trans-unit id="ID3115">
<source>Callback URLs cannot contain a fragment.</source>
<target state="translated">重新導向位址不能包含 fragment.</target>
<note/>
<note />
</trans-unit>
<trans-unit id="ID3116">
<source>The authorization type cannot be null or empty.</source>
<target state="translated">授權型別不能為空.</target>
<note/>
<note />
</trans-unit>
<trans-unit id="ID3117">
<source>The specified authorization type is not supported by the default token manager.</source>
<target state="translated">預設權杖管理器不支援指定的授權型別.</target>
<note/>
<note />
</trans-unit>
<trans-unit id="ID3118">
<source>The client type cannot be null or empty.</source>
<target state="translated">連線應用程式型別不能為空.</target>
<note/>
<note />
</trans-unit>
<trans-unit id="ID3119">
<source>Callback URLs cannot be null or empty.</source>
<target state="translated">重新導向位址不能為空.</target>
<note/>
<note />
</trans-unit>
<trans-unit id="ID3120">
<source>Callback URLs must be valid absolute URLs.</source>
<target state="translated">重新導向位址必須是有效的絕對路徑網址.</target>
<note/>
<note />
</trans-unit>
<trans-unit id="ID9000">
<source>Removes orphaned tokens and authorizations from the database.</source>
<target state="translated">從資料庫中刪除孤立的權杖和授權.</target>
<note/>
<note />
</trans-unit>
<trans-unit id="ID9001">
<source>Starts the scheduled task at regular intervals.</source>
<target state="translated">定期啟動計劃的任務.</target>
<note/>
<note />
</trans-unit>
</body>
</file>

30
src/OpenIddict.Abstractions/Stores/IOpenIddictAuthorizationStore.cs

@ -162,6 +162,17 @@ namespace OpenIddict.Abstractions
Func<IQueryable<TAuthorization>, TState, IQueryable<TResult>> query,
TState state, CancellationToken cancellationToken);
/// <summary>
/// Retrieves the creation date associated with an authorization.
/// </summary>
/// <param name="authorization">The authorization.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="ValueTask{TResult}"/> that can be used to monitor the asynchronous operation,
/// whose result returns the creation date associated with the specified authorization.
/// </returns>
ValueTask<DateTimeOffset?> GetCreationDateAsync(TAuthorization authorization, CancellationToken cancellationToken);
/// <summary>
/// Retrieves the unique identifier associated with an authorization.
/// </summary>
@ -261,11 +272,17 @@ namespace OpenIddict.Abstractions
TState state, CancellationToken cancellationToken);
/// <summary>
/// Removes the authorizations that are marked as invalid and the ad-hoc ones that have no valid/nonexpired token attached.
/// Removes the authorizations that are marked as invalid and the ad-hoc ones that have no token attached.
/// Only authorizations created before the specified <paramref name="threshold"/> are removed.
/// </summary>
/// <remarks>
/// To ensure ad-hoc authorizations that no longer have any valid/non-expired token
/// attached are correctly removed, the tokens should always be pruned first.
/// </remarks>
/// <param name="threshold">The date before which authorizations are not pruned.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>A <see cref="ValueTask"/> that can be used to monitor the asynchronous operation.</returns>
ValueTask PruneAsync(CancellationToken cancellationToken);
ValueTask PruneAsync(DateTimeOffset threshold, CancellationToken cancellationToken);
/// <summary>
/// Sets the application identifier associated with an authorization.
@ -276,6 +293,15 @@ namespace OpenIddict.Abstractions
/// <returns>A <see cref="ValueTask"/> that can be used to monitor the asynchronous operation.</returns>
ValueTask SetApplicationIdAsync(TAuthorization authorization, string? identifier, CancellationToken cancellationToken);
/// <summary>
/// Sets the creation date associated with an authorization.
/// </summary>
/// <param name="authorization">The authorization.</param>
/// <param name="date">The expiration date.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>A <see cref="ValueTask"/> that can be used to monitor the asynchronous operation.</returns>
ValueTask SetCreationDateAsync(TAuthorization authorization, DateTimeOffset? date, CancellationToken cancellationToken);
/// <summary>
/// Sets the additional properties associated with an authorization.
/// </summary>

6
src/OpenIddict.Abstractions/Stores/IOpenIddictTokenStore.cs

@ -312,11 +312,13 @@ namespace OpenIddict.Abstractions
TState state, CancellationToken cancellationToken);
/// <summary>
/// Removes the tokens that are marked as expired or invalid.
/// Removes the tokens that are marked as invalid or whose attached authorization is no longer valid.
/// Only tokens created before the specified <paramref name="threshold"/> are removed.
/// </summary>
/// <param name="threshold">The date before which tokens are not pruned.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>A <see cref="ValueTask"/> that can be used to monitor the asynchronous operation.</returns>
ValueTask PruneAsync(CancellationToken cancellationToken);
ValueTask PruneAsync(DateTimeOffset threshold, CancellationToken cancellationToken);
/// <summary>
/// Sets the application identifier associated with a token.

43
src/OpenIddict.Core/Managers/OpenIddictAuthorizationManager.cs

@ -683,6 +683,26 @@ namespace OpenIddict.Core
return Store.GetAsync(query, state, cancellationToken);
}
/// <summary>
/// Retrieves the creation date associated with an authorization.
/// </summary>
/// <param name="authorization">The authorization.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="ValueTask{TResult}"/> that can be used to monitor the asynchronous operation,
/// whose result returns the creation date associated with the specified authorization.
/// </returns>
public virtual ValueTask<DateTimeOffset?> GetCreationDateAsync(
TAuthorization authorization, CancellationToken cancellationToken = default)
{
if (authorization == null)
{
throw new ArgumentNullException(nameof(authorization));
}
return Store.GetCreationDateAsync(authorization, cancellationToken);
}
/// <summary>
/// Retrieves the unique identifier associated with an authorization.
/// </summary>
@ -920,7 +940,8 @@ namespace OpenIddict.Core
}
await Store.SetApplicationIdAsync(authorization, descriptor.ApplicationId, cancellationToken);
await Store.SetScopesAsync(authorization, ImmutableArray.CreateRange(descriptor.Scopes), cancellationToken);
await Store.SetCreationDateAsync(authorization, descriptor.CreationDate, cancellationToken);
await Store.SetScopesAsync(authorization, descriptor.Scopes.ToImmutableArray(), cancellationToken);
await Store.SetStatusAsync(authorization, descriptor.Status, cancellationToken);
await Store.SetSubjectAsync(authorization, descriptor.Subject, cancellationToken);
await Store.SetTypeAsync(authorization, descriptor.Type, cancellationToken);
@ -950,6 +971,7 @@ namespace OpenIddict.Core
}
descriptor.ApplicationId = await Store.GetApplicationIdAsync(authorization, cancellationToken);
descriptor.CreationDate = await Store.GetCreationDateAsync(authorization, cancellationToken);
descriptor.Scopes.Clear();
descriptor.Scopes.UnionWith(await Store.GetScopesAsync(authorization, cancellationToken));
descriptor.Status = await Store.GetStatusAsync(authorization, cancellationToken);
@ -958,14 +980,20 @@ namespace OpenIddict.Core
}
/// <summary>
/// Removes the authorizations that are marked as invalid and the ad-hoc ones that have no valid/nonexpired token attached.
/// Removes the authorizations that are marked as invalid and the ad-hoc ones that have no token attached.
/// Only authorizations created before the specified <paramref name="threshold"/> are removed.
/// </summary>
/// <remarks>
/// To ensure ad-hoc authorizations that no longer have any valid/non-expired token
/// attached are correctly removed, the tokens should always be pruned first.
/// </remarks>
/// <param name="threshold">The date before which authorizations are not pruned.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="ValueTask"/> that can be used to monitor the asynchronous operation.
/// </returns>
public virtual ValueTask PruneAsync(CancellationToken cancellationToken = default)
=> Store.PruneAsync(cancellationToken);
public virtual ValueTask PruneAsync(DateTimeOffset threshold, CancellationToken cancellationToken = default)
=> Store.PruneAsync(threshold, cancellationToken);
/// <summary>
/// Sets the application identifier associated with an authorization.
@ -1225,6 +1253,9 @@ namespace OpenIddict.Core
ValueTask<TResult> IOpenIddictAuthorizationManager.GetAsync<TState, TResult>(Func<IQueryable<object>, TState, IQueryable<TResult>> query, TState state, CancellationToken cancellationToken)
=> GetAsync(query, state, cancellationToken);
ValueTask<DateTimeOffset?> IOpenIddictAuthorizationManager.GetCreationDateAsync(object authorization, CancellationToken cancellationToken)
=> GetCreationDateAsync((TAuthorization) authorization, cancellationToken);
/// <inheritdoc/>
ValueTask<string?> IOpenIddictAuthorizationManager.GetIdAsync(object authorization, CancellationToken cancellationToken)
=> GetIdAsync((TAuthorization) authorization, cancellationToken);
@ -1278,8 +1309,8 @@ namespace OpenIddict.Core
=> PopulateAsync((TAuthorization) authorization, descriptor, cancellationToken);
/// <inheritdoc/>
ValueTask IOpenIddictAuthorizationManager.PruneAsync(CancellationToken cancellationToken)
=> PruneAsync(cancellationToken);
ValueTask IOpenIddictAuthorizationManager.PruneAsync(DateTimeOffset threshold, CancellationToken cancellationToken)
=> PruneAsync(threshold, cancellationToken);
/// <inheritdoc/>
ValueTask IOpenIddictAuthorizationManager.SetApplicationIdAsync(object authorization, string? identifier, CancellationToken cancellationToken)

12
src/OpenIddict.Core/Managers/OpenIddictTokenManager.cs

@ -984,14 +984,16 @@ namespace OpenIddict.Core
}
/// <summary>
/// Removes the tokens that are marked as expired or invalid.
/// Removes the tokens that are marked as invalid or whose attached authorization is no longer valid.
/// Only tokens created before the specified <paramref name="threshold"/> are removed.
/// </summary>
/// <param name="threshold">The date before which tokens are not pruned.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="ValueTask"/> that can be used to monitor the asynchronous operation.
/// </returns>
public virtual ValueTask PruneAsync(CancellationToken cancellationToken = default)
=> Store.PruneAsync(cancellationToken);
public virtual ValueTask PruneAsync(DateTimeOffset threshold, CancellationToken cancellationToken = default)
=> Store.PruneAsync(threshold, cancellationToken);
/// <summary>
/// Sets the application identifier associated with a token.
@ -1508,8 +1510,8 @@ namespace OpenIddict.Core
=> PopulateAsync((TToken) token, descriptor, cancellationToken);
/// <inheritdoc/>
ValueTask IOpenIddictTokenManager.PruneAsync(CancellationToken cancellationToken)
=> PruneAsync(cancellationToken);
ValueTask IOpenIddictTokenManager.PruneAsync(DateTimeOffset threshold, CancellationToken cancellationToken)
=> PruneAsync(threshold, cancellationToken);
/// <inheritdoc/>
ValueTask IOpenIddictTokenManager.SetApplicationIdAsync(object token, string? identifier, CancellationToken cancellationToken)

16
src/OpenIddict.Core/OpenIddictCoreBuilder.cs

@ -794,25 +794,15 @@ namespace Microsoft.Extensions.DependencyInjection
return Configure(options => options.EntityCacheLimit = limit);
}
/// <summary>
/// Determines whether the specified object is equal to the current object.
/// </summary>
/// <param name="obj">The object to compare with the current object.</param>
/// <returns><c>true</c> if the specified object is equal to the current object; otherwise, false.</returns>
/// <inheritdoc/>
[EditorBrowsable(EditorBrowsableState.Never)]
public override bool Equals(object? obj) => base.Equals(obj);
/// <summary>
/// Serves as the default hash function.
/// </summary>
/// <returns>A hash code for the current object.</returns>
/// <inheritdoc/>
[EditorBrowsable(EditorBrowsableState.Never)]
public override int GetHashCode() => base.GetHashCode();
/// <summary>
/// Returns a string that represents the current object.
/// </summary>
/// <returns>A string that represents the current object.</returns>
/// <inheritdoc/>
[EditorBrowsable(EditorBrowsableState.Never)]
public override string? ToString() => base.ToString();
}

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

@ -42,6 +42,11 @@ namespace OpenIddict.EntityFramework.Models
/// </summary>
public virtual string? ConcurrencyToken { get; set; } = Guid.NewGuid().ToString();
/// <summary>
/// Gets or sets the creation date of the current authorization.
/// </summary>
public virtual DateTimeOffset? CreationDate { get; set; }
/// <summary>
/// Gets or sets the unique identifier associated with the current authorization.
/// </summary>

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

@ -47,12 +47,12 @@ namespace OpenIddict.EntityFramework.Models
public virtual string? ConcurrencyToken { get; set; } = Guid.NewGuid().ToString();
/// <summary>
/// Gets or sets the date on which the token will start to be considered valid.
/// Gets or sets the creation date of the current token.
/// </summary>
public virtual DateTimeOffset? CreationDate { get; set; }
/// <summary>
/// Gets or sets the date on which the token will no longer be considered valid.
/// Gets or sets the expiration date of the current token.
/// </summary>
public virtual DateTimeOffset? ExpirationDate { get; set; }

16
src/OpenIddict.EntityFramework/OpenIddictEntityFrameworkBuilder.cs

@ -114,25 +114,15 @@ namespace Microsoft.Extensions.DependencyInjection
return Configure(options => options.DbContextType = type);
}
/// <summary>
/// Determines whether the specified object is equal to the current object.
/// </summary>
/// <param name="obj">The object to compare with the current object.</param>
/// <returns><c>true</c> if the specified object is equal to the current object; otherwise, false.</returns>
/// <inheritdoc/>
[EditorBrowsable(EditorBrowsableState.Never)]
public override bool Equals(object? obj) => base.Equals(obj);
/// <summary>
/// Serves as the default hash function.
/// </summary>
/// <returns>A hash code for the current object.</returns>
/// <inheritdoc/>
[EditorBrowsable(EditorBrowsableState.Never)]
public override int GetHashCode() => base.GetHashCode();
/// <summary>
/// Returns a string that represents the current object.
/// </summary>
/// <returns>A string that represents the current object.</returns>
/// <inheritdoc/>
[EditorBrowsable(EditorBrowsableState.Never)]
public override string? ToString() => base.ToString();
}

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

@ -414,6 +414,17 @@ namespace OpenIddict.EntityFramework
Authorizations.Include(authorization => authorization.Application), state).FirstOrDefaultAsync(cancellationToken);
}
/// <inheritdoc/>
public virtual ValueTask<DateTimeOffset?> GetCreationDateAsync(TAuthorization authorization, CancellationToken cancellationToken)
{
if (authorization == null)
{
throw new ArgumentNullException(nameof(authorization));
}
return new ValueTask<DateTimeOffset?>(authorization.CreationDate);
}
/// <inheritdoc/>
public virtual ValueTask<string?> GetIdAsync(TAuthorization authorization, CancellationToken cancellationToken)
{
@ -561,7 +572,7 @@ namespace OpenIddict.EntityFramework
}
/// <inheritdoc/>
public virtual async ValueTask PruneAsync(CancellationToken cancellationToken)
public virtual async ValueTask PruneAsync(DateTimeOffset threshold, CancellationToken cancellationToken)
{
// Note: Entity Framework 6.x doesn't support set-based deletes, which prevents removing
// entities in a single command without having to retrieve and materialize them first.
@ -597,10 +608,9 @@ namespace OpenIddict.EntityFramework
var authorizations =
await (from authorization in Authorizations.Include(authorization => authorization.Tokens)
where authorization.CreationDate < threshold
where authorization.Status != OpenIddictConstants.Statuses.Valid ||
(authorization.Type == OpenIddictConstants.AuthorizationTypes.AdHoc &&
!authorization.Tokens.Any(token => token.Status == OpenIddictConstants.Statuses.Valid &&
token.ExpirationDate > DateTimeOffset.UtcNow))
(authorization.Type == OpenIddictConstants.AuthorizationTypes.AdHoc && !authorization.Tokens.Any())
orderby authorization.Id
select authorization).Skip(offset).Take(1_000).ToListAsync(cancellationToken);
@ -614,7 +624,6 @@ namespace OpenIddict.EntityFramework
// repeatable read instead of serializable for performance reasons). In this
// case, the operation will fail, which is considered an acceptable risk.
Authorizations.RemoveRange(authorizations);
Tokens.RemoveRange(authorizations.SelectMany(authorization => authorization.Tokens));
try
{
@ -624,11 +633,7 @@ namespace OpenIddict.EntityFramework
catch (Exception exception)
{
if (exceptions == null)
{
exceptions = new List<Exception>(capacity: 1);
}
exceptions ??= new List<Exception>(capacity: 1);
exceptions.Add(exception);
}
}
@ -677,6 +682,20 @@ namespace OpenIddict.EntityFramework
}
}
/// <inheritdoc/>
public virtual ValueTask SetCreationDateAsync(TAuthorization authorization,
DateTimeOffset? date, CancellationToken cancellationToken)
{
if (authorization == null)
{
throw new ArgumentNullException(nameof(authorization));
}
authorization.CreationDate = date;
return default;
}
/// <inheritdoc/>
public virtual ValueTask SetPropertiesAsync(TAuthorization authorization,
ImmutableDictionary<string, JsonElement> properties, CancellationToken cancellationToken)

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

@ -21,6 +21,7 @@ using Microsoft.Extensions.Caching.Memory;
using Microsoft.Extensions.Options;
using OpenIddict.Abstractions;
using OpenIddict.EntityFramework.Models;
using static OpenIddict.Abstractions.OpenIddictConstants;
using SR = OpenIddict.Abstractions.OpenIddictResources;
namespace OpenIddict.EntityFramework
@ -551,7 +552,7 @@ namespace OpenIddict.EntityFramework
}
/// <inheritdoc/>
public virtual async ValueTask PruneAsync(CancellationToken cancellationToken)
public virtual async ValueTask PruneAsync(DateTimeOffset threshold, CancellationToken cancellationToken)
{
// Note: Entity Framework 6.x doesn't support set-based deletes, which prevents removing
// entities in a single command without having to retrieve and materialize them first.
@ -587,11 +588,14 @@ namespace OpenIddict.EntityFramework
// and thus prevent them from being concurrently modified outside this block.
using var transaction = CreateTransaction();
var tokens = await (from token in Tokens
where token.Status != OpenIddictConstants.Statuses.Valid ||
token.ExpirationDate < DateTimeOffset.UtcNow
orderby token.Id
select token).Skip(offset).Take(1_000).ToListAsync(cancellationToken);
var tokens = await
(from token in Tokens
where token.CreationDate < threshold
where (token.Status != Statuses.Inactive && token.Status != Statuses.Valid) ||
(token.Authorization != null && token.Authorization.Status != Statuses.Valid) ||
token.ExpirationDate < DateTimeOffset.UtcNow
orderby token.Id
select token).Skip(offset).Take(1_000).ToListAsync(cancellationToken);
if (tokens.Count == 0)
{
@ -608,11 +612,7 @@ namespace OpenIddict.EntityFramework
catch (Exception exception)
{
if (exceptions == null)
{
exceptions = new List<Exception>(capacity: 1);
}
exceptions ??= new List<Exception>(capacity: 1);
exceptions.Add(exception);
}
}

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

@ -50,6 +50,11 @@ namespace OpenIddict.EntityFrameworkCore.Models
/// </summary>
public virtual string? ConcurrencyToken { get; set; } = Guid.NewGuid().ToString();
/// <summary>
/// Gets or sets the creation date of the current authorization.
/// </summary>
public virtual DateTimeOffset? CreationDate { get; set; }
/// <summary>
/// Gets or sets the unique identifier associated with the current authorization.
/// </summary>

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

@ -55,12 +55,12 @@ namespace OpenIddict.EntityFrameworkCore.Models
public virtual string? ConcurrencyToken { get; set; } = Guid.NewGuid().ToString();
/// <summary>
/// Gets or sets the date on which the token will start to be considered valid.
/// Gets or sets the creation date of the current token.
/// </summary>
public virtual DateTimeOffset? CreationDate { get; set; }
/// <summary>
/// Gets or sets the date on which the token will no longer be considered valid.
/// Gets or sets the expiration date of the current token.
/// </summary>
public virtual DateTimeOffset? ExpirationDate { get; set; }

16
src/OpenIddict.EntityFrameworkCore/OpenIddictEntityFrameworkCoreBuilder.cs

@ -114,25 +114,15 @@ namespace Microsoft.Extensions.DependencyInjection
return Configure(options => options.DbContextType = type);
}
/// <summary>
/// Determines whether the specified object is equal to the current object.
/// </summary>
/// <param name="obj">The object to compare with the current object.</param>
/// <returns><c>true</c> if the specified object is equal to the current object; otherwise, false.</returns>
/// <inheritdoc/>
[EditorBrowsable(EditorBrowsableState.Never)]
public override bool Equals(object? obj) => base.Equals(obj);
/// <summary>
/// Serves as the default hash function.
/// </summary>
/// <returns>A hash code for the current object.</returns>
/// <inheritdoc/>
[EditorBrowsable(EditorBrowsableState.Never)]
public override int GetHashCode() => base.GetHashCode();
/// <summary>
/// Returns a string that represents the current object.
/// </summary>
/// <returns>A string that represents the current object.</returns>
/// <inheritdoc/>
[EditorBrowsable(EditorBrowsableState.Never)]
public override string? ToString() => base.ToString();
}

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

@ -478,6 +478,17 @@ namespace OpenIddict.EntityFrameworkCore
.AsTracking(), state).FirstOrDefaultAsync(cancellationToken);
}
/// <inheritdoc/>
public virtual ValueTask<DateTimeOffset?> GetCreationDateAsync(TAuthorization authorization, CancellationToken cancellationToken)
{
if (authorization == null)
{
throw new ArgumentNullException(nameof(authorization));
}
return new ValueTask<DateTimeOffset?>(authorization.CreationDate);
}
/// <inheritdoc/>
public virtual ValueTask<string?> GetIdAsync(TAuthorization authorization, CancellationToken cancellationToken)
{
@ -627,7 +638,7 @@ namespace OpenIddict.EntityFrameworkCore
}
/// <inheritdoc/>
public virtual async ValueTask PruneAsync(CancellationToken cancellationToken)
public virtual async ValueTask PruneAsync(DateTimeOffset threshold, CancellationToken cancellationToken)
{
// Note: Entity Framework Core doesn't support set-based deletes, which prevents removing
// entities in a single command without having to retrieve and materialize them first.
@ -673,10 +684,9 @@ namespace OpenIddict.EntityFrameworkCore
var authorizations =
await (from authorization in Authorizations.Include(authorization => authorization.Tokens).AsTracking()
where authorization.CreationDate < threshold
where authorization.Status != OpenIddictConstants.Statuses.Valid ||
(authorization.Type == OpenIddictConstants.AuthorizationTypes.AdHoc &&
!authorization.Tokens.Any(token => token.Status == OpenIddictConstants.Statuses.Valid &&
token.ExpirationDate > DateTimeOffset.UtcNow))
(authorization.Type == OpenIddictConstants.AuthorizationTypes.AdHoc && !authorization.Tokens.Any())
orderby authorization.Id
select authorization).Skip(offset).Take(1_000).ToListAsync(cancellationToken);
@ -690,7 +700,6 @@ namespace OpenIddict.EntityFrameworkCore
// repeatable read instead of serializable for performance reasons). In this
// case, the operation will fail, which is considered an acceptable risk.
Context.RemoveRange(authorizations);
Context.RemoveRange(authorizations.SelectMany(authorization => authorization.Tokens));
try
{
@ -700,11 +709,7 @@ namespace OpenIddict.EntityFrameworkCore
catch (Exception exception)
{
if (exceptions == null)
{
exceptions = new List<Exception>(capacity: 1);
}
exceptions ??= new List<Exception>(capacity: 1);
exceptions.Add(exception);
}
}
@ -759,6 +764,20 @@ namespace OpenIddict.EntityFrameworkCore
}
}
/// <inheritdoc/>
public virtual ValueTask SetCreationDateAsync(TAuthorization authorization,
DateTimeOffset? date, CancellationToken cancellationToken)
{
if (authorization == null)
{
throw new ArgumentNullException(nameof(authorization));
}
authorization.CreationDate = date;
return default;
}
/// <inheritdoc/>
public virtual ValueTask SetPropertiesAsync(TAuthorization authorization,
ImmutableDictionary<string, JsonElement> properties, CancellationToken cancellationToken)

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

@ -22,6 +22,7 @@ using Microsoft.Extensions.Caching.Memory;
using Microsoft.Extensions.Options;
using OpenIddict.Abstractions;
using OpenIddict.EntityFrameworkCore.Models;
using static OpenIddict.Abstractions.OpenIddictConstants;
using SR = OpenIddict.Abstractions.OpenIddictResources;
namespace OpenIddict.EntityFrameworkCore
@ -599,7 +600,7 @@ namespace OpenIddict.EntityFrameworkCore
}
/// <inheritdoc/>
public virtual async ValueTask PruneAsync(CancellationToken cancellationToken)
public virtual async ValueTask PruneAsync(DateTimeOffset threshold, CancellationToken cancellationToken)
{
// Note: Entity Framework Core doesn't support set-based deletes, which prevents removing
// entities in a single command without having to retrieve and materialize them first.
@ -645,11 +646,14 @@ namespace OpenIddict.EntityFrameworkCore
// and thus prevent them from being concurrently modified outside this block.
using var transaction = await CreateTransactionAsync();
var tokens = await (from token in Tokens.AsTracking()
where token.Status != OpenIddictConstants.Statuses.Valid ||
token.ExpirationDate < DateTimeOffset.UtcNow
orderby token.Id
select token).Skip(offset).Take(1_000).ToListAsync(cancellationToken);
var tokens = await
(from token in Tokens.AsTracking()
where token.CreationDate < threshold
where (token.Status != Statuses.Inactive && token.Status != Statuses.Valid) ||
(token.Authorization != null && token.Authorization.Status != Statuses.Valid) ||
token.ExpirationDate < DateTimeOffset.UtcNow
orderby token.Id
select token).Skip(offset).Take(1_000).ToListAsync(cancellationToken);
if (tokens.Count == 0)
{
@ -666,11 +670,7 @@ namespace OpenIddict.EntityFrameworkCore
catch (Exception exception)
{
if (exceptions == null)
{
exceptions = new List<Exception>(capacity: 1);
}
exceptions ??= new List<Exception>(capacity: 1);
exceptions.Add(exception);
}
}

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

@ -31,6 +31,11 @@ namespace OpenIddict.MongoDb.Models
[BsonElement("concurrency_token"), BsonIgnoreIfNull]
public virtual string? ConcurrencyToken { get; set; } = Guid.NewGuid().ToString();
/// <summary>
/// Gets or sets the creation date of the current authorization.
/// </summary>
public virtual DateTime? CreationDate { get; set; }
/// <summary>
/// Gets or sets the unique identifier associated with the current authorization.
/// </summary>

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

@ -36,13 +36,13 @@ namespace OpenIddict.MongoDb.Models
public virtual string? ConcurrencyToken { get; set; } = Guid.NewGuid().ToString();
/// <summary>
/// Gets or sets the date on which the token will start to be considered valid.
/// Gets or sets the creation date of the current token.
/// </summary>
[BsonElement("creation_date"), BsonIgnoreIfNull]
public virtual DateTime? CreationDate { get; set; }
/// <summary>
/// Gets or sets the date on which the token will no longer be considered valid.
/// Gets or sets the expiration date of the current token.
/// </summary>
[BsonElement("expiration_date"), BsonIgnoreIfNull]
public virtual DateTime? ExpirationDate { get; set; }

16
src/OpenIddict.MongoDb/OpenIddictMongoDbBuilder.cs

@ -174,25 +174,15 @@ namespace Microsoft.Extensions.DependencyInjection
return Configure(options => options.Database = database);
}
/// <summary>
/// Determines whether the specified object is equal to the current object.
/// </summary>
/// <param name="obj">The object to compare with the current object.</param>
/// <returns><c>true</c> if the specified object is equal to the current object; otherwise, false.</returns>
/// <inheritdoc/>
[EditorBrowsable(EditorBrowsableState.Never)]
public override bool Equals(object? obj) => base.Equals(obj);
/// <summary>
/// Serves as the default hash function.
/// </summary>
/// <returns>A hash code for the current object.</returns>
/// <inheritdoc/>
[EditorBrowsable(EditorBrowsableState.Never)]
public override int GetHashCode() => base.GetHashCode();
/// <summary>
/// Returns a string that represents the current object.
/// </summary>
/// <returns>A string that represents the current object.</returns>
/// <inheritdoc/>
[EditorBrowsable(EditorBrowsableState.Never)]
public override string? ToString() => base.ToString();
}

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

@ -361,6 +361,17 @@ namespace OpenIddict.MongoDb
return await ((IMongoQueryable<TResult>) query(collection.AsQueryable(), state)).FirstOrDefaultAsync(cancellationToken);
}
/// <inheritdoc/>
public virtual ValueTask<DateTimeOffset?> GetCreationDateAsync(TAuthorization authorization, CancellationToken cancellationToken)
{
if (authorization == null)
{
throw new ArgumentNullException(nameof(authorization));
}
return new ValueTask<DateTimeOffset?>(authorization.CreationDate);
}
/// <inheritdoc/>
public virtual ValueTask<string?> GetIdAsync(TAuthorization authorization, CancellationToken cancellationToken)
{
@ -503,7 +514,7 @@ namespace OpenIddict.MongoDb
}
/// <inheritdoc/>
public virtual async ValueTask PruneAsync(CancellationToken cancellationToken)
public virtual async ValueTask PruneAsync(DateTimeOffset threshold, CancellationToken cancellationToken)
{
var database = await Context.GetDatabaseAsync(cancellationToken);
var collection = database.GetCollection<TAuthorization>(Options.CurrentValue.AuthorizationsCollectionName);
@ -516,10 +527,9 @@ namespace OpenIddict.MongoDb
await (from authorization in collection.AsQueryable()
join token in database.GetCollection<OpenIddictMongoDbToken>(Options.CurrentValue.TokensCollectionName).AsQueryable()
on authorization.Id equals token.AuthorizationId into tokens
where authorization.CreationDate < threshold.UtcDateTime
where authorization.Status != OpenIddictConstants.Statuses.Valid ||
(authorization.Type == OpenIddictConstants.AuthorizationTypes.AdHoc &&
!tokens.Any(token => token.Status == OpenIddictConstants.Statuses.Valid &&
token.ExpirationDate > DateTime.UtcNow))
(authorization.Type == OpenIddictConstants.AuthorizationTypes.AdHoc && !tokens.Any())
select authorization.Id).ToListAsync(cancellationToken);
// Note: to avoid generating delete requests with very large filters, a buffer is used here and the
@ -527,10 +537,6 @@ namespace OpenIddict.MongoDb
foreach (var buffer in Buffer(identifiers.Take(50_000), 1_000))
{
await collection.DeleteManyAsync(authorization => buffer.Contains(authorization.Id));
// Delete the tokens associated with the pruned authorizations.
await database.GetCollection<OpenIddictMongoDbToken>(Options.CurrentValue.TokensCollectionName)
.DeleteManyAsync(token => buffer.Contains(token.AuthorizationId), cancellationToken);
}
static IEnumerable<List<TSource>> Buffer<TSource>(IEnumerable<TSource> source, int count)
@ -539,11 +545,7 @@ namespace OpenIddict.MongoDb
foreach (var element in source)
{
if (buffer == null)
{
buffer = new List<TSource>();
}
buffer ??= new List<TSource>(capacity: 1);
buffer.Add(element);
if (buffer.Count == count)
@ -583,6 +585,20 @@ namespace OpenIddict.MongoDb
return default;
}
/// <inheritdoc/>
public virtual ValueTask SetCreationDateAsync(TAuthorization authorization,
DateTimeOffset? date, CancellationToken cancellationToken)
{
if (authorization == null)
{
throw new ArgumentNullException(nameof(authorization));
}
authorization.CreationDate = date?.UtcDateTime;
return default;
}
/// <inheritdoc/>
public virtual ValueTask SetPropertiesAsync(TAuthorization authorization,
ImmutableDictionary<string, JsonElement> properties, CancellationToken cancellationToken)

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

@ -19,6 +19,7 @@ using MongoDB.Driver;
using MongoDB.Driver.Linq;
using OpenIddict.Abstractions;
using OpenIddict.MongoDb.Models;
using static OpenIddict.Abstractions.OpenIddictConstants;
using SR = OpenIddict.Abstractions.OpenIddictResources;
namespace OpenIddict.MongoDb
@ -529,13 +530,54 @@ namespace OpenIddict.MongoDb
}
/// <inheritdoc/>
public virtual async ValueTask PruneAsync(CancellationToken cancellationToken)
public virtual async ValueTask PruneAsync(DateTimeOffset threshold, CancellationToken cancellationToken)
{
var database = await Context.GetDatabaseAsync(cancellationToken);
var collection = database.GetCollection<TToken>(Options.CurrentValue.TokensCollectionName);
await collection.DeleteManyAsync(token => token.Status != OpenIddictConstants.Statuses.Valid ||
token.ExpirationDate < DateTime.UtcNow, cancellationToken);
// Note: directly deleting the resulting set of an aggregate query is not supported by MongoDB.
// To work around this limitation, the token identifiers are stored in an intermediate list
// and delete requests are sent to remove the documents corresponding to these identifiers.
var identifiers =
await (from token in collection.AsQueryable()
join authorization in database.GetCollection<OpenIddictMongoDbAuthorization>(Options.CurrentValue.AuthorizationsCollectionName).AsQueryable()
on token.AuthorizationId equals authorization.Id into authorizations
where token.CreationDate < threshold
where (token.Status != Statuses.Inactive && token.Status != Statuses.Valid) ||
token.ExpirationDate < DateTime.UtcNow ||
authorizations.Any(authorization => authorization.Status != Statuses.Valid)
select token.Id).ToListAsync(cancellationToken);
// Note: to avoid generating delete requests with very large filters, a buffer is used here and the
// maximum number of elements that can be removed by a single call to PruneAsync() is limited to 50000.
foreach (var buffer in Buffer(identifiers.Take(50_000), 1_000))
{
await collection.DeleteManyAsync(token => buffer.Contains(token.Id));
}
static IEnumerable<List<TSource>> Buffer<TSource>(IEnumerable<TSource> source, int count)
{
List<TSource>? buffer = null;
foreach (var element in source)
{
buffer ??= new List<TSource>(capacity: 1);
buffer.Add(element);
if (buffer.Count == count)
{
yield return buffer;
buffer = null;
}
}
if (buffer != null)
{
yield return buffer;
}
}
}
/// <inheritdoc/>

16
src/OpenIddict.Server.AspNetCore/OpenIddictServerAspNetCoreBuilder.cs

@ -196,25 +196,15 @@ namespace Microsoft.Extensions.DependencyInjection
return Configure(options => options.LogoutEndpointCachingPolicy = policy);
}
/// <summary>
/// Determines whether the specified object is equal to the current object.
/// </summary>
/// <param name="obj">The object to compare with the current object.</param>
/// <returns><c>true</c> if the specified object is equal to the current object; otherwise, false.</returns>
/// <inheritdoc/>
[EditorBrowsable(EditorBrowsableState.Never)]
public override bool Equals(object? obj) => base.Equals(obj);
/// <summary>
/// Serves as the default hash function.
/// </summary>
/// <returns>A hash code for the current object.</returns>
/// <inheritdoc/>
[EditorBrowsable(EditorBrowsableState.Never)]
public override int GetHashCode() => base.GetHashCode();
/// <summary>
/// Returns a string that represents the current object.
/// </summary>
/// <returns>A string that represents the current object.</returns>
/// <inheritdoc/>
[EditorBrowsable(EditorBrowsableState.Never)]
public override string? ToString() => base.ToString();
}

16
src/OpenIddict.Server.DataProtection/OpenIddictServerDataProtectionBuilder.cs

@ -114,25 +114,15 @@ namespace Microsoft.Extensions.DependencyInjection
public OpenIddictServerDataProtectionBuilder PreferDefaultUserCodeFormat()
=> Configure(options => options.PreferDefaultUserCodeFormat = true);
/// <summary>
/// Determines whether the specified object is equal to the current object.
/// </summary>
/// <param name="obj">The object to compare with the current object.</param>
/// <returns><c>true</c> if the specified object is equal to the current object; otherwise, false.</returns>
/// <inheritdoc/>
[EditorBrowsable(EditorBrowsableState.Never)]
public override bool Equals(object? obj) => base.Equals(obj);
/// <summary>
/// Serves as the default hash function.
/// </summary>
/// <returns>A hash code for the current object.</returns>
/// <inheritdoc/>
[EditorBrowsable(EditorBrowsableState.Never)]
public override int GetHashCode() => base.GetHashCode();
/// <summary>
/// Returns a string that represents the current object.
/// </summary>
/// <returns>A string that represents the current object.</returns>
/// <inheritdoc/>
[EditorBrowsable(EditorBrowsableState.Never)]
public override string? ToString() => base.ToString();
}

16
src/OpenIddict.Server.Owin/OpenIddictServerOwinBuilder.cs

@ -185,25 +185,15 @@ namespace Microsoft.Extensions.DependencyInjection
return Configure(options => options.LogoutEndpointCachingPolicy = policy);
}
/// <summary>
/// Determines whether the specified object is equal to the current object.
/// </summary>
/// <param name="obj">The object to compare with the current object.</param>
/// <returns><c>true</c> if the specified object is equal to the current object; otherwise, false.</returns>
/// <inheritdoc/>
[EditorBrowsable(EditorBrowsableState.Never)]
public override bool Equals(object? obj) => base.Equals(obj);
/// <summary>
/// Serves as the default hash function.
/// </summary>
/// <returns>A hash code for the current object.</returns>
/// <inheritdoc/>
[EditorBrowsable(EditorBrowsableState.Never)]
public override int GetHashCode() => base.GetHashCode();
/// <summary>
/// Returns a string that represents the current object.
/// </summary>
/// <returns>A string that represents the current object.</returns>
/// <inheritdoc/>
[EditorBrowsable(EditorBrowsableState.Never)]
public override string? ToString() => base.ToString();
}

38
src/OpenIddict.Server.Quartz/OpenIddictServerQuartzBuilder.cs

@ -52,15 +52,15 @@ namespace Microsoft.Extensions.DependencyInjection
/// Disables authorizations pruning.
/// </summary>
/// <returns>The <see cref="OpenIddictServerQuartzBuilder"/>.</returns>
public OpenIddictServerQuartzBuilder DisableAuthorizationsPruning()
=> Configure(options => options.DisableAuthorizationsPruning = true);
public OpenIddictServerQuartzBuilder DisableAuthorizationPruning()
=> Configure(options => options.DisableAuthorizationPruning = true);
/// <summary>
/// Disables tokens pruning.
/// </summary>
/// <returns>The <see cref="OpenIddictServerQuartzBuilder"/>.</returns>
public OpenIddictServerQuartzBuilder DisableTokensPruning()
=> Configure(options => options.DisableTokensPruning = true);
public OpenIddictServerQuartzBuilder DisableTokenPruning()
=> Configure(options => options.DisableTokenPruning = true);
/// <summary>
/// Sets the number of times a failed Quartz.NET job can be retried.
@ -77,6 +77,36 @@ namespace Microsoft.Extensions.DependencyInjection
return Configure(options => options.MaximumRefireCount = count);
}
/// <summary>
/// Sets the minimum lifespan authorizations must have to be pruned.
/// </summary>
/// <param name="lifespan">The minimum lifespan authorizations must have to be pruned.</param>
/// <returns>The <see cref="OpenIddictServerQuartzBuilder"/>.</returns>
public OpenIddictServerQuartzBuilder SetMinimumAuthorizationLifespan(TimeSpan lifespan)
{
if (lifespan < TimeSpan.FromMinutes(10))
{
throw new ArgumentOutOfRangeException(nameof(lifespan), SR.GetResourceString(SR.ID1279));
}
return Configure(options => options.MinimumAuthorizationLifespan = lifespan);
}
/// <summary>
/// Sets the minimum lifespan tokens must have to be pruned.
/// </summary>
/// <param name="lifespan">The minimum lifespan tokens must have to be pruned.</param>
/// <returns>The <see cref="OpenIddictServerQuartzBuilder"/>.</returns>
public OpenIddictServerQuartzBuilder SetMinimumTokenLifespan(TimeSpan lifespan)
{
if (lifespan < TimeSpan.FromMinutes(10))
{
throw new ArgumentOutOfRangeException(nameof(lifespan), SR.GetResourceString(SR.ID1279));
}
return Configure(options => options.MinimumTokenLifespan = lifespan);
}
/// <inheritdoc/>
[EditorBrowsable(EditorBrowsableState.Never)]
public override bool Equals(object? obj) => base.Equals(obj);

27
src/OpenIddict.Server.Quartz/OpenIddictServerQuartzJob.cs

@ -65,15 +65,12 @@ namespace OpenIddict.Server.Quartz
{
// Note: this background task is responsible of automatically removing orphaned tokens/authorizations
// (i.e tokens that are no longer valid and ad-hoc authorizations that have no valid tokens associated).
// Since ad-hoc authorizations and their associated tokens are removed as part of the same operation
// when they no longer have any token attached, it's more efficient to remove the authorizations first.
// Import: since tokens associated to ad-hoc authorizations are not removed as part of the same operation,
// the tokens MUST be deleted before removing the ad-hoc authorizations that no longer have any token.
// Note: the authorization/token managers MUST be resolved from the scoped provider
// as they depend on scoped stores that should be disposed as soon as possible.
if (!_options.CurrentValue.DisableAuthorizationsPruning)
if (!_options.CurrentValue.DisableTokenPruning)
{
var manager = scope.ServiceProvider.GetService<IOpenIddictAuthorizationManager>();
var manager = scope.ServiceProvider.GetService<IOpenIddictTokenManager>();
if (manager == null)
{
// Inform Quartz.NET that the triggers associated with this job should be removed,
@ -87,9 +84,11 @@ namespace OpenIddict.Server.Quartz
};
}
var threshold = DateTimeOffset.UtcNow - _options.CurrentValue.MinimumTokenLifespan;
try
{
await manager.PruneAsync(context.CancellationToken);
await manager.PruneAsync(threshold, context.CancellationToken);
}
// OutOfMemoryExceptions are treated as fatal errors and are always re-thrown as-is.
@ -113,7 +112,7 @@ namespace OpenIddict.Server.Quartz
// occurred while trying to prune the entities. In this case, add the inner exceptions to the collection.
catch (AggregateException exception)
{
exceptions ??= new List<Exception>(capacity: 1);
exceptions ??= new List<Exception>(capacity: exception.InnerExceptions.Count);
exceptions.AddRange(exception.InnerExceptions);
}
@ -126,9 +125,9 @@ namespace OpenIddict.Server.Quartz
}
}
if (!_options.CurrentValue.DisableTokensPruning)
if (!_options.CurrentValue.DisableAuthorizationPruning)
{
var manager = scope.ServiceProvider.GetService<IOpenIddictTokenManager>();
var manager = scope.ServiceProvider.GetService<IOpenIddictAuthorizationManager>();
if (manager == null)
{
// Inform Quartz.NET that the triggers associated with this job should be removed,
@ -142,9 +141,11 @@ namespace OpenIddict.Server.Quartz
};
}
var threshold = DateTimeOffset.UtcNow - _options.CurrentValue.MinimumAuthorizationLifespan;
try
{
await manager.PruneAsync(context.CancellationToken);
await manager.PruneAsync(threshold, context.CancellationToken);
}
// OutOfMemoryExceptions are treated as fatal errors and are always re-thrown as-is.
@ -168,7 +169,7 @@ namespace OpenIddict.Server.Quartz
// occurred while trying to prune the entities. In this case, add the inner exceptions to the collection.
catch (AggregateException exception)
{
exceptions ??= new List<Exception>(capacity: 1);
exceptions ??= new List<Exception>(capacity: exception.InnerExceptions.Count);
exceptions.AddRange(exception.InnerExceptions);
}

18
src/OpenIddict.Server.Quartz/OpenIddictServerQuartzOptions.cs

@ -4,6 +4,8 @@
* the license and the contributors participating to this project.
*/
using System;
namespace OpenIddict.Server.Quartz
{
/// <summary>
@ -14,17 +16,29 @@ namespace OpenIddict.Server.Quartz
/// <summary>
/// Gets or sets a boolean indicating whether authorizations pruning should be disabled.
/// </summary>
public bool DisableAuthorizationsPruning { get; set; }
public bool DisableAuthorizationPruning { get; set; }
/// <summary>
/// Gets or sets a boolean indicating whether tokens pruning should be disabled.
/// </summary>
public bool DisableTokensPruning { get; set; }
public bool DisableTokenPruning { get; set; }
/// <summary>
/// Gets or sets the number of times a failed Quartz.NET job can be retried.
/// By default, failed jobs are automatically retried twice after the initial failure.
/// </summary>
public int MaximumRefireCount { get; set; } = 2;
/// <summary>
/// Gets or sets the minimum lifespan authorizations must have to be pruned.
/// By default, this value is set to 14 days and cannot be less than 10 minutes.
/// </summary>
public TimeSpan MinimumAuthorizationLifespan { get; set; } = TimeSpan.FromDays(14);
/// <summary>
/// Gets or sets the minimum lifespan tokens must have to be pruned.
/// By default, this value is set to 14 days and cannot be less than 10 minutes.
/// </summary>
public TimeSpan MinimumTokenLifespan { get; set; } = TimeSpan.FromDays(14);
}
}

16
src/OpenIddict.Server/OpenIddictServerBuilder.cs

@ -1793,25 +1793,15 @@ namespace Microsoft.Extensions.DependencyInjection
public OpenIddictServerBuilder UseRollingRefreshTokens()
=> Configure(options => options.UseRollingRefreshTokens = true);
/// <summary>
/// Determines whether the specified object is equal to the current object.
/// </summary>
/// <param name="obj">The object to compare with the current object.</param>
/// <returns><c>true</c> if the specified object is equal to the current object; otherwise, false.</returns>
/// <inheritdoc/>
[EditorBrowsable(EditorBrowsableState.Never)]
public override bool Equals(object? obj) => base.Equals(obj);
/// <summary>
/// Serves as the default hash function.
/// </summary>
/// <returns>A hash code for the current object.</returns>
/// <inheritdoc/>
[EditorBrowsable(EditorBrowsableState.Never)]
public override int GetHashCode() => base.GetHashCode();
/// <summary>
/// Returns a string that represents the current object.
/// </summary>
/// <returns>A string that represents the current object.</returns>
/// <inheritdoc/>
[EditorBrowsable(EditorBrowsableState.Never)]
public override string? ToString() => base.ToString();
}

1
src/OpenIddict.Server/OpenIddictServerHandlers.cs

@ -1727,6 +1727,7 @@ namespace OpenIddict.Server
var descriptor = new OpenIddictAuthorizationDescriptor
{
CreationDate = DateTimeOffset.UtcNow,
Principal = context.Principal,
Status = Statuses.Valid,
Subject = context.Principal.GetClaim(Claims.Subject),

16
src/OpenIddict.Validation.AspNetCore/OpenIddictValidationAspNetCoreBuilder.cs

@ -63,25 +63,15 @@ namespace Microsoft.Extensions.DependencyInjection
return Configure(options => options.Realm = realm);
}
/// <summary>
/// Determines whether the specified object is equal to the current object.
/// </summary>
/// <param name="obj">The object to compare with the current object.</param>
/// <returns><c>true</c> if the specified object is equal to the current object; otherwise, false.</returns>
/// <inheritdoc/>
[EditorBrowsable(EditorBrowsableState.Never)]
public override bool Equals(object? obj) => base.Equals(obj);
/// <summary>
/// Serves as the default hash function.
/// </summary>
/// <returns>A hash code for the current object.</returns>
/// <inheritdoc/>
[EditorBrowsable(EditorBrowsableState.Never)]
public override int GetHashCode() => base.GetHashCode();
/// <summary>
/// Returns a string that represents the current object.
/// </summary>
/// <returns>A string that represents the current object.</returns>
/// <inheritdoc/>
[EditorBrowsable(EditorBrowsableState.Never)]
public override string? ToString() => base.ToString();
}

16
src/OpenIddict.Validation.DataProtection/OpenIddictValidationDataProtectionBuilder.cs

@ -79,25 +79,15 @@ namespace Microsoft.Extensions.DependencyInjection
return Configure(options => options.Formatter = formatter);
}
/// <summary>
/// Determines whether the specified object is equal to the current object.
/// </summary>
/// <param name="obj">The object to compare with the current object.</param>
/// <returns><c>true</c> if the specified object is equal to the current object; otherwise, false.</returns>
/// <inheritdoc/>
[EditorBrowsable(EditorBrowsableState.Never)]
public override bool Equals(object? obj) => base.Equals(obj);
/// <summary>
/// Serves as the default hash function.
/// </summary>
/// <returns>A hash code for the current object.</returns>
/// <inheritdoc/>
[EditorBrowsable(EditorBrowsableState.Never)]
public override int GetHashCode() => base.GetHashCode();
/// <summary>
/// Returns a string that represents the current object.
/// </summary>
/// <returns>A string that represents the current object.</returns>
/// <inheritdoc/>
[EditorBrowsable(EditorBrowsableState.Never)]
public override string? ToString() => base.ToString();
}

16
src/OpenIddict.Validation.Owin/OpenIddictValidationOwinBuilder.cs

@ -79,25 +79,15 @@ namespace Microsoft.Extensions.DependencyInjection
return Configure(options => options.Realm = realm);
}
/// <summary>
/// Determines whether the specified object is equal to the current object.
/// </summary>
/// <param name="obj">The object to compare with the current object.</param>
/// <returns><c>true</c> if the specified object is equal to the current object; otherwise, false.</returns>
/// <inheritdoc/>
[EditorBrowsable(EditorBrowsableState.Never)]
public override bool Equals(object? obj) => base.Equals(obj);
/// <summary>
/// Serves as the default hash function.
/// </summary>
/// <returns>A hash code for the current object.</returns>
/// <inheritdoc/>
[EditorBrowsable(EditorBrowsableState.Never)]
public override int GetHashCode() => base.GetHashCode();
/// <summary>
/// Returns a string that represents the current object.
/// </summary>
/// <returns>A string that represents the current object.</returns>
/// <inheritdoc/>
[EditorBrowsable(EditorBrowsableState.Never)]
public override string? ToString() => base.ToString();
}

16
src/OpenIddict.Validation.ServerIntegration/OpenIddictValidationServerIntegrationBuilder.cs

@ -46,25 +46,15 @@ namespace Microsoft.Extensions.DependencyInjection
return this;
}
/// <summary>
/// Determines whether the specified object is equal to the current object.
/// </summary>
/// <param name="obj">The object to compare with the current object.</param>
/// <returns><c>true</c> if the specified object is equal to the current object; otherwise, false.</returns>
/// <inheritdoc/>
[EditorBrowsable(EditorBrowsableState.Never)]
public override bool Equals(object? obj) => base.Equals(obj);
/// <summary>
/// Serves as the default hash function.
/// </summary>
/// <returns>A hash code for the current object.</returns>
/// <inheritdoc/>
[EditorBrowsable(EditorBrowsableState.Never)]
public override int GetHashCode() => base.GetHashCode();
/// <summary>
/// Returns a string that represents the current object.
/// </summary>
/// <returns>A string that represents the current object.</returns>
/// <inheritdoc/>
[EditorBrowsable(EditorBrowsableState.Never)]
public override string? ToString() => base.ToString();
}

16
src/OpenIddict.Validation.SystemNetHttp/OpenIddictValidationSystemNetHttpBuilder.cs

@ -56,25 +56,15 @@ namespace Microsoft.Extensions.DependencyInjection
public OpenIddictValidationSystemNetHttpBuilder SetHttpErrorPolicy(IAsyncPolicy<HttpResponseMessage> policy)
=> Configure(options => options.HttpErrorPolicy = policy);
/// <summary>
/// Determines whether the specified object is equal to the current object.
/// </summary>
/// <param name="obj">The object to compare with the current object.</param>
/// <returns><c>true</c> if the specified object is equal to the current object; otherwise, false.</returns>
/// <inheritdoc/>
[EditorBrowsable(EditorBrowsableState.Never)]
public override bool Equals(object? obj) => base.Equals(obj);
/// <summary>
/// Serves as the default hash function.
/// </summary>
/// <returns>A hash code for the current object.</returns>
/// <inheritdoc/>
[EditorBrowsable(EditorBrowsableState.Never)]
public override int GetHashCode() => base.GetHashCode();
/// <summary>
/// Returns a string that represents the current object.
/// </summary>
/// <returns>A string that represents the current object.</returns>
/// <inheritdoc/>
[EditorBrowsable(EditorBrowsableState.Never)]
public override string? ToString() => base.ToString();
}

16
src/OpenIddict.Validation/OpenIddictValidationBuilder.cs

@ -504,25 +504,15 @@ namespace Microsoft.Extensions.DependencyInjection
public OpenIddictValidationBuilder UseIntrospection()
=> Configure(options => options.ValidationType = OpenIddictValidationType.Introspection);
/// <summary>
/// Determines whether the specified object is equal to the current object.
/// </summary>
/// <param name="obj">The object to compare with the current object.</param>
/// <returns><c>true</c> if the specified object is equal to the current object; otherwise, false.</returns>
/// <inheritdoc/>
[EditorBrowsable(EditorBrowsableState.Never)]
public override bool Equals(object? obj) => base.Equals(obj);
/// <summary>
/// Serves as the default hash function.
/// </summary>
/// <returns>A hash code for the current object.</returns>
/// <inheritdoc/>
[EditorBrowsable(EditorBrowsableState.Never)]
public override int GetHashCode() => base.GetHashCode();
/// <summary>
/// Returns a string that represents the current object.
/// </summary>
/// <returns>A string that represents the current object.</returns>
/// <inheritdoc/>
[EditorBrowsable(EditorBrowsableState.Never)]
public override string? ToString() => base.ToString();
}

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

@ -3928,6 +3928,7 @@ namespace OpenIddict.Server.IntegrationTests
Mock.Get(manager).Verify(manager => manager.CreateAsync(
It.Is<OpenIddictAuthorizationDescriptor>(descriptor =>
descriptor.ApplicationId == "3E228451-1555-46F7-A471-951EFBA23A56" &&
descriptor.CreationDate != null &&
descriptor.Subject == "Bob le Magnifique" &&
descriptor.Type == AuthorizationTypes.AdHoc),
It.IsAny<CancellationToken>()), Times.Once());

72
test/OpenIddict.Server.Quartz.Tests/OpenIddictServerQuartzBuilderTests.cs

@ -50,35 +50,35 @@ namespace OpenIddict.Server.Quartz.Tests
}
[Fact]
public void DisableAuthorizationsPruning_AuthorizationsPruningIsDisabled()
public void DisableAuthorizationPruning_AuthorizationPruningIsDisabled()
{
// Arrange
var services = CreateServices();
var builder = CreateBuilder(services);
// Act
builder.DisableAuthorizationsPruning();
builder.DisableAuthorizationPruning();
var options = GetOptions(services);
// Assert
Assert.True(options.DisableAuthorizationsPruning);
Assert.True(options.DisableAuthorizationPruning);
}
[Fact]
public void DisableTokensPruning_TokensPruningIsDisabled()
public void DisableTokenPruning_TokenPruningIsDisabled()
{
// Arrange
var services = CreateServices();
var builder = CreateBuilder(services);
// Act
builder.DisableTokensPruning();
builder.DisableTokenPruning();
var options = GetOptions(services);
// Assert
Assert.True(options.DisableTokensPruning);
Assert.True(options.DisableTokenPruning);
}
[Fact]
@ -111,6 +111,66 @@ namespace OpenIddict.Server.Quartz.Tests
Assert.Equal(42, options.MaximumRefireCount);
}
[Fact]
public void SetMinimumAuthorizationLifespan_ThrowsAnExceptionForNegativeLifespan()
{
// Arrange
var services = CreateServices();
var builder = CreateBuilder(services);
// Act and assert
var exception = Assert.Throws<ArgumentOutOfRangeException>(() => builder.SetMinimumAuthorizationLifespan(TimeSpan.FromSeconds(-1)));
Assert.Equal("lifespan", exception.ParamName);
Assert.StartsWith(SR.GetResourceString(SR.ID1279), exception.Message);
}
[Fact]
public void SetMinimumAuthorizationLifespan_MinimumAuthorizationLifespanIsSet()
{
// Arrange
var services = CreateServices();
var builder = CreateBuilder(services);
// Act
builder.SetMinimumAuthorizationLifespan(TimeSpan.FromDays(42));
var options = GetOptions(services);
// Assert
Assert.Equal(42, options.MinimumAuthorizationLifespan.TotalDays);
}
[Fact]
public void SetMinimumTokenLifespan_ThrowsAnExceptionForNegativeLifespan()
{
// Arrange
var services = CreateServices();
var builder = CreateBuilder(services);
// Act and assert
var exception = Assert.Throws<ArgumentOutOfRangeException>(() => builder.SetMinimumTokenLifespan(TimeSpan.FromSeconds(-1)));
Assert.Equal("lifespan", exception.ParamName);
Assert.StartsWith(SR.GetResourceString(SR.ID1279), exception.Message);
}
[Fact]
public void SetMinimumTokenLifespan_MinimumTokenLifespanIsSet()
{
// Arrange
var services = CreateServices();
var builder = CreateBuilder(services);
// Act
builder.SetMinimumTokenLifespan(TimeSpan.FromDays(42));
var options = GetOptions(services);
// Assert
Assert.Equal(42, options.MinimumTokenLifespan.TotalDays);
}
private static IServiceCollection CreateServices()
=> new ServiceCollection().AddOptions();

84
test/OpenIddict.Server.Quartz.Tests/OpenIddictServerQuartzJobTests.cs

@ -46,57 +46,58 @@ namespace OpenIddict.Server.Quartz.Tests
}
[Fact]
public async Task Execute_IgnoresPruningWhenAuthorizationsPruningIsDisabled()
public async Task Execute_IgnoresPruningWhenTokenPruningIsDisabled()
{
// Arrange
var manager = new Mock<IOpenIddictAuthorizationManager>();
var provider = Mock.Of<IServiceProvider>(provider =>
provider.GetService(typeof(IOpenIddictAuthorizationManager)) == manager.Object &&
provider.GetService(typeof(IOpenIddictTokenManager)) == Mock.Of<IOpenIddictTokenManager>());
provider.GetService(typeof(IOpenIddictAuthorizationManager)) == Mock.Of<IOpenIddictAuthorizationManager>() &&
provider.GetService(typeof(IOpenIddictTokenManager)) == manager.Object);
var job = CreateJob(provider, new OpenIddictServerQuartzOptions
{
DisableAuthorizationsPruning = true
DisableTokenPruning = true
});
// Act
await job.Execute(Mock.Of<IJobExecutionContext>());
// Assert
manager.Verify(manager => manager.PruneAsync(It.IsAny<CancellationToken>()), Times.Never());
manager.Verify(manager => manager.PruneAsync(It.IsAny<DateTimeOffset>(), It.IsAny<CancellationToken>()), Times.Never());
}
[Fact]
public async Task Execute_IgnoresPruningWhenTokensPruningIsDisabled()
public async Task Execute_IgnoresPruningWhenAuthorizationPruningIsDisabled()
{
// Arrange
var manager = new Mock<IOpenIddictAuthorizationManager>();
var provider = Mock.Of<IServiceProvider>(provider =>
provider.GetService(typeof(IOpenIddictAuthorizationManager)) == Mock.Of<IOpenIddictAuthorizationManager>() &&
provider.GetService(typeof(IOpenIddictTokenManager)) == manager.Object);
provider.GetService(typeof(IOpenIddictAuthorizationManager)) == manager.Object &&
provider.GetService(typeof(IOpenIddictTokenManager)) == Mock.Of<IOpenIddictTokenManager>());
var job = CreateJob(provider, new OpenIddictServerQuartzOptions
{
DisableTokensPruning = true
DisableAuthorizationPruning = true
});
// Act
await job.Execute(Mock.Of<IJobExecutionContext>());
// Assert
manager.Verify(manager => manager.PruneAsync(It.IsAny<CancellationToken>()), Times.Never());
manager.Verify(manager => manager.PruneAsync(It.IsAny<DateTimeOffset>(), It.IsAny<CancellationToken>()), Times.Never());
}
[Fact]
public async Task Execute_UnschedulesTriggersWhenAuthorizationManagerIsMissing()
public async Task Execute_UnschedulesTriggersWhenTokenManagerIsMissing()
{
// Arrange
var provider = Mock.Of<IServiceProvider>(provider =>
provider.GetService(typeof(IOpenIddictAuthorizationManager)) == null);
provider.GetService(typeof(IOpenIddictAuthorizationManager)) == Mock.Of<IOpenIddictAuthorizationManager>() &&
provider.GetService(typeof(IOpenIddictTokenManager)) == null);
var job = CreateJob(provider);
@ -112,12 +113,11 @@ namespace OpenIddict.Server.Quartz.Tests
}
[Fact]
public async Task Execute_UnschedulesTriggersWhenTokenManagerIsMissing()
public async Task Execute_UnschedulesTriggersWhenAuthorizationManagerIsMissing()
{
// Arrange
var provider = Mock.Of<IServiceProvider>(provider =>
provider.GetService(typeof(IOpenIddictAuthorizationManager)) == Mock.Of<IOpenIddictAuthorizationManager>() &&
provider.GetService(typeof(IOpenIddictTokenManager)) == null);
provider.GetService(typeof(IOpenIddictAuthorizationManager)) == null);
var job = CreateJob(provider);
@ -133,16 +133,16 @@ namespace OpenIddict.Server.Quartz.Tests
}
[Fact]
public async Task Execute_RethrowsOutOfMemoryExceptionsThrownDuringAuthorizationsPruning()
public async Task Execute_RethrowsOutOfMemoryExceptionsThrownDuringTokenPruning()
{
// Arrange
var manager = new Mock<IOpenIddictAuthorizationManager>();
manager.Setup(manager => manager.PruneAsync(It.IsAny<CancellationToken>()))
var manager = new Mock<IOpenIddictTokenManager>();
manager.Setup(manager => manager.PruneAsync(It.IsAny<DateTimeOffset>(), It.IsAny<CancellationToken>()))
.Throws(new OutOfMemoryException());
var provider = Mock.Of<IServiceProvider>(provider =>
provider.GetService(typeof(IOpenIddictAuthorizationManager)) == manager.Object &&
provider.GetService(typeof(IOpenIddictTokenManager)) == Mock.Of<IOpenIddictTokenManager>());
provider.GetService(typeof(IOpenIddictAuthorizationManager)) == Mock.Of<IOpenIddictAuthorizationManager>() &&
provider.GetService(typeof(IOpenIddictTokenManager)) == manager.Object);
var job = CreateJob(provider);
@ -151,16 +151,16 @@ namespace OpenIddict.Server.Quartz.Tests
}
[Fact]
public async Task Execute_RethrowsOutOfMemoryExceptionsThrownDuringTokensPruning()
public async Task Execute_RethrowsOutOfMemoryExceptionsThrownDuringAuthorizationPruning()
{
// Arrange
var manager = new Mock<IOpenIddictTokenManager>();
manager.Setup(manager => manager.PruneAsync(It.IsAny<CancellationToken>()))
var manager = new Mock<IOpenIddictAuthorizationManager>();
manager.Setup(manager => manager.PruneAsync(It.IsAny<DateTimeOffset>(), It.IsAny<CancellationToken>()))
.Throws(new OutOfMemoryException());
var provider = Mock.Of<IServiceProvider>(provider =>
provider.GetService(typeof(IOpenIddictAuthorizationManager)) == Mock.Of<IOpenIddictAuthorizationManager>() &&
provider.GetService(typeof(IOpenIddictTokenManager)) == manager.Object);
provider.GetService(typeof(IOpenIddictAuthorizationManager)) == manager.Object &&
provider.GetService(typeof(IOpenIddictTokenManager)) == Mock.Of<IOpenIddictTokenManager>());
var job = CreateJob(provider);
@ -169,18 +169,18 @@ namespace OpenIddict.Server.Quartz.Tests
}
[Fact]
public async Task Execute_DisablesRefiringWhenJobIsCanceledDuringAuthorizationsPruning()
public async Task Execute_DisablesRefiringWhenJobIsCanceledDuringTokenPruning()
{
// Arrange
var token = new CancellationToken(canceled: true);
var manager = new Mock<IOpenIddictAuthorizationManager>();
manager.Setup(manager => manager.PruneAsync(It.IsAny<CancellationToken>()))
var manager = new Mock<IOpenIddictTokenManager>();
manager.Setup(manager => manager.PruneAsync(It.IsAny<DateTimeOffset>(), It.IsAny<CancellationToken>()))
.Throws(new OperationCanceledException(token));
var provider = Mock.Of<IServiceProvider>(provider =>
provider.GetService(typeof(IOpenIddictAuthorizationManager)) == manager.Object &&
provider.GetService(typeof(IOpenIddictTokenManager)) == Mock.Of<IOpenIddictTokenManager>());
provider.GetService(typeof(IOpenIddictAuthorizationManager)) == Mock.Of<IOpenIddictAuthorizationManager>() &&
provider.GetService(typeof(IOpenIddictTokenManager)) == manager.Object);
var context = Mock.Of<IJobExecutionContext>(context => context.CancellationToken == token);
@ -191,22 +191,22 @@ namespace OpenIddict.Server.Quartz.Tests
Assert.False(exception.RefireImmediately);
manager.Verify(manager => manager.PruneAsync(It.IsAny<CancellationToken>()), Times.Once());
manager.Verify(manager => manager.PruneAsync(It.IsAny<DateTimeOffset>(), It.IsAny<CancellationToken>()), Times.Once());
}
[Fact]
public async Task Execute_DisablesRefiringWhenJobIsCanceledDuringTokensPruning()
public async Task Execute_DisablesRefiringWhenJobIsCanceledDuringAuthorizationPruning()
{
// Arrange
var token = new CancellationToken(canceled: true);
var manager = new Mock<IOpenIddictTokenManager>();
manager.Setup(manager => manager.PruneAsync(It.IsAny<CancellationToken>()))
var manager = new Mock<IOpenIddictAuthorizationManager>();
manager.Setup(manager => manager.PruneAsync(It.IsAny<DateTimeOffset>(), It.IsAny<CancellationToken>()))
.Throws(new OperationCanceledException(token));
var provider = Mock.Of<IServiceProvider>(provider =>
provider.GetService(typeof(IOpenIddictAuthorizationManager)) == Mock.Of<IOpenIddictAuthorizationManager>() &&
provider.GetService(typeof(IOpenIddictTokenManager)) == manager.Object);
provider.GetService(typeof(IOpenIddictAuthorizationManager)) == manager.Object &&
provider.GetService(typeof(IOpenIddictTokenManager)) == Mock.Of<IOpenIddictTokenManager>());
var context = Mock.Of<IJobExecutionContext>(context => context.CancellationToken == token);
@ -217,7 +217,7 @@ namespace OpenIddict.Server.Quartz.Tests
Assert.False(exception.RefireImmediately);
manager.Verify(manager => manager.PruneAsync(It.IsAny<CancellationToken>()), Times.Once());
manager.Verify(manager => manager.PruneAsync(It.IsAny<DateTimeOffset>(), It.IsAny<CancellationToken>()), Times.Once());
}
[Fact]
@ -247,7 +247,7 @@ namespace OpenIddict.Server.Quartz.Tests
static IOpenIddictAuthorizationManager CreateAuthorizationManager(Exception exception)
{
var mock = new Mock<IOpenIddictAuthorizationManager>();
mock.Setup(manager => manager.PruneAsync(It.IsAny<CancellationToken>()))
mock.Setup(manager => manager.PruneAsync(It.IsAny<DateTimeOffset>(), It.IsAny<CancellationToken>()))
.Throws(exception);
return mock.Object;
@ -256,7 +256,7 @@ namespace OpenIddict.Server.Quartz.Tests
static IOpenIddictTokenManager CreateTokenManager(Exception exception)
{
var mock = new Mock<IOpenIddictTokenManager>();
mock.Setup(manager => manager.PruneAsync(It.IsAny<CancellationToken>()))
mock.Setup(manager => manager.PruneAsync(It.IsAny<DateTimeOffset>(), It.IsAny<CancellationToken>()))
.Throws(exception);
return mock.Object;
@ -294,7 +294,7 @@ namespace OpenIddict.Server.Quartz.Tests
static IOpenIddictAuthorizationManager CreateAuthorizationManager(Exception exception)
{
var mock = new Mock<IOpenIddictAuthorizationManager>();
mock.Setup(manager => manager.PruneAsync(It.IsAny<CancellationToken>()))
mock.Setup(manager => manager.PruneAsync(It.IsAny<DateTimeOffset>(), It.IsAny<CancellationToken>()))
.Throws(exception);
return mock.Object;
@ -303,7 +303,7 @@ namespace OpenIddict.Server.Quartz.Tests
static IOpenIddictTokenManager CreateTokenManager(Exception exception)
{
var mock = new Mock<IOpenIddictTokenManager>();
mock.Setup(manager => manager.PruneAsync(It.IsAny<CancellationToken>()))
mock.Setup(manager => manager.PruneAsync(It.IsAny<DateTimeOffset>(), It.IsAny<CancellationToken>()))
.Throws(exception);
return mock.Object;
@ -315,7 +315,7 @@ namespace OpenIddict.Server.Quartz.Tests
{
// Arrange
var manager = new Mock<IOpenIddictAuthorizationManager>();
manager.Setup(manager => manager.PruneAsync(It.IsAny<CancellationToken>()))
manager.Setup(manager => manager.PruneAsync(It.IsAny<DateTimeOffset>(), It.IsAny<CancellationToken>()))
.Throws(new ApplicationException());
var provider = Mock.Of<IServiceProvider>(provider =>

Loading…
Cancel
Save