diff --git a/Directory.Build.targets b/Directory.Build.targets index fb986cc61..46dfd60bb 100644 --- a/Directory.Build.targets +++ b/Directory.Build.targets @@ -24,17 +24,17 @@ 12.0 + $([MSBuild]::VersionGreaterThanOrEquals($(TargetPlatformVersion), '12.2'))) ">12.2 13.1 + $([MSBuild]::VersionGreaterThanOrEquals($(TargetPlatformVersion), '15.0'))) ">15.0 10.15 + $([MSBuild]::VersionGreaterThanOrEquals($(TargetPlatformVersion), '12.0'))) ">12.0 - + @@ -103,7 +103,7 @@ - + @@ -160,7 +160,7 @@ - + @@ -206,7 +206,7 @@ - + @@ -228,31 +228,31 @@ - - - + + + - - - - - - + + + + + + - + - - + + @@ -274,32 +274,32 @@ - + - + - - - - - + + + + + - - + + - + @@ -321,57 +321,57 @@ - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + - - - - - - + + + + + + - - - - - - - - - - - + + + + + + + + + + + - + Sébastien RosSchmitt ChristianSebastian StehleCommunicatie CockpitJasmin SavardDigitalOps Co. Ltd.EYERIDE Fleet Management SystemJulien DebacheStian HåveRavindu LiyanapathiranaHieronymusBlazeAkhan ZhakiyanovBarry DorransDevQ S.r.l.GrégoireForterroMarcelJens WillmerBlauhaus Technology (Pty) LtdJan TrejbalAviationexam s.r.o.MonoforRatiodata SEDennis van ZettenJeroenElfsterLombiq Technologies Ltd.PureBlazorAndrew BabbittKarl Schrieksoftaware gmbhSingular SystemsSCP-srlRealisable Software +Sébastien RosSchmitt ChristianSebastian StehleCommunicatie CockpitJasmin SavardDigitalOps Co. Ltd.EYERIDE Fleet Management SystemJulien DebacheStian HåveRavindu LiyanapathiranaHieronymusBlazeAkhan ZhakiyanovBarry DorransDevQ S.r.l.GrégoireForterroMarcelJens WillmerBlauhaus Technology (Pty) LtdJan TrejbalAviationexam s.r.o.MonoforRatiodata SEDennis van ZettenJeroenElfsterLombiq Technologies Ltd.PureBlazorAndrew Babbittsoftaware gmbhSingular SystemsSCP-srlRealisable Software -------------- diff --git a/WorkloadRollback.json b/WorkloadRollback.json index 53eb2e595..9fa723978 100644 --- a/WorkloadRollback.json +++ b/WorkloadRollback.json @@ -1,17 +1,17 @@ { - "microsoft.net.sdk.android": "35.0.0-rc.2.152/9.0.100-rc.2", - "microsoft.net.sdk.ios": "18.0.9600-net9-rc2/9.0.100-rc.2", - "microsoft.net.sdk.maccatalyst": "18.0.9600-net9-rc2/9.0.100-rc.2", - "microsoft.net.sdk.macos": "15.0.9600-net9-rc2/9.0.100-rc.2", - "microsoft.net.sdk.maui": "9.0.0-rc.2.24503.2/9.0.100-rc.2", - "microsoft.net.sdk.tvos": "18.0.9600-net9-rc2/9.0.100-rc.2", - "microsoft.net.workload.mono.toolchain.current": "9.0.0-rc.2.24473.5/9.0.100-rc.2", - "microsoft.net.workload.emscripten.current": "9.0.0-rc.2.24468.8/9.0.100-rc.2", - "microsoft.net.workload.emscripten.net6": "9.0.0-rc.2.24468.8/9.0.100-rc.2", - "microsoft.net.workload.emscripten.net7": "9.0.0-rc.2.24468.8/9.0.100-rc.2", - "microsoft.net.workload.emscripten.net8": "9.0.0-rc.2.24468.8/9.0.100-rc.2", - "microsoft.net.workload.mono.toolchain.net6": "9.0.0-rc.2.24473.5/9.0.100-rc.2", - "microsoft.net.workload.mono.toolchain.net7": "9.0.0-rc.2.24473.5/9.0.100-rc.2", - "microsoft.net.workload.mono.toolchain.net8": "9.0.0-rc.2.24473.5/9.0.100-rc.2", - "microsoft.net.sdk.aspire": "8.2.0/8.0.100" + "microsoft.net.sdk.android": "35.0.7/9.0.100", + "microsoft.net.sdk.ios": "18.0.9617/9.0.100", + "microsoft.net.sdk.maccatalyst": "18.0.9617/9.0.100", + "microsoft.net.sdk.macos": "15.0.9617/9.0.100", + "microsoft.net.sdk.maui": "9.0.0/9.0.100", + "microsoft.net.sdk.tvos": "18.0.9617/9.0.100", + "microsoft.net.workload.mono.toolchain.current": "9.0.0/9.0.100", + "microsoft.net.workload.emscripten.current": "9.0.0/9.0.100", + "microsoft.net.workload.emscripten.net6": "9.0.0/9.0.100", + "microsoft.net.workload.emscripten.net7": "9.0.0/9.0.100", + "microsoft.net.workload.emscripten.net8": "9.0.0/9.0.100", + "microsoft.net.workload.mono.toolchain.net6": "9.0.0/9.0.100", + "microsoft.net.workload.mono.toolchain.net7": "9.0.0/9.0.100", + "microsoft.net.workload.mono.toolchain.net8": "9.0.0/9.0.100", + "microsoft.net.sdk.aspire": "8.2.2/8.0.100" } diff --git a/eng/Versions.props b/eng/Versions.props index 7f7aa0e4e..a00cc94a0 100644 --- a/eng/Versions.props +++ b/eng/Versions.props @@ -5,9 +5,9 @@ 0 0 $(MajorVersion).$(MinorVersion).$(PatchVersion) - preview3 + preview5 - Preview 3 + Preview 5 false release true diff --git a/global.json b/global.json index a58515918..4f305b4ae 100644 --- a/global.json +++ b/global.json @@ -1,17 +1,17 @@ { "sdk": { - "version": "9.0.100-rc.2.24474.11", + "version": "9.0.100", "allowPrerelease": true, "rollForward": "major" }, "tools": { - "dotnet": "9.0.100-rc.2.24474.11", + "dotnet": "9.0.100", "runtimes": { "aspnetcore": [ - "6.0.35", - "8.0.10" + "6.0.36", + "8.0.11" ] } }, diff --git a/sandbox/OpenIddict.Sandbox.AspNet.Server/Startup.cs b/sandbox/OpenIddict.Sandbox.AspNet.Server/Startup.cs index 287bfc7cd..ad53f9781 100644 --- a/sandbox/OpenIddict.Sandbox.AspNet.Server/Startup.cs +++ b/sandbox/OpenIddict.Sandbox.AspNet.Server/Startup.cs @@ -201,7 +201,7 @@ await manager.CreateAsync(new OpenIddictApplicationDescriptor ClientId = "mvc", ClientSecret = "901564A5-E7FE-42CB-B10D-61EF6A8F3654", ClientType = ClientTypes.Confidential, - ConsentType = ConsentTypes.Explicit, + ConsentType = ConsentTypes.Systematic, DisplayName = "MVC client application", RedirectUris = { diff --git a/sandbox/OpenIddict.Sandbox.AspNetCore.Server/Worker.cs b/sandbox/OpenIddict.Sandbox.AspNetCore.Server/Worker.cs index 6ad635fcf..f4d44f2fa 100644 --- a/sandbox/OpenIddict.Sandbox.AspNetCore.Server/Worker.cs +++ b/sandbox/OpenIddict.Sandbox.AspNetCore.Server/Worker.cs @@ -152,7 +152,7 @@ await manager.CreateAsync(new OpenIddictApplicationDescriptor ClientId = "mvc", ClientSecret = "901564A5-E7FE-42CB-B10D-61EF6A8F3654", ClientType = ClientTypes.Confidential, - ConsentType = ConsentTypes.Explicit, + ConsentType = ConsentTypes.Systematic, DisplayName = "MVC client application", DisplayNames = { diff --git a/src/OpenIddict.Abstractions/Caches/IOpenIddictAuthorizationCache.cs b/src/OpenIddict.Abstractions/Caches/IOpenIddictAuthorizationCache.cs index a397ef6d2..f87fcefd7 100644 --- a/src/OpenIddict.Abstractions/Caches/IOpenIddictAuthorizationCache.cs +++ b/src/OpenIddict.Abstractions/Caches/IOpenIddictAuthorizationCache.cs @@ -22,52 +22,19 @@ public interface IOpenIddictAuthorizationCache where TAuthorizat /// A that can be used to monitor the asynchronous operation. ValueTask AddAsync(TAuthorization authorization, CancellationToken cancellationToken); - /// - /// Retrieves the authorizations corresponding to the specified - /// subject and associated with the application identifier. - /// - /// The subject associated with the authorization. - /// The client associated with the authorization. - /// The that can be used to abort the operation. - /// The authorizations corresponding to the subject/client. - IAsyncEnumerable FindAsync(string subject, string client, CancellationToken cancellationToken); - /// /// Retrieves the authorizations matching the specified parameters. /// - /// The subject associated with the authorization. - /// The client associated with the authorization. - /// The authorization status. - /// The that can be used to abort the operation. - /// The authorizations corresponding to the criteria. - IAsyncEnumerable FindAsync(string subject, string client, string status, CancellationToken cancellationToken); - - /// - /// Retrieves the authorizations matching the specified parameters. - /// - /// The subject associated with the authorization. - /// The client associated with the authorization. - /// The authorization status. - /// The authorization type. - /// The that can be used to abort the operation. - /// The authorizations corresponding to the criteria. - IAsyncEnumerable FindAsync( - string subject, string client, string status, - string type, CancellationToken cancellationToken); - - /// - /// Retrieves the authorizations matching the specified parameters. - /// - /// The subject associated with the authorization. - /// The client associated with the authorization. - /// The authorization status. - /// The authorization type. - /// The minimal scopes associated with the authorization. + /// The subject associated with the authorization, or not to filter out specific subjects. + /// The client associated with the authorization, or not to filter out specific clients. + /// The authorization status, or not to filter out specific authorization statuses. + /// The authorization type, or not to filter out specific authorization types. + /// The minimal scopes associated with the authorization, or not to filter out scopes. /// The that can be used to abort the operation. /// The authorizations corresponding to the criteria. IAsyncEnumerable FindAsync( - string subject, string client, string status, - string type, ImmutableArray scopes, CancellationToken cancellationToken); + string? subject, string? client, string? status, + string? type, ImmutableArray? scopes, CancellationToken cancellationToken); /// /// Retrieves the list of authorizations corresponding to the specified application identifier. diff --git a/src/OpenIddict.Abstractions/Caches/IOpenIddictTokenCache.cs b/src/OpenIddict.Abstractions/Caches/IOpenIddictTokenCache.cs index 7bba6bc31..bf3364f09 100644 --- a/src/OpenIddict.Abstractions/Caches/IOpenIddictTokenCache.cs +++ b/src/OpenIddict.Abstractions/Caches/IOpenIddictTokenCache.cs @@ -20,38 +20,18 @@ public interface IOpenIddictTokenCache where TToken : class /// A that can be used to monitor the asynchronous operation. ValueTask AddAsync(TToken token, CancellationToken cancellationToken); - /// - /// Retrieves the tokens corresponding to the specified - /// subject and associated with the application identifier. - /// - /// The subject associated with the token. - /// The client associated with the token. - /// The that can be used to abort the operation. - /// The tokens corresponding to the subject/client. - IAsyncEnumerable FindAsync(string subject, string client, CancellationToken cancellationToken); - - /// - /// Retrieves the tokens matching the specified parameters. - /// - /// The subject associated with the token. - /// The client associated with the token. - /// The token status. - /// The that can be used to abort the operation. - /// The tokens corresponding to the criteria. - IAsyncEnumerable FindAsync(string subject, string client, string status, CancellationToken cancellationToken); - /// /// Retrieves the tokens matching the specified parameters. /// - /// The subject associated with the token. - /// The client associated with the token. - /// The token status. - /// The token type. + /// The subject associated with the token, or not to filter out specific subjects. + /// The client associated with the token, or not to filter out specific clients. + /// The token status, or not to filter out specific token statuses. + /// The token type, or not to filter out specific token types. /// The that can be used to abort the operation. /// The tokens corresponding to the criteria. IAsyncEnumerable FindAsync( - string subject, string client, - string status, string type, CancellationToken cancellationToken); + string? subject, string? client, + string? status, string? type, CancellationToken cancellationToken); /// /// Retrieves the list of tokens corresponding to the specified application identifier. diff --git a/src/OpenIddict.Abstractions/Managers/IOpenIddictAuthorizationManager.cs b/src/OpenIddict.Abstractions/Managers/IOpenIddictAuthorizationManager.cs index 7a9e41b2b..a72ace979 100644 --- a/src/OpenIddict.Abstractions/Managers/IOpenIddictAuthorizationManager.cs +++ b/src/OpenIddict.Abstractions/Managers/IOpenIddictAuthorizationManager.cs @@ -106,54 +106,19 @@ ValueTask CreateAsync( /// ValueTask DeleteAsync(object authorization, CancellationToken cancellationToken = default); - /// - /// Retrieves the authorizations corresponding to the specified - /// subject and associated with the application identifier. - /// - /// The subject associated with the authorization. - /// The client associated with the authorization. - /// The that can be used to abort the operation. - /// The authorizations corresponding to the subject/client. - IAsyncEnumerable FindAsync(string subject, string client, CancellationToken cancellationToken = default); - - /// - /// Retrieves the authorizations matching the specified parameters. - /// - /// The subject associated with the authorization. - /// The client associated with the authorization. - /// The authorization status. - /// The that can be used to abort the operation. - /// The authorizations corresponding to the criteria. - IAsyncEnumerable FindAsync( - string subject, string client, - string status, CancellationToken cancellationToken = default); - - /// - /// Retrieves the authorizations matching the specified parameters. - /// - /// The subject associated with the authorization. - /// The client associated with the authorization. - /// The authorization status. - /// The authorization type. - /// The that can be used to abort the operation. - /// The authorizations corresponding to the criteria. - IAsyncEnumerable FindAsync( - string subject, string client, - string status, string type, CancellationToken cancellationToken = default); - /// /// Retrieves the authorizations matching the specified parameters. /// - /// The subject associated with the authorization. - /// The client associated with the authorization. - /// The authorization status. - /// The authorization type. - /// The minimal scopes associated with the authorization. + /// The subject associated with the authorization, or not to filter out specific subjects. + /// The client associated with the authorization, or not to filter out specific clients. + /// The authorization status, or not to filter out specific authorization statuses. + /// The authorization type, or not to filter out specific authorization types. + /// The minimal scopes associated with the authorization, or not to filter out scopes. /// The that can be used to abort the operation. /// The authorizations corresponding to the criteria. IAsyncEnumerable FindAsync( - string subject, string client, string status, - string type, ImmutableArray scopes, CancellationToken cancellationToken = default); + string? subject, string? client, string? status, + string? type, ImmutableArray? scopes, CancellationToken cancellationToken = default); /// /// Retrieves the list of authorizations corresponding to the specified application identifier. @@ -394,36 +359,16 @@ IAsyncEnumerable ListAsync( /// The number of authorizations that were removed. ValueTask PruneAsync(DateTimeOffset threshold, CancellationToken cancellationToken = default); - /// - /// Revokes all the authorizations corresponding to the specified - /// subject and associated with the application identifier. - /// - /// The subject associated with the authorization. - /// The client associated with the authorization. - /// The that can be used to abort the operation. - /// The number of authorizations corresponding to the criteria that were marked as revoked. - ValueTask RevokeAsync(string subject, string client, CancellationToken cancellationToken = default); - - /// - /// Revokes all the authorizations matching the specified parameters. - /// - /// The subject associated with the authorization. - /// The client associated with the authorization. - /// The authorization status. - /// The that can be used to abort the operation. - /// The number of authorizations corresponding to the criteria that were marked as revoked. - ValueTask RevokeAsync(string subject, string client, string status, CancellationToken cancellationToken = default); - /// /// Revokes all the authorizations matching the specified parameters. /// - /// The subject associated with the authorization. - /// The client associated with the authorization. - /// The authorization status. - /// The authorization type. + /// The subject associated with the authorization, or not to filter out specific subjects. + /// The client associated with the authorization, or not to filter out specific clients. + /// The authorization status, or not to filter out specific authorization statuses. + /// The authorization type, or not to filter out specific authorization types. /// The that can be used to abort the operation. /// The number of authorizations corresponding to the criteria that were marked as revoked. - ValueTask RevokeAsync(string subject, string client, string status, string type, CancellationToken cancellationToken = default); + ValueTask RevokeAsync(string? subject, string? client, string? status, string? type, CancellationToken cancellationToken = default); /// /// Revokes all the authorizations associated with the specified application identifier. diff --git a/src/OpenIddict.Abstractions/Managers/IOpenIddictTokenManager.cs b/src/OpenIddict.Abstractions/Managers/IOpenIddictTokenManager.cs index 3ddcfde7f..3773e7021 100644 --- a/src/OpenIddict.Abstractions/Managers/IOpenIddictTokenManager.cs +++ b/src/OpenIddict.Abstractions/Managers/IOpenIddictTokenManager.cs @@ -72,41 +72,18 @@ public interface IOpenIddictTokenManager /// ValueTask DeleteAsync(object token, CancellationToken cancellationToken = default); - /// - /// Retrieves the tokens corresponding to the specified - /// subject and associated with the application identifier. - /// - /// The subject associated with the token. - /// The client associated with the token. - /// The that can be used to abort the operation. - /// The tokens corresponding to the subject/client. - IAsyncEnumerable FindAsync(string subject, - string client, CancellationToken cancellationToken = default); - - /// - /// Retrieves the tokens matching the specified parameters. - /// - /// The subject associated with the token. - /// The client associated with the token. - /// The token status. - /// The that can be used to abort the operation. - /// The tokens corresponding to the criteria. - IAsyncEnumerable FindAsync( - string subject, string client, - string status, CancellationToken cancellationToken = default); - /// /// Retrieves the tokens matching the specified parameters. /// - /// The subject associated with the token. - /// The client associated with the token. - /// The token status. - /// The token type. + /// The subject associated with the token, or not to filter out specific subjects. + /// The client associated with the token, or not to filter out specific clients. + /// The token status, or not to filter out specific token statuses. + /// The token type, or not to filter out specific token types. /// The that can be used to abort the operation. /// The tokens corresponding to the criteria. IAsyncEnumerable FindAsync( - string subject, string client, - string status, string type, CancellationToken cancellationToken = default); + string? subject, string? client, + string? status, string? type, CancellationToken cancellationToken = default); /// /// Retrieves the list of tokens corresponding to the specified application identifier. @@ -409,36 +386,16 @@ IAsyncEnumerable ListAsync( /// The number of tokens that were removed. ValueTask PruneAsync(DateTimeOffset threshold, CancellationToken cancellationToken = default); - /// - /// Revokes all the tokens corresponding to the specified - /// subject and associated with the application identifier. - /// - /// The subject associated with the token. - /// The client associated with the token. - /// The that can be used to abort the operation. - /// The number of tokens corresponding to the criteria that were marked as revoked. - ValueTask RevokeAsync(string subject, string client, CancellationToken cancellationToken = default); - - /// - /// Revokes all the tokens matching the specified parameters. - /// - /// The subject associated with the token. - /// The client associated with the token. - /// The token status. - /// The that can be used to abort the operation. - /// The number of tokens corresponding to the criteria that were marked as revoked. - ValueTask RevokeAsync(string subject, string client, string status, CancellationToken cancellationToken = default); - /// /// Revokes all the tokens matching the specified parameters. /// - /// The subject associated with the token. - /// The client associated with the token. - /// The token status. - /// The token type. + /// The subject associated with the token, or not to filter out specific subjects. + /// The client associated with the token, or not to filter out specific clients. + /// The token status, or not to filter out specific token statuses. + /// The token type, or not to filter out specific token types. /// The that can be used to abort the operation. /// The number of tokens corresponding to the criteria that were marked as revoked. - ValueTask RevokeAsync(string subject, string client, string status, string type, CancellationToken cancellationToken = default); + ValueTask RevokeAsync(string? subject, string? client, string? status, string? type, CancellationToken cancellationToken = default); /// /// Revokes all the tokens associated with the specified application identifier. diff --git a/src/OpenIddict.Abstractions/OpenIddictResources.resx b/src/OpenIddict.Abstractions/OpenIddictResources.resx index 0ab3e2479..4e260d063 100644 --- a/src/OpenIddict.Abstractions/OpenIddictResources.resx +++ b/src/OpenIddict.Abstractions/OpenIddictResources.resx @@ -1701,6 +1701,12 @@ To apply post-logout redirection responses, create a class implementing 'IOpenId The '{0}' parameter cannot contain null or empty values. + + A token must be specified when using introspection. + + + A token must be specified when using revocation. + The security token is missing. diff --git a/src/OpenIddict.Abstractions/Primitives/OpenIddictParameter.cs b/src/OpenIddict.Abstractions/Primitives/OpenIddictParameter.cs index 039d64cb7..d6be1a7e5 100644 --- a/src/OpenIddict.Abstractions/Primitives/OpenIddictParameter.cs +++ b/src/OpenIddict.Abstractions/Primitives/OpenIddictParameter.cs @@ -200,7 +200,7 @@ public bool Equals(OpenIddictParameter other) (string left, string right) => string.Equals(left, right, StringComparison.Ordinal), // If the two parameters are string arrays, use SequenceEqual(). - (string?[] left, string?[] right) => left.SequenceEqual(right), + (string?[] left, string?[] right) => Enumerable.SequenceEqual(left, right), // If one of the two parameters is an undefined JsonElement, treat it // as a null value and return true if the other parameter is null too. diff --git a/src/OpenIddict.Abstractions/Stores/IOpenIddictAuthorizationStore.cs b/src/OpenIddict.Abstractions/Stores/IOpenIddictAuthorizationStore.cs index cac2c2970..b1526fd41 100644 --- a/src/OpenIddict.Abstractions/Stores/IOpenIddictAuthorizationStore.cs +++ b/src/OpenIddict.Abstractions/Stores/IOpenIddictAuthorizationStore.cs @@ -53,55 +53,20 @@ public interface IOpenIddictAuthorizationStore where TAuthorizat /// A that can be used to monitor the asynchronous operation. ValueTask DeleteAsync(TAuthorization authorization, CancellationToken cancellationToken); - /// - /// Retrieves the authorizations corresponding to the specified - /// subject and associated with the application identifier. - /// - /// The subject associated with the authorization. - /// The client associated with the authorization. - /// The that can be used to abort the operation. - /// The authorizations corresponding to the subject/client. - IAsyncEnumerable FindAsync(string subject, string client, CancellationToken cancellationToken); - /// /// Retrieves the authorizations matching the specified parameters. /// - /// The subject associated with the authorization. - /// The client associated with the authorization. - /// The authorization status. + /// The subject associated with the authorization, or not to filter out specific subjects. + /// The client associated with the authorization, or not to filter out specific clients. + /// The authorization status, or not to filter out specific authorization statuses. + /// The authorization type, or not to filter out specific authorization types. + /// The minimal scopes associated with the authorization, or not to filter out scopes. /// The that can be used to abort the operation. /// The authorizations corresponding to the criteria. IAsyncEnumerable FindAsync( - string subject, string client, - string status, CancellationToken cancellationToken); - - /// - /// Retrieves the authorizations matching the specified parameters. - /// - /// The subject associated with the authorization. - /// The client associated with the authorization. - /// The authorization status. - /// The authorization type. - /// The that can be used to abort the operation. - /// The authorizations corresponding to the criteria. - IAsyncEnumerable FindAsync( - string subject, string client, - string status, string type, CancellationToken cancellationToken); - - /// - /// Retrieves the authorizations matching the specified parameters. - /// - /// The subject associated with the authorization. - /// The client associated with the authorization. - /// The authorization status. - /// The authorization type. - /// The minimal scopes associated with the authorization. - /// The that can be used to abort the operation. - /// The authorizations corresponding to the criteria. - IAsyncEnumerable FindAsync( - string subject, string client, - string status, string type, - ImmutableArray scopes, CancellationToken cancellationToken); + string? subject, string? client, + string? status, string? type, + ImmutableArray? scopes, CancellationToken cancellationToken); /// /// Retrieves the list of authorizations corresponding to the specified application identifier. @@ -279,36 +244,16 @@ IAsyncEnumerable ListAsync( /// The number of authorizations that were removed. ValueTask PruneAsync(DateTimeOffset threshold, CancellationToken cancellationToken); - /// - /// Revokes all the authorizations corresponding to the specified - /// subject and associated with the application identifier. - /// - /// The subject associated with the authorization. - /// The client associated with the authorization. - /// The that can be used to abort the operation. - /// The number of authorizations corresponding to the criteria that were marked as revoked. - ValueTask RevokeAsync(string subject, string client, CancellationToken cancellationToken); - /// /// Revokes all the authorizations matching the specified parameters. /// - /// The subject associated with the authorization. - /// The client associated with the authorization. - /// The authorization status. - /// The that can be used to abort the operation. - /// The number of authorizations corresponding to the criteria that were marked as revoked. - ValueTask RevokeAsync(string subject, string client, string status, CancellationToken cancellationToken); - - /// - /// Revokes all the authorizations matching the specified parameters. - /// - /// The subject associated with the authorization. - /// The client associated with the authorization. - /// The authorization status. - /// The authorization type. + /// The subject associated with the authorization, or not to filter out specific subjects. + /// The client associated with the authorization, or not to filter out specific clients. + /// The authorization status, or not to filter out specific authorization statuses. + /// The authorization type, or not to filter out specific authorization types. /// The that can be used to abort the operation. /// The number of authorizations corresponding to the criteria that were marked as revoked. - ValueTask RevokeAsync(string subject, string client, string status, string type, CancellationToken cancellationToken); + ValueTask RevokeAsync(string? subject, string? client, string? status, string? type, CancellationToken cancellationToken); /// /// Revokes all the authorizations associated with the specified application identifier. @@ -316,7 +261,7 @@ IAsyncEnumerable ListAsync( /// The application identifier associated with the authorizations. /// The that can be used to abort the operation. /// The number of authorizations associated with the specified application that were marked as revoked. - ValueTask RevokeByApplicationIdAsync(string identifier, CancellationToken cancellationToken = default); + ValueTask RevokeByApplicationIdAsync(string identifier, CancellationToken cancellationToken); /// /// Revokes all the authorizations associated with the specified subject. @@ -324,7 +269,7 @@ IAsyncEnumerable ListAsync( /// The subject associated with the authorizations. /// The that can be used to abort the operation. /// The number of authorizations associated with the specified subject that were marked as revoked. - ValueTask RevokeBySubjectAsync(string subject, CancellationToken cancellationToken = default); + ValueTask RevokeBySubjectAsync(string subject, CancellationToken cancellationToken); /// /// Sets the application identifier associated with an authorization. diff --git a/src/OpenIddict.Abstractions/Stores/IOpenIddictTokenStore.cs b/src/OpenIddict.Abstractions/Stores/IOpenIddictTokenStore.cs index b2cde75da..9f7856ab0 100644 --- a/src/OpenIddict.Abstractions/Stores/IOpenIddictTokenStore.cs +++ b/src/OpenIddict.Abstractions/Stores/IOpenIddictTokenStore.cs @@ -53,40 +53,18 @@ public interface IOpenIddictTokenStore where TToken : class /// A that can be used to monitor the asynchronous operation. ValueTask DeleteAsync(TToken token, CancellationToken cancellationToken); - /// - /// Retrieves the tokens corresponding to the specified - /// subject and associated with the application identifier. - /// - /// The subject associated with the token. - /// The client associated with the token. - /// The that can be used to abort the operation. - /// The tokens corresponding to the subject/client. - IAsyncEnumerable FindAsync(string subject, string client, CancellationToken cancellationToken); - - /// - /// Retrieves the tokens matching the specified parameters. - /// - /// The subject associated with the token. - /// The client associated with the token. - /// The token status. - /// The that can be used to abort the operation. - /// The tokens corresponding to the criteria. - IAsyncEnumerable FindAsync( - string subject, string client, - string status, CancellationToken cancellationToken); - /// /// Retrieves the tokens matching the specified parameters. /// - /// The subject associated with the token. - /// The client associated with the token. - /// The token status. - /// The token type. + /// The subject associated with the token, or not to filter out specific subjects. + /// The client associated with the token, or not to filter out specific clients. + /// The token status, or not to filter out specific token statuses. + /// The token type, or not to filter out specific token types. /// The that can be used to abort the operation. /// The tokens corresponding to the criteria. IAsyncEnumerable FindAsync( - string subject, string client, - string status, string type, CancellationToken cancellationToken); + string? subject, string? client, + string? status, string? type, CancellationToken cancellationToken); /// /// Retrieves the list of tokens corresponding to the specified application identifier. @@ -326,36 +304,16 @@ IAsyncEnumerable ListAsync( /// The number of tokens that were removed. ValueTask PruneAsync(DateTimeOffset threshold, CancellationToken cancellationToken); - /// - /// Revokes all the tokens corresponding to the specified - /// subject and associated with the application identifier. - /// - /// The subject associated with the token. - /// The client associated with the token. - /// The that can be used to abort the operation. - /// The number of tokens corresponding to the criteria that were marked as revoked. - ValueTask RevokeAsync(string subject, string client, CancellationToken cancellationToken); - - /// - /// Revokes all the tokens matching the specified parameters. - /// - /// The subject associated with the token. - /// The client associated with the token. - /// The token status. - /// The that can be used to abort the operation. - /// The number of tokens corresponding to the criteria that were marked as revoked. - ValueTask RevokeAsync(string subject, string client, string status, CancellationToken cancellationToken); - /// /// Revokes all the tokens matching the specified parameters. /// - /// The subject associated with the token. - /// The client associated with the token. - /// The token status. - /// The token type. + /// The subject associated with the token, or not to filter out specific subjects. + /// The client associated with the token, or not to filter out specific clients. + /// The token status, or not to filter out specific token statuses. + /// The token type, or not to filter out specific token types. /// The that can be used to abort the operation. /// The number of tokens corresponding to the criteria that were marked as revoked. - ValueTask RevokeAsync(string subject, string client, string status, string type, CancellationToken cancellationToken); + ValueTask RevokeAsync(string? subject, string? client, string? status, string? type, CancellationToken cancellationToken); /// /// Revokes all the tokens associated with the specified application identifier. diff --git a/src/OpenIddict.Client.AspNetCore/OpenIddictClientAspNetCoreHandler.cs b/src/OpenIddict.Client.AspNetCore/OpenIddictClientAspNetCoreHandler.cs index 1e0df7e7d..67148aa41 100644 --- a/src/OpenIddict.Client.AspNetCore/OpenIddictClientAspNetCoreHandler.cs +++ b/src/OpenIddict.Client.AspNetCore/OpenIddictClientAspNetCoreHandler.cs @@ -156,17 +156,24 @@ protected override async Task HandleAuthenticateAsync() return AuthenticateResult.NoResult(); } - var properties = new AuthenticationProperties(new Dictionary - { - [Properties.Error] = context.Error, - [Properties.ErrorDescription] = context.ErrorDescription, - [Properties.ErrorUri] = context.ErrorUri - }); + var properties = CreateAuthenticationProperties(); + properties.Items[Properties.Error] = context.Error; + properties.Items[Properties.ErrorDescription] = context.ErrorDescription; + properties.Items[Properties.ErrorUri] = context.ErrorUri; return AuthenticateResult.Fail(SR.GetResourceString(SR.ID0113), properties); } else + { + var properties = CreateAuthenticationProperties(); + + return AuthenticateResult.Success(new AuthenticationTicket( + context.MergedPrincipal ?? new ClaimsPrincipal(new ClaimsIdentity()), properties, + OpenIddictClientAspNetCoreDefaults.AuthenticationScheme)); + } + + AuthenticationProperties CreateAuthenticationProperties() { var properties = new AuthenticationProperties { @@ -336,9 +343,7 @@ protected override async Task HandleAuthenticateAsync() properties.SetParameter(Properties.UserInfoTokenPrincipal, context.UserInfoTokenPrincipal); } - return AuthenticateResult.Success(new AuthenticationTicket( - context.MergedPrincipal ?? new ClaimsPrincipal(new ClaimsIdentity()), properties, - OpenIddictClientAspNetCoreDefaults.AuthenticationScheme)); + return properties; } } diff --git a/src/OpenIddict.Client.Owin/OpenIddictClientOwinHandler.cs b/src/OpenIddict.Client.Owin/OpenIddictClientOwinHandler.cs index b6c480d7b..8f7f44a31 100644 --- a/src/OpenIddict.Client.Owin/OpenIddictClientOwinHandler.cs +++ b/src/OpenIddict.Client.Owin/OpenIddictClientOwinHandler.cs @@ -155,17 +155,22 @@ public override async Task InvokeAsync() return null; } - var properties = new AuthenticationProperties(new Dictionary - { - [Properties.Error] = context.Error, - [Properties.ErrorDescription] = context.ErrorDescription, - [Properties.ErrorUri] = context.ErrorUri - }); + var properties = CreateAuthenticationProperties(); + properties.Dictionary[Properties.Error] = context.Error; + properties.Dictionary[Properties.ErrorDescription] = context.ErrorDescription; + properties.Dictionary[Properties.ErrorUri] = context.ErrorUri; - return new AuthenticationTicket(null, properties); + return new AuthenticationTicket(new ClaimsIdentity(), properties); } else + { + var properties = CreateAuthenticationProperties(); + + return new AuthenticationTicket(context.MergedPrincipal?.Identity as ClaimsIdentity ?? new ClaimsIdentity(), properties); + } + + AuthenticationProperties CreateAuthenticationProperties() { var properties = new AuthenticationProperties { @@ -240,7 +245,7 @@ public override async Task InvokeAsync() properties.Dictionary[Tokens.UserInfoToken] = context.UserInfoToken; } - return new AuthenticationTicket(context.MergedPrincipal?.Identity as ClaimsIdentity, properties); + return properties; } } diff --git a/src/OpenIddict.Client.WebIntegration/OpenIddictClientWebIntegrationConfiguration.cs b/src/OpenIddict.Client.WebIntegration/OpenIddictClientWebIntegrationConfiguration.cs index 85d5b74e5..b1bc3e0c9 100644 --- a/src/OpenIddict.Client.WebIntegration/OpenIddictClientWebIntegrationConfiguration.cs +++ b/src/OpenIddict.Client.WebIntegration/OpenIddictClientWebIntegrationConfiguration.cs @@ -5,7 +5,10 @@ */ using System.ComponentModel; +using System.Security.Cryptography.X509Certificates; using Microsoft.Extensions.Options; +using OpenIddict.Client.SystemNetHttp; +using static OpenIddict.Client.WebIntegration.OpenIddictClientWebIntegrationConstants; namespace OpenIddict.Client.WebIntegration; @@ -14,7 +17,8 @@ namespace OpenIddict.Client.WebIntegration; /// [EditorBrowsable(EditorBrowsableState.Advanced)] public sealed partial class OpenIddictClientWebIntegrationConfiguration : IConfigureOptions, - IPostConfigureOptions + IPostConfigureOptions, + IPostConfigureOptions { /// public void Configure(OpenIddictClientOptions options) @@ -47,6 +51,38 @@ public void PostConfigure(string? name, OpenIddictClientOptions options) }); } + /// + public void PostConfigure(string? name, OpenIddictClientSystemNetHttpOptions options) + { + if (options is null) + { + throw new ArgumentNullException(nameof(options)); + } + + // Override the default/user-defined selectors to support attaching TLS client + // certificates that don't meet the requirements enforced by default by OpenIddict. + options.SelfSignedTlsClientAuthenticationCertificateSelector = CreateSelector(options.SelfSignedTlsClientAuthenticationCertificateSelector); + options.TlsClientAuthenticationCertificateSelector = CreateSelector(options.TlsClientAuthenticationCertificateSelector); + + static Func CreateSelector(Func selector) + => registration => + { + var certificate = registration.ProviderType switch + { + ProviderTypes.ProSantéConnect => registration.GetProSantéConnectSettings().SigningCertificate, + + _ => null + }; + + if (certificate is not null) + { + return certificate; + } + + return selector(registration); + }; + } + /// /// Amends the registration with the provider-specific configuration logic. /// diff --git a/src/OpenIddict.Client.WebIntegration/OpenIddictClientWebIntegrationExtensions.cs b/src/OpenIddict.Client.WebIntegration/OpenIddictClientWebIntegrationExtensions.cs index 3b78e5d0e..1ad218023 100644 --- a/src/OpenIddict.Client.WebIntegration/OpenIddictClientWebIntegrationExtensions.cs +++ b/src/OpenIddict.Client.WebIntegration/OpenIddictClientWebIntegrationExtensions.cs @@ -7,6 +7,7 @@ using Microsoft.Extensions.DependencyInjection.Extensions; using Microsoft.Extensions.Options; using OpenIddict.Client; +using OpenIddict.Client.SystemNetHttp; using OpenIddict.Client.WebIntegration; namespace Microsoft.Extensions.DependencyInjection; @@ -40,6 +41,8 @@ public static OpenIddictClientWebIntegrationBuilder UseWebProviders(this OpenIdd // Note: TryAddEnumerable() is used here to ensure the initializers are registered only once. builder.Services.TryAddEnumerable(ServiceDescriptor.Singleton< IConfigureOptions, OpenIddictClientWebIntegrationConfiguration>()); + builder.Services.TryAddEnumerable(ServiceDescriptor.Singleton< + IPostConfigureOptions, OpenIddictClientWebIntegrationConfiguration>()); // Note: the IPostConfigureOptions service responsible for populating // the client registrations MUST be registered before OpenIddictClientConfiguration to ensure diff --git a/src/OpenIddict.Client/OpenIddictClientHandlers.cs b/src/OpenIddict.Client/OpenIddictClientHandlers.cs index f327891d2..5de69eb74 100644 --- a/src/OpenIddict.Client/OpenIddictClientHandlers.cs +++ b/src/OpenIddict.Client/OpenIddictClientHandlers.cs @@ -6171,6 +6171,11 @@ public ValueTask HandleAsync(ProcessIntrospectionContext context) throw new ArgumentNullException(nameof(context)); } + if (string.IsNullOrEmpty(context.Token)) + { + throw new InvalidOperationException(SR.GetResourceString(SR.ID0458)); + } + if (context.Registration is null && string.IsNullOrEmpty(context.RegistrationId) && context.Issuer is null && string.IsNullOrEmpty(context.ProviderName) && context.Options.Registrations.Count is not 1) @@ -6841,6 +6846,11 @@ public ValueTask HandleAsync(ProcessRevocationContext context) throw new ArgumentNullException(nameof(context)); } + if (string.IsNullOrEmpty(context.Token)) + { + throw new InvalidOperationException(SR.GetResourceString(SR.ID0459)); + } + if (context.Registration is null && string.IsNullOrEmpty(context.RegistrationId) && context.Issuer is null && string.IsNullOrEmpty(context.ProviderName) && context.Options.Registrations.Count is not 1) diff --git a/src/OpenIddict.Core/Caches/OpenIddictAuthorizationCache.cs b/src/OpenIddict.Core/Caches/OpenIddictAuthorizationCache.cs index 631205bc7..17b79006c 100644 --- a/src/OpenIddict.Core/Caches/OpenIddictAuthorizationCache.cs +++ b/src/OpenIddict.Core/Caches/OpenIddictAuthorizationCache.cs @@ -44,30 +44,6 @@ public async ValueTask AddAsync(TAuthorization authorization, CancellationToken throw new ArgumentNullException(nameof(authorization)); } - _cache.Remove(new - { - Method = nameof(FindAsync), - Subject = await _store.GetSubjectAsync(authorization, cancellationToken), - Client = await _store.GetApplicationIdAsync(authorization, cancellationToken) - }); - - _cache.Remove(new - { - Method = nameof(FindAsync), - Subject = await _store.GetSubjectAsync(authorization, cancellationToken), - Client = await _store.GetApplicationIdAsync(authorization, cancellationToken), - Status = await _store.GetStatusAsync(authorization, cancellationToken) - }); - - _cache.Remove(new - { - Method = nameof(FindAsync), - Subject = await _store.GetSubjectAsync(authorization, cancellationToken), - Client = await _store.GetApplicationIdAsync(authorization, cancellationToken), - Status = await _store.GetStatusAsync(authorization, cancellationToken), - Type = await _store.GetTypeAsync(authorization, cancellationToken) - }); - _cache.Remove(new { Method = nameof(FindByApplicationIdAsync), @@ -105,206 +81,18 @@ public void Dispose() } /// - public IAsyncEnumerable FindAsync(string subject, string client, CancellationToken cancellationToken) - { - if (string.IsNullOrEmpty(subject)) - { - throw new ArgumentException(SR.GetResourceString(SR.ID0198), nameof(subject)); - } - - if (string.IsNullOrEmpty(client)) - { - throw new ArgumentException(SR.GetResourceString(SR.ID0124), nameof(client)); - } - - return ExecuteAsync(cancellationToken); - - async IAsyncEnumerable ExecuteAsync([EnumeratorCancellation] CancellationToken cancellationToken) - { - var parameters = new - { - Method = nameof(FindAsync), - Subject = subject, - Client = client - }; - - if (!_cache.TryGetValue(parameters, out ImmutableArray authorizations)) - { - var builder = ImmutableArray.CreateBuilder(); - - await foreach (var authorization in _store.FindAsync(subject, client, cancellationToken)) - { - builder.Add(authorization); - - await AddAsync(authorization, cancellationToken); - } - - authorizations = builder.ToImmutable(); - - await CreateEntryAsync(parameters, authorizations, cancellationToken); - } - - foreach (var authorization in authorizations) - { - yield return authorization; - } - } - } - - /// - public IAsyncEnumerable FindAsync( - string subject, string client, - string status, CancellationToken cancellationToken) - { - if (string.IsNullOrEmpty(subject)) - { - throw new ArgumentException(SR.GetResourceString(SR.ID0198), nameof(subject)); - } - - if (string.IsNullOrEmpty(client)) - { - throw new ArgumentException(SR.GetResourceString(SR.ID0124), nameof(client)); - } - - if (string.IsNullOrEmpty(status)) - { - throw new ArgumentException(SR.GetResourceString(SR.ID0199), nameof(status)); - } - - return ExecuteAsync(cancellationToken); - - async IAsyncEnumerable ExecuteAsync([EnumeratorCancellation] CancellationToken cancellationToken) - { - var parameters = new - { - Method = nameof(FindAsync), - Subject = subject, - Client = client, - Status = status - }; - - if (!_cache.TryGetValue(parameters, out ImmutableArray authorizations)) - { - var builder = ImmutableArray.CreateBuilder(); - - await foreach (var authorization in _store.FindAsync(subject, client, status, cancellationToken)) - { - builder.Add(authorization); - - await AddAsync(authorization, cancellationToken); - } - - authorizations = builder.ToImmutable(); - - await CreateEntryAsync(parameters, authorizations, cancellationToken); - } - - foreach (var authorization in authorizations) - { - yield return authorization; - } - } - } - - /// - public IAsyncEnumerable FindAsync( - string subject, string client, - string status, string type, CancellationToken cancellationToken) + public async IAsyncEnumerable FindAsync( + string? subject, string? client, + string? status, string? type, + ImmutableArray? scopes, [EnumeratorCancellation] CancellationToken cancellationToken) { - if (string.IsNullOrEmpty(subject)) - { - throw new ArgumentException(SR.GetResourceString(SR.ID0198), nameof(subject)); - } - - if (string.IsNullOrEmpty(client)) - { - throw new ArgumentException(SR.GetResourceString(SR.ID0124), nameof(client)); - } - - if (string.IsNullOrEmpty(status)) - { - throw new ArgumentException(SR.GetResourceString(SR.ID0199), nameof(status)); - } - - if (string.IsNullOrEmpty(type)) - { - throw new ArgumentException(SR.GetResourceString(SR.ID0200), nameof(type)); - } - - return ExecuteAsync(cancellationToken); - - async IAsyncEnumerable ExecuteAsync([EnumeratorCancellation] CancellationToken cancellationToken) - { - var parameters = new - { - Method = nameof(FindAsync), - Subject = subject, - Client = client, - Status = status, - Type = type - }; - - if (!_cache.TryGetValue(parameters, out ImmutableArray authorizations)) - { - var builder = ImmutableArray.CreateBuilder(); - - await foreach (var authorization in _store.FindAsync(subject, client, status, type, cancellationToken)) - { - builder.Add(authorization); - - await AddAsync(authorization, cancellationToken); - } - - authorizations = builder.ToImmutable(); - - await CreateEntryAsync(parameters, authorizations, cancellationToken); - } - - foreach (var authorization in authorizations) - { - yield return authorization; - } - } - } - - /// - public IAsyncEnumerable FindAsync( - string subject, string client, - string status, string type, - ImmutableArray scopes, CancellationToken cancellationToken) - { - if (string.IsNullOrEmpty(subject)) - { - throw new ArgumentException(SR.GetResourceString(SR.ID0198), nameof(subject)); - } - - if (string.IsNullOrEmpty(client)) - { - throw new ArgumentException(SR.GetResourceString(SR.ID0124), nameof(client)); - } - - if (string.IsNullOrEmpty(status)) - { - throw new ArgumentException(SR.GetResourceString(SR.ID0199), nameof(status)); - } - - if (string.IsNullOrEmpty(type)) - { - throw new ArgumentException(SR.GetResourceString(SR.ID0200), nameof(type)); - } - // Note: this method is only partially cached. - return ExecuteAsync(cancellationToken); - - async IAsyncEnumerable ExecuteAsync([EnumeratorCancellation] CancellationToken cancellationToken) + await foreach (var authorization in _store.FindAsync(subject, client, status, type, scopes, cancellationToken)) { - await foreach (var authorization in _store.FindAsync(subject, client, status, type, scopes, cancellationToken)) - { - await AddAsync(authorization, cancellationToken); + await AddAsync(authorization, cancellationToken); - yield return authorization; - } + yield return authorization; } } diff --git a/src/OpenIddict.Core/Caches/OpenIddictTokenCache.cs b/src/OpenIddict.Core/Caches/OpenIddictTokenCache.cs index 7d3108670..f1f34d5cf 100644 --- a/src/OpenIddict.Core/Caches/OpenIddictTokenCache.cs +++ b/src/OpenIddict.Core/Caches/OpenIddictTokenCache.cs @@ -44,30 +44,6 @@ public async ValueTask AddAsync(TToken token, CancellationToken cancellationToke throw new ArgumentNullException(nameof(token)); } - _cache.Remove(new - { - Method = nameof(FindAsync), - Subject = await _store.GetSubjectAsync(token, cancellationToken), - Client = await _store.GetApplicationIdAsync(token, cancellationToken) - }); - - _cache.Remove(new - { - Method = nameof(FindAsync), - Subject = await _store.GetSubjectAsync(token, cancellationToken), - Client = await _store.GetApplicationIdAsync(token, cancellationToken), - Status = await _store.GetStatusAsync(token, cancellationToken) - }); - - _cache.Remove(new - { - Method = nameof(FindAsync), - Subject = await _store.GetSubjectAsync(token, cancellationToken), - Client = await _store.GetApplicationIdAsync(token, cancellationToken), - Status = await _store.GetStatusAsync(token, cancellationToken), - Type = await _store.GetTypeAsync(token, cancellationToken) - }); - _cache.Remove(new { Method = nameof(FindByApplicationIdAsync), @@ -123,165 +99,17 @@ public void Dispose() } /// - public IAsyncEnumerable FindAsync(string subject, string client, CancellationToken cancellationToken) + public async IAsyncEnumerable FindAsync( + string? subject, string? client, + string? status, string? type, [EnumeratorCancellation] CancellationToken cancellationToken) { - if (string.IsNullOrEmpty(subject)) - { - throw new ArgumentException(SR.GetResourceString(SR.ID0198), nameof(subject)); - } - - if (string.IsNullOrEmpty(client)) - { - throw new ArgumentException(SR.GetResourceString(SR.ID0124), nameof(client)); - } + // Note: this method is only partially cached. - return ExecuteAsync(cancellationToken); - - async IAsyncEnumerable ExecuteAsync([EnumeratorCancellation] CancellationToken cancellationToken) + await foreach (var token in _store.FindAsync(subject, client, status, type, cancellationToken)) { - var parameters = new - { - Method = nameof(FindAsync), - Subject = subject, - Client = client - }; + await AddAsync(token, cancellationToken); - if (!_cache.TryGetValue(parameters, out ImmutableArray tokens)) - { - var builder = ImmutableArray.CreateBuilder(); - - await foreach (var token in _store.FindAsync(subject, client, cancellationToken)) - { - builder.Add(token); - - await AddAsync(token, cancellationToken); - } - - tokens = builder.ToImmutable(); - - await CreateEntryAsync(parameters, tokens, cancellationToken); - } - - foreach (var token in tokens) - { - yield return token; - } - } - } - - /// - public IAsyncEnumerable FindAsync( - string subject, string client, - string status, CancellationToken cancellationToken) - { - if (string.IsNullOrEmpty(subject)) - { - throw new ArgumentException(SR.GetResourceString(SR.ID0198), nameof(subject)); - } - - if (string.IsNullOrEmpty(client)) - { - throw new ArgumentException(SR.GetResourceString(SR.ID0124), nameof(client)); - } - - if (string.IsNullOrEmpty(status)) - { - throw new ArgumentException(SR.GetResourceString(SR.ID0199), nameof(status)); - } - - return ExecuteAsync(cancellationToken); - - async IAsyncEnumerable ExecuteAsync([EnumeratorCancellation] CancellationToken cancellationToken) - { - var parameters = new - { - Method = nameof(FindAsync), - Subject = subject, - Client = client, - Status = status - }; - - if (!_cache.TryGetValue(parameters, out ImmutableArray tokens)) - { - var builder = ImmutableArray.CreateBuilder(); - - await foreach (var token in _store.FindAsync(subject, client, status, cancellationToken)) - { - builder.Add(token); - - await AddAsync(token, cancellationToken); - } - - tokens = builder.ToImmutable(); - - await CreateEntryAsync(parameters, tokens, cancellationToken); - } - - foreach (var token in tokens) - { - yield return token; - } - } - } - - /// - public IAsyncEnumerable FindAsync( - string subject, string client, - string status, string type, CancellationToken cancellationToken) - { - if (string.IsNullOrEmpty(subject)) - { - throw new ArgumentException(SR.GetResourceString(SR.ID0198), nameof(subject)); - } - - if (string.IsNullOrEmpty(client)) - { - throw new ArgumentException(SR.GetResourceString(SR.ID0124), nameof(client)); - } - - if (string.IsNullOrEmpty(status)) - { - throw new ArgumentException(SR.GetResourceString(SR.ID0199), nameof(status)); - } - - if (string.IsNullOrEmpty(type)) - { - throw new ArgumentException(SR.GetResourceString(SR.ID0200), nameof(type)); - } - - return ExecuteAsync(cancellationToken); - - async IAsyncEnumerable ExecuteAsync([EnumeratorCancellation] CancellationToken cancellationToken) - { - var parameters = new - { - Method = nameof(FindAsync), - Subject = subject, - Client = client, - Status = status, - Type = type - }; - - if (!_cache.TryGetValue(parameters, out ImmutableArray tokens)) - { - var builder = ImmutableArray.CreateBuilder(); - - await foreach (var token in _store.FindAsync(subject, client, status, type, cancellationToken)) - { - builder.Add(token); - - await AddAsync(token, cancellationToken); - } - - tokens = builder.ToImmutable(); - - await CreateEntryAsync(parameters, tokens, cancellationToken); - } - - foreach (var token in tokens) - { - yield return token; - } + yield return token; } } diff --git a/src/OpenIddict.Core/Managers/OpenIddictAuthorizationManager.cs b/src/OpenIddict.Core/Managers/OpenIddictAuthorizationManager.cs index e2c4b556d..4b085e04a 100644 --- a/src/OpenIddict.Core/Managers/OpenIddictAuthorizationManager.cs +++ b/src/OpenIddict.Core/Managers/OpenIddictAuthorizationManager.cs @@ -271,207 +271,21 @@ public virtual async ValueTask DeleteAsync(TAuthorization authorization, Cancell await Store.DeleteAsync(authorization, cancellationToken); } - /// - /// Retrieves the authorizations corresponding to the specified - /// subject and associated with the application identifier. - /// - /// The subject associated with the authorization. - /// The client associated with the authorization. - /// The that can be used to abort the operation. - /// The authorizations corresponding to the subject/client. - public virtual IAsyncEnumerable FindAsync( - string subject, string client, CancellationToken cancellationToken = default) - { - if (string.IsNullOrEmpty(subject)) - { - throw new ArgumentException(SR.GetResourceString(SR.ID0198), nameof(subject)); - } - - if (string.IsNullOrEmpty(client)) - { - throw new ArgumentException(SR.GetResourceString(SR.ID0124), nameof(client)); - } - - var authorizations = Options.CurrentValue.DisableEntityCaching ? - Store.FindAsync(subject, client, cancellationToken) : - Cache.FindAsync(subject, client, cancellationToken); - - // SQL engines like Microsoft SQL Server or MySQL are known to use case-insensitive lookups by default. - // To ensure a case-sensitive comparison is enforced independently of the database/table/query collation - // used by the store, a second pass using string.Equals(StringComparison.Ordinal) is manually made here. - - if (Options.CurrentValue.DisableAdditionalFiltering) - { - return authorizations; - } - - // SQL engines like Microsoft SQL Server or MySQL are known to use case-insensitive lookups by default. - // To ensure a case-sensitive comparison is enforced independently of the database/table/query collation - // used by the store, a second pass using string.Equals(StringComparison.Ordinal) is manually made here. - - return ExecuteAsync(cancellationToken); - - async IAsyncEnumerable ExecuteAsync([EnumeratorCancellation] CancellationToken cancellationToken) - { - await foreach (var authorization in authorizations) - { - if (string.Equals(await Store.GetSubjectAsync(authorization, cancellationToken), subject, StringComparison.Ordinal)) - { - yield return authorization; - } - } - } - } - - /// - /// Retrieves the authorizations matching the specified parameters. - /// - /// The subject associated with the authorization. - /// The client associated with the authorization. - /// The authorization status. - /// The that can be used to abort the operation. - /// The authorizations corresponding to the criteria. - public virtual IAsyncEnumerable FindAsync( - string subject, string client, - string status, CancellationToken cancellationToken = default) - { - if (string.IsNullOrEmpty(subject)) - { - throw new ArgumentException(SR.GetResourceString(SR.ID0198), nameof(subject)); - } - - if (string.IsNullOrEmpty(client)) - { - throw new ArgumentException(SR.GetResourceString(SR.ID0124), nameof(client)); - } - - if (string.IsNullOrEmpty(status)) - { - throw new ArgumentException(SR.GetResourceString(SR.ID0199), nameof(status)); - } - - var authorizations = Options.CurrentValue.DisableEntityCaching ? - Store.FindAsync(subject, client, status, cancellationToken) : - Cache.FindAsync(subject, client, status, cancellationToken); - - if (Options.CurrentValue.DisableAdditionalFiltering) - { - return authorizations; - } - - // SQL engines like Microsoft SQL Server or MySQL are known to use case-insensitive lookups by default. - // To ensure a case-sensitive comparison is enforced independently of the database/table/query collation - // used by the store, a second pass using string.Equals(StringComparison.Ordinal) is manually made here. - - return ExecuteAsync(cancellationToken); - - async IAsyncEnumerable ExecuteAsync([EnumeratorCancellation] CancellationToken cancellationToken) - { - await foreach (var authorization in authorizations) - { - if (string.Equals(await Store.GetSubjectAsync(authorization, cancellationToken), subject, StringComparison.Ordinal)) - { - yield return authorization; - } - } - } - } - /// /// Retrieves the authorizations matching the specified parameters. /// - /// The subject associated with the authorization. - /// The client associated with the authorization. - /// The authorization status. - /// The authorization type. + /// The subject associated with the authorization, or not to filter out specific subjects. + /// The client associated with the authorization, or not to filter out specific clients. + /// The authorization status, or not to filter out specific authorization statuses. + /// The authorization type, or not to filter out specific authorization types. + /// The minimal scopes associated with the authorization, or not to filter out scopes. /// The that can be used to abort the operation. /// The authorizations corresponding to the criteria. public virtual IAsyncEnumerable FindAsync( - string subject, string client, - string status, string type, CancellationToken cancellationToken = default) + string? subject, string? client, + string? status, string? type, + ImmutableArray? scopes, CancellationToken cancellationToken = default) { - if (string.IsNullOrEmpty(subject)) - { - throw new ArgumentException(SR.GetResourceString(SR.ID0198), nameof(subject)); - } - - if (string.IsNullOrEmpty(client)) - { - throw new ArgumentException(SR.GetResourceString(SR.ID0124), nameof(client)); - } - - if (string.IsNullOrEmpty(status)) - { - throw new ArgumentException(SR.GetResourceString(SR.ID0199), nameof(status)); - } - - if (string.IsNullOrEmpty(type)) - { - throw new ArgumentException(SR.GetResourceString(SR.ID0200), nameof(type)); - } - - var authorizations = Options.CurrentValue.DisableEntityCaching ? - Store.FindAsync(subject, client, status, type, cancellationToken) : - Cache.FindAsync(subject, client, status, type, cancellationToken); - - if (Options.CurrentValue.DisableAdditionalFiltering) - { - return authorizations; - } - - // SQL engines like Microsoft SQL Server or MySQL are known to use case-insensitive lookups by default. - // To ensure a case-sensitive comparison is enforced independently of the database/table/query collation - // used by the store, a second pass using string.Equals(StringComparison.Ordinal) is manually made here. - - return ExecuteAsync(cancellationToken); - - async IAsyncEnumerable ExecuteAsync([EnumeratorCancellation] CancellationToken cancellationToken) - { - await foreach (var authorization in authorizations) - { - if (string.Equals(await Store.GetSubjectAsync(authorization, cancellationToken), subject, StringComparison.Ordinal)) - { - yield return authorization; - } - } - } - } - - /// - /// Retrieves the authorizations matching the specified parameters. - /// - /// The subject associated with the authorization. - /// The client associated with the authorization. - /// The authorization status. - /// The authorization type. - /// The minimal scopes associated with the authorization. - /// The that can be used to abort the operation. - /// The authorizations corresponding to the criteria. - public virtual IAsyncEnumerable FindAsync( - string subject, string client, - string status, string type, - ImmutableArray scopes, CancellationToken cancellationToken = default) - { - if (string.IsNullOrEmpty(subject)) - { - throw new ArgumentException(SR.GetResourceString(SR.ID0198), nameof(subject)); - } - - if (string.IsNullOrEmpty(client)) - { - throw new ArgumentException(SR.GetResourceString(SR.ID0124), nameof(client)); - } - - if (string.IsNullOrEmpty(status)) - { - throw new ArgumentException(SR.GetResourceString(SR.ID0199), nameof(status)); - } - - if (string.IsNullOrEmpty(type)) - { - throw new ArgumentException(SR.GetResourceString(SR.ID0200), nameof(type)); - } - var authorizations = Options.CurrentValue.DisableEntityCaching ? Store.FindAsync(subject, client, status, type, scopes, cancellationToken) : Cache.FindAsync(subject, client, status, type, scopes, cancellationToken); @@ -491,12 +305,13 @@ async IAsyncEnumerable ExecuteAsync([EnumeratorCancellation] Can { await foreach (var authorization in authorizations) { - if (!string.Equals(await Store.GetSubjectAsync(authorization, cancellationToken), subject, StringComparison.Ordinal)) + if (!string.IsNullOrEmpty(subject) && + !string.Equals(await Store.GetSubjectAsync(authorization, cancellationToken), subject, StringComparison.Ordinal)) { continue; } - if (!await HasScopesAsync(authorization, scopes, cancellationToken)) + if (scopes is not null && !await HasScopesAsync(authorization, scopes.Value, cancellationToken)) { continue; } @@ -1028,90 +843,17 @@ public virtual async ValueTask PopulateAsync( public virtual ValueTask PruneAsync(DateTimeOffset threshold, CancellationToken cancellationToken = default) => Store.PruneAsync(threshold, cancellationToken); - /// - /// Revokes all the authorizations corresponding to the specified - /// subject and associated with the application identifier. - /// - /// The subject associated with the authorization. - /// The client associated with the authorization. - /// The that can be used to abort the operation. - /// The number of authorizations corresponding to the criteria that were marked as revoked. - public virtual ValueTask RevokeAsync(string subject, string client, CancellationToken cancellationToken = default) - { - if (string.IsNullOrEmpty(subject)) - { - throw new ArgumentException(SR.GetResourceString(SR.ID0198), nameof(subject)); - } - - if (string.IsNullOrEmpty(client)) - { - throw new ArgumentException(SR.GetResourceString(SR.ID0124), nameof(client)); - } - - return Store.RevokeAsync(subject, client, cancellationToken); - } - /// /// Revokes all the authorizations matching the specified parameters. /// - /// The subject associated with the authorization. - /// The client associated with the authorization. - /// The authorization status. + /// The subject associated with the authorization, or not to filter out specific subjects. + /// The client associated with the authorization, or not to filter out specific clients. + /// The authorization status, or not to filter out specific authorization statuses. + /// The authorization type, or not to filter out specific authorization types. /// The that can be used to abort the operation. /// The number of authorizations corresponding to the criteria that were marked as revoked. - public virtual ValueTask RevokeAsync(string subject, string client, string status, CancellationToken cancellationToken = default) - { - if (string.IsNullOrEmpty(subject)) - { - throw new ArgumentException(SR.GetResourceString(SR.ID0198), nameof(subject)); - } - - if (string.IsNullOrEmpty(client)) - { - throw new ArgumentException(SR.GetResourceString(SR.ID0124), nameof(client)); - } - - if (string.IsNullOrEmpty(status)) - { - throw new ArgumentException(SR.GetResourceString(SR.ID0199), nameof(status)); - } - - return Store.RevokeAsync(subject, client, status, cancellationToken); - } - - /// - /// Revokes all the authorizations matching the specified parameters. - /// - /// The subject associated with the authorization. - /// The client associated with the authorization. - /// The authorization status. - /// The authorization type. - /// The that can be used to abort the operation. - /// The number of authorizations corresponding to the criteria that were marked as revoked. - public virtual ValueTask RevokeAsync(string subject, string client, string status, string type, CancellationToken cancellationToken = default) - { - if (string.IsNullOrEmpty(subject)) - { - throw new ArgumentException(SR.GetResourceString(SR.ID0198), nameof(subject)); - } - - if (string.IsNullOrEmpty(client)) - { - throw new ArgumentException(SR.GetResourceString(SR.ID0124), nameof(client)); - } - - if (string.IsNullOrEmpty(status)) - { - throw new ArgumentException(SR.GetResourceString(SR.ID0199), nameof(status)); - } - - if (string.IsNullOrEmpty(type)) - { - throw new ArgumentException(SR.GetResourceString(SR.ID0200), nameof(type)); - } - - return Store.RevokeAsync(subject, client, status, type, cancellationToken); - } + public virtual ValueTask RevokeAsync(string? subject, string? client, string? status, string? type, CancellationToken cancellationToken = default) + => Store.RevokeAsync(subject, client, status, type, cancellationToken); /// /// Revokes all the authorizations associated with the specified application identifier. @@ -1352,19 +1094,7 @@ ValueTask IOpenIddictAuthorizationManager.DeleteAsync(object authorization, Canc => DeleteAsync((TAuthorization) authorization, cancellationToken); /// - IAsyncEnumerable IOpenIddictAuthorizationManager.FindAsync(string subject, string client, CancellationToken cancellationToken) - => FindAsync(subject, client, cancellationToken); - - /// - IAsyncEnumerable IOpenIddictAuthorizationManager.FindAsync(string subject, string client, string status, CancellationToken cancellationToken) - => FindAsync(subject, client, status, cancellationToken); - - /// - IAsyncEnumerable IOpenIddictAuthorizationManager.FindAsync(string subject, string client, string status, string type, CancellationToken cancellationToken) - => FindAsync(subject, client, status, type, cancellationToken); - - /// - IAsyncEnumerable IOpenIddictAuthorizationManager.FindAsync(string subject, string client, string status, string type, ImmutableArray scopes, CancellationToken cancellationToken) + IAsyncEnumerable IOpenIddictAuthorizationManager.FindAsync(string? subject, string? client, string? status, string? type, ImmutableArray? scopes, CancellationToken cancellationToken) => FindAsync(subject, client, status, type, scopes, cancellationToken); /// @@ -1455,15 +1185,7 @@ ValueTask IOpenIddictAuthorizationManager.PruneAsync(DateTimeOffset thresh => PruneAsync(threshold, cancellationToken); /// - ValueTask IOpenIddictAuthorizationManager.RevokeAsync(string subject, string client, CancellationToken cancellationToken) - => RevokeAsync(subject, client, cancellationToken); - - /// - ValueTask IOpenIddictAuthorizationManager.RevokeAsync(string subject, string client, string status, CancellationToken cancellationToken) - => RevokeAsync(subject, client, status, cancellationToken); - - /// - ValueTask IOpenIddictAuthorizationManager.RevokeAsync(string subject, string client, string status, string type, CancellationToken cancellationToken) + ValueTask IOpenIddictAuthorizationManager.RevokeAsync(string? subject, string? client, string? status, string? type, CancellationToken cancellationToken) => RevokeAsync(subject, client, status, type, cancellationToken); /// diff --git a/src/OpenIddict.Core/Managers/OpenIddictTokenManager.cs b/src/OpenIddict.Core/Managers/OpenIddictTokenManager.cs index c16ba7269..54529e759 100644 --- a/src/OpenIddict.Core/Managers/OpenIddictTokenManager.cs +++ b/src/OpenIddict.Core/Managers/OpenIddictTokenManager.cs @@ -205,141 +205,19 @@ public virtual async ValueTask DeleteAsync(TToken token, CancellationToken cance await Store.DeleteAsync(token, cancellationToken); } - /// - /// Retrieves the tokens corresponding to the specified - /// subject and associated with the application identifier. - /// - /// The subject associated with the token. - /// The client associated with the token. - /// The that can be used to abort the operation. - /// The tokens corresponding to the subject/client. - public virtual IAsyncEnumerable FindAsync(string subject, - string client, CancellationToken cancellationToken = default) - { - if (string.IsNullOrEmpty(subject)) - { - throw new ArgumentException(SR.GetResourceString(SR.ID0198), nameof(subject)); - } - - if (string.IsNullOrEmpty(client)) - { - throw new ArgumentException(SR.GetResourceString(SR.ID0124), nameof(client)); - } - - var tokens = Options.CurrentValue.DisableEntityCaching ? - Store.FindAsync(subject, client, cancellationToken) : - Cache.FindAsync(subject, client, cancellationToken); - - if (Options.CurrentValue.DisableAdditionalFiltering) - { - return tokens; - } - - // SQL engines like Microsoft SQL Server or MySQL are known to use case-insensitive lookups by default. - // To ensure a case-sensitive comparison is enforced independently of the database/table/query collation - // used by the store, a second pass using string.Equals(StringComparison.Ordinal) is manually made here. - - return ExecuteAsync(cancellationToken); - - async IAsyncEnumerable ExecuteAsync([EnumeratorCancellation] CancellationToken cancellationToken) - { - await foreach (var token in tokens) - { - if (string.Equals(await Store.GetSubjectAsync(token, cancellationToken), subject, StringComparison.Ordinal)) - { - yield return token; - } - } - } - } - - /// - /// Retrieves the tokens matching the specified parameters. - /// - /// The subject associated with the token. - /// The client associated with the token. - /// The token status. - /// The that can be used to abort the operation. - /// The tokens corresponding to the criteria. - public virtual IAsyncEnumerable FindAsync( - string subject, string client, - string status, CancellationToken cancellationToken = default) - { - if (string.IsNullOrEmpty(subject)) - { - throw new ArgumentException(SR.GetResourceString(SR.ID0198), nameof(subject)); - } - - if (string.IsNullOrEmpty(client)) - { - throw new ArgumentException(SR.GetResourceString(SR.ID0124), nameof(client)); - } - - if (string.IsNullOrEmpty(status)) - { - throw new ArgumentException(SR.GetResourceString(SR.ID0199), nameof(status)); - } - - var tokens = Options.CurrentValue.DisableEntityCaching ? - Store.FindAsync(subject, client, status, cancellationToken) : - Cache.FindAsync(subject, client, status, cancellationToken); - - if (Options.CurrentValue.DisableAdditionalFiltering) - { - return tokens; - } - - // SQL engines like Microsoft SQL Server or MySQL are known to use case-insensitive lookups by default. - // To ensure a case-sensitive comparison is enforced independently of the database/table/query collation - // used by the store, a second pass using string.Equals(StringComparison.Ordinal) is manually made here. - - return ExecuteAsync(cancellationToken); - - async IAsyncEnumerable ExecuteAsync([EnumeratorCancellation] CancellationToken cancellationToken) - { - await foreach (var token in tokens) - { - if (string.Equals(await Store.GetSubjectAsync(token, cancellationToken), subject, StringComparison.Ordinal)) - { - yield return token; - } - } - } - } - /// /// Retrieves the tokens matching the specified parameters. /// - /// The subject associated with the token. - /// The client associated with the token. - /// The token status. - /// The token type. + /// The subject associated with the token, or not to filter out specific subjects. + /// The client associated with the token, or not to filter out specific clients. + /// The token status, or not to filter out specific token statuses. + /// The token type, or not to filter out specific token types. /// The that can be used to abort the operation. /// Tokens corresponding to the criteria. public virtual IAsyncEnumerable FindAsync( - string subject, string client, - string status, string type, CancellationToken cancellationToken = default) + string? subject, string? client, + string? status, string? type, CancellationToken cancellationToken = default) { - if (string.IsNullOrEmpty(subject)) - { - throw new ArgumentException(SR.GetResourceString(SR.ID0198), nameof(subject)); - } - - if (string.IsNullOrEmpty(client)) - { - throw new ArgumentException(SR.GetResourceString(SR.ID0124), nameof(client)); - } - - if (string.IsNullOrEmpty(status)) - { - throw new ArgumentException(SR.GetResourceString(SR.ID0199), nameof(status)); - } - - if (string.IsNullOrEmpty(type)) - { - throw new ArgumentException(SR.GetResourceString(SR.ID0200), nameof(type)); - } - var tokens = Options.CurrentValue.DisableEntityCaching ? Store.FindAsync(subject, client, status, type, cancellationToken) : Cache.FindAsync(subject, client, status, type, cancellationToken); @@ -359,7 +237,8 @@ async IAsyncEnumerable ExecuteAsync([EnumeratorCancellation] Cancellatio { await foreach (var token in tokens) { - if (string.Equals(await Store.GetSubjectAsync(token, cancellationToken), subject, StringComparison.Ordinal)) + if (string.IsNullOrEmpty(subject) || + string.Equals(await Store.GetSubjectAsync(token, cancellationToken), subject, StringComparison.Ordinal)) { yield return token; } @@ -1055,90 +934,17 @@ public virtual async ValueTask PopulateAsync( public virtual ValueTask PruneAsync(DateTimeOffset threshold, CancellationToken cancellationToken = default) => Store.PruneAsync(threshold, cancellationToken); - /// - /// Revokes all the tokens corresponding to the specified - /// subject and associated with the application identifier. - /// - /// The subject associated with the token. - /// The client associated with the token. - /// The that can be used to abort the operation. - /// The number of tokens corresponding to the criteria that were marked as revoked. - public virtual ValueTask RevokeAsync(string subject, string client, CancellationToken cancellationToken = default) - { - if (string.IsNullOrEmpty(subject)) - { - throw new ArgumentException(SR.GetResourceString(SR.ID0198), nameof(subject)); - } - - if (string.IsNullOrEmpty(client)) - { - throw new ArgumentException(SR.GetResourceString(SR.ID0124), nameof(client)); - } - - return Store.RevokeAsync(subject, client, cancellationToken); - } - - /// - /// Revokes all the tokens matching the specified parameters. - /// - /// The subject associated with the token. - /// The client associated with the token. - /// The token status. - /// The that can be used to abort the operation. - /// The number of tokens corresponding to the criteria that were marked as revoked. - public virtual ValueTask RevokeAsync(string subject, string client, string status, CancellationToken cancellationToken = default) - { - if (string.IsNullOrEmpty(subject)) - { - throw new ArgumentException(SR.GetResourceString(SR.ID0198), nameof(subject)); - } - - if (string.IsNullOrEmpty(client)) - { - throw new ArgumentException(SR.GetResourceString(SR.ID0124), nameof(client)); - } - - if (string.IsNullOrEmpty(status)) - { - throw new ArgumentException(SR.GetResourceString(SR.ID0199), nameof(status)); - } - - return Store.RevokeAsync(subject, client, status, cancellationToken); - } - /// /// Revokes all the tokens matching the specified parameters. /// - /// The subject associated with the token. - /// The client associated with the token. - /// The token status. - /// The token type. + /// The subject associated with the token, or not to filter out specific subjects. + /// The client associated with the token, or not to filter out specific clients. + /// The token status, or not to filter out specific token statuses. + /// The token type, or not to filter out specific token types. /// The that can be used to abort the operation. /// The number of tokens corresponding to the criteria that were marked as revoked. - public virtual ValueTask RevokeAsync(string subject, string client, string status, string type, CancellationToken cancellationToken = default) - { - if (string.IsNullOrEmpty(subject)) - { - throw new ArgumentException(SR.GetResourceString(SR.ID0198), nameof(subject)); - } - - if (string.IsNullOrEmpty(client)) - { - throw new ArgumentException(SR.GetResourceString(SR.ID0124), nameof(client)); - } - - if (string.IsNullOrEmpty(status)) - { - throw new ArgumentException(SR.GetResourceString(SR.ID0199), nameof(status)); - } - - if (string.IsNullOrEmpty(type)) - { - throw new ArgumentException(SR.GetResourceString(SR.ID0200), nameof(type)); - } - - return Store.RevokeAsync(subject, client, status, type, cancellationToken); - } + public virtual ValueTask RevokeAsync(string? subject, string? client, string? status, string? type, CancellationToken cancellationToken = default) + => Store.RevokeAsync(subject, client, status, type, cancellationToken); /// /// Revokes all the tokens associated with the specified application identifier. @@ -1495,15 +1301,7 @@ ValueTask IOpenIddictTokenManager.DeleteAsync(object token, CancellationToken ca => DeleteAsync((TToken) token, cancellationToken); /// - IAsyncEnumerable IOpenIddictTokenManager.FindAsync(string subject, string client, CancellationToken cancellationToken) - => FindAsync(subject, client, cancellationToken); - - /// - IAsyncEnumerable IOpenIddictTokenManager.FindAsync(string subject, string client, string status, CancellationToken cancellationToken) - => FindAsync(subject, client, status, cancellationToken); - - /// - IAsyncEnumerable IOpenIddictTokenManager.FindAsync(string subject, string client, string status, string type, CancellationToken cancellationToken) + IAsyncEnumerable IOpenIddictTokenManager.FindAsync(string? subject, string? client, string? status, string? type, CancellationToken cancellationToken) => FindAsync(subject, client, status, type, cancellationToken); /// @@ -1619,15 +1417,7 @@ ValueTask IOpenIddictTokenManager.PruneAsync(DateTimeOffset threshold, Can => PruneAsync(threshold, cancellationToken); /// - ValueTask IOpenIddictTokenManager.RevokeAsync(string subject, string client, CancellationToken cancellationToken) - => RevokeAsync(subject, client, cancellationToken); - - /// - ValueTask IOpenIddictTokenManager.RevokeAsync(string subject, string client, string status, CancellationToken cancellationToken) - => RevokeAsync(subject, client, status, cancellationToken); - - /// - ValueTask IOpenIddictTokenManager.RevokeAsync(string subject, string client, string status, string type, CancellationToken cancellationToken) + ValueTask IOpenIddictTokenManager.RevokeAsync(string? subject, string? client, string? status, string? type, CancellationToken cancellationToken) => RevokeAsync(subject, client, status, type, cancellationToken); /// diff --git a/src/OpenIddict.EntityFramework/Stores/OpenIddictEntityFrameworkAuthorizationStore.cs b/src/OpenIddict.EntityFramework/Stores/OpenIddictEntityFrameworkAuthorizationStore.cs index b0ba2e067..434fe2119 100644 --- a/src/OpenIddict.EntityFramework/Stores/OpenIddictEntityFrameworkAuthorizationStore.cs +++ b/src/OpenIddict.EntityFramework/Stores/OpenIddictEntityFrameworkAuthorizationStore.cs @@ -170,138 +170,42 @@ Task> ListTokensAsync() } /// - public virtual IAsyncEnumerable FindAsync( - string subject, string client, CancellationToken cancellationToken) + public virtual async IAsyncEnumerable FindAsync( + string? subject, string? client, + string? status, string? type, + ImmutableArray? scopes, [EnumeratorCancellation] CancellationToken cancellationToken) { - if (string.IsNullOrEmpty(subject)) - { - throw new ArgumentException(SR.GetResourceString(SR.ID0198), nameof(subject)); - } - - if (string.IsNullOrEmpty(client)) - { - throw new ArgumentException(SR.GetResourceString(SR.ID0124), nameof(client)); - } - - var key = ConvertIdentifierFromString(client); - - return (from authorization in Authorizations.Include(authorization => authorization.Application) - where authorization.Application!.Id!.Equals(key) && - authorization.Subject == subject - select authorization).AsAsyncEnumerable(cancellationToken); - } - - /// - public virtual IAsyncEnumerable FindAsync( - string subject, string client, - string status, CancellationToken cancellationToken) - { - if (string.IsNullOrEmpty(subject)) - { - throw new ArgumentException(SR.GetResourceString(SR.ID0198), nameof(subject)); - } - - if (string.IsNullOrEmpty(client)) - { - throw new ArgumentException(SR.GetResourceString(SR.ID0124), nameof(client)); - } - - if (string.IsNullOrEmpty(status)) - { - throw new ArgumentException(SR.GetResourceString(SR.ID0199), nameof(status)); - } - - var key = ConvertIdentifierFromString(client); - - return (from authorization in Authorizations.Include(authorization => authorization.Application) - where authorization.Application!.Id!.Equals(key) && - authorization.Subject == subject && - authorization.Status == status - select authorization).AsAsyncEnumerable(cancellationToken); - } - - /// - public virtual IAsyncEnumerable FindAsync( - string subject, string client, - string status, string type, CancellationToken cancellationToken) - { - if (string.IsNullOrEmpty(subject)) - { - throw new ArgumentException(SR.GetResourceString(SR.ID0198), nameof(subject)); - } - - if (string.IsNullOrEmpty(client)) - { - throw new ArgumentException(SR.GetResourceString(SR.ID0124), nameof(client)); - } + IQueryable query = Authorizations.Include(authorization => authorization.Application); - if (string.IsNullOrEmpty(status)) + if (!string.IsNullOrEmpty(subject)) { - throw new ArgumentException(SR.GetResourceString(SR.ID0199), nameof(status)); + query = query.Where(authorization => authorization.Subject == subject); } - if (string.IsNullOrEmpty(type)) + if (!string.IsNullOrEmpty(client)) { - throw new ArgumentException(SR.GetResourceString(SR.ID0200), nameof(type)); - } - - var key = ConvertIdentifierFromString(client); - - return (from authorization in Authorizations.Include(authorization => authorization.Application) - where authorization.Application!.Id!.Equals(key) && - authorization.Subject == subject && - authorization.Status == status && - authorization.Type == type - select authorization).AsAsyncEnumerable(cancellationToken); - } - - /// - public virtual IAsyncEnumerable FindAsync( - string subject, string client, - string status, string type, - ImmutableArray scopes, CancellationToken cancellationToken) - { - if (string.IsNullOrEmpty(subject)) - { - throw new ArgumentException(SR.GetResourceString(SR.ID0198), nameof(subject)); - } + var key = ConvertIdentifierFromString(client); - if (string.IsNullOrEmpty(client)) - { - throw new ArgumentException(SR.GetResourceString(SR.ID0124), nameof(client)); + query = query.Where(authorization => authorization.Application!.Id!.Equals(key)); } - if (string.IsNullOrEmpty(status)) + if (!string.IsNullOrEmpty(status)) { - throw new ArgumentException(SR.GetResourceString(SR.ID0199), nameof(status)); + query = query.Where(authorization => authorization.Status == status); } - if (string.IsNullOrEmpty(type)) + if (!string.IsNullOrEmpty(type)) { - throw new ArgumentException(SR.GetResourceString(SR.ID0200), nameof(type)); + query = query.Where(authorization => authorization.Type == type); } - return ExecuteAsync(cancellationToken); - - async IAsyncEnumerable ExecuteAsync([EnumeratorCancellation] CancellationToken cancellationToken) + await foreach (var authorization in query.AsAsyncEnumerable(cancellationToken)) { - var key = ConvertIdentifierFromString(client); - - var authorizations = (from authorization in Authorizations.Include(authorization => authorization.Application) - where authorization.Application!.Id!.Equals(key) && - authorization.Subject == subject && - authorization.Status == status && - authorization.Type == type - select authorization).AsAsyncEnumerable(cancellationToken); - - await foreach (var authorization in authorizations) + if (scopes is null || (await GetScopesAsync(authorization, cancellationToken)) + .ToHashSet(StringComparer.Ordinal) + .IsSupersetOf(scopes)) { - if ((await GetScopesAsync(authorization, cancellationToken)) - .ToHashSet(StringComparer.Ordinal) - .IsSupersetOf(scopes)) - { - yield return authorization; - } + yield return authorization; } } } @@ -659,151 +563,37 @@ orderby authorization.Id } /// - public virtual async ValueTask RevokeAsync(string subject, string client, CancellationToken cancellationToken) - { - if (string.IsNullOrEmpty(subject)) - { - throw new ArgumentException(SR.GetResourceString(SR.ID0198), nameof(subject)); - } - - if (string.IsNullOrEmpty(client)) - { - throw new ArgumentException(SR.GetResourceString(SR.ID0124), nameof(client)); - } - - var key = ConvertIdentifierFromString(client); - - List? exceptions = null; - - var result = 0L; - - foreach (var authorization in await (from authorization in Authorizations - where authorization.Application!.Id!.Equals(key) && authorization.Subject == subject - select authorization).ToListAsync(cancellationToken)) - { - authorization.Status = Statuses.Revoked; - - try - { - await Context.SaveChangesAsync(cancellationToken); - } - - catch (Exception exception) when (!OpenIddictHelpers.IsFatal(exception)) - { - // Reset the state of the entity to prevents future calls to SaveChangesAsync() from failing. - Context.Entry(authorization).State = EntityState.Unchanged; - - exceptions ??= []; - exceptions.Add(exception); - - continue; - } - - result++; - } - - if (exceptions is not null) - { - throw new AggregateException(SR.GetResourceString(SR.ID0249), exceptions); - } - - return result; - } - - /// - public virtual async ValueTask RevokeAsync(string subject, string client, string status, CancellationToken cancellationToken) + public virtual async ValueTask RevokeAsync(string? subject, string? client, string? status, string? type, CancellationToken cancellationToken) { - if (string.IsNullOrEmpty(subject)) - { - throw new ArgumentException(SR.GetResourceString(SR.ID0198), nameof(subject)); - } - - if (string.IsNullOrEmpty(client)) - { - throw new ArgumentException(SR.GetResourceString(SR.ID0124), nameof(client)); - } - - if (string.IsNullOrEmpty(status)) - { - throw new ArgumentException(SR.GetResourceString(SR.ID0199), nameof(status)); - } - - var key = ConvertIdentifierFromString(client); - - List? exceptions = null; - - var result = 0L; - - foreach (var authorization in await (from authorization in Authorizations - where authorization.Application!.Id!.Equals(key) && - authorization.Subject == subject && - authorization.Status == status - select authorization).ToListAsync(cancellationToken)) - { - authorization.Status = Statuses.Revoked; - - try - { - await Context.SaveChangesAsync(cancellationToken); - } - - catch (Exception exception) when (!OpenIddictHelpers.IsFatal(exception)) - { - // Reset the state of the entity to prevents future calls to SaveChangesAsync() from failing. - Context.Entry(authorization).State = EntityState.Unchanged; - - exceptions ??= []; - exceptions.Add(exception); + IQueryable query = Authorizations.Include(authorization => authorization.Application); - continue; - } - - result++; - } - - if (exceptions is not null) + if (!string.IsNullOrEmpty(subject)) { - throw new AggregateException(SR.GetResourceString(SR.ID0249), exceptions); + query = query.Where(authorization => authorization.Subject == subject); } - return result; - } - - /// - public virtual async ValueTask RevokeAsync(string subject, string client, string status, string type, CancellationToken cancellationToken) - { - if (string.IsNullOrEmpty(subject)) + if (!string.IsNullOrEmpty(client)) { - throw new ArgumentException(SR.GetResourceString(SR.ID0198), nameof(subject)); - } + var key = ConvertIdentifierFromString(client); - if (string.IsNullOrEmpty(client)) - { - throw new ArgumentException(SR.GetResourceString(SR.ID0124), nameof(client)); + query = query.Where(authorization => authorization.Application!.Id!.Equals(key)); } - if (string.IsNullOrEmpty(status)) + if (!string.IsNullOrEmpty(status)) { - throw new ArgumentException(SR.GetResourceString(SR.ID0199), nameof(status)); + query = query.Where(authorization => authorization.Status == status); } - if (string.IsNullOrEmpty(type)) + if (!string.IsNullOrEmpty(type)) { - throw new ArgumentException(SR.GetResourceString(SR.ID0200), nameof(type)); + query = query.Where(authorization => authorization.Type == type); } - var key = ConvertIdentifierFromString(client); - List? exceptions = null; var result = 0L; - foreach (var authorization in await (from authorization in Authorizations - where authorization.Application!.Id!.Equals(key) && - authorization.Subject == subject && - authorization.Status == status && - authorization.Type == type - select authorization).ToListAsync(cancellationToken)) + foreach (var authorization in await query.ToListAsync(cancellationToken)) { authorization.Status = Statuses.Revoked; @@ -848,7 +638,7 @@ public virtual async ValueTask RevokeByApplicationIdAsync(string identifie var result = 0L; - foreach (var authorization in await (from authorization in Authorizations + foreach (var authorization in await (from authorization in Authorizations.Include(authorization => authorization.Application) where authorization.Application!.Id!.Equals(key) select authorization).ToListAsync(cancellationToken)) { @@ -893,7 +683,7 @@ public virtual async ValueTask RevokeBySubjectAsync(string subject, Cancel var result = 0L; - foreach (var authorization in await (from authorization in Authorizations + foreach (var authorization in await (from authorization in Authorizations.Include(authorization => authorization.Application) where authorization.Subject == subject select authorization).ToListAsync(cancellationToken)) { diff --git a/src/OpenIddict.EntityFramework/Stores/OpenIddictEntityFrameworkTokenStore.cs b/src/OpenIddict.EntityFramework/Stores/OpenIddictEntityFrameworkTokenStore.cs index 56edf7bb3..44cbe134b 100644 --- a/src/OpenIddict.EntityFramework/Stores/OpenIddictEntityFrameworkTokenStore.cs +++ b/src/OpenIddict.EntityFramework/Stores/OpenIddictEntityFrameworkTokenStore.cs @@ -145,90 +145,36 @@ public virtual async ValueTask DeleteAsync(TToken token, CancellationToken cance } } - /// - public virtual IAsyncEnumerable FindAsync(string subject, - string client, CancellationToken cancellationToken) - { - if (string.IsNullOrEmpty(subject)) - { - throw new ArgumentException(SR.GetResourceString(SR.ID0198), nameof(subject)); - } - - if (string.IsNullOrEmpty(client)) - { - throw new ArgumentException(SR.GetResourceString(SR.ID0124), nameof(client)); - } - - var key = ConvertIdentifierFromString(client); - - return (from token in Tokens.Include(token => token.Application).Include(token => token.Authorization) - where token.Application!.Id!.Equals(key) && - token.Subject == subject - select token).AsAsyncEnumerable(cancellationToken); - } - /// public virtual IAsyncEnumerable FindAsync( - string subject, string client, - string status, CancellationToken cancellationToken) + string? subject, string? client, + string? status, string? type, CancellationToken cancellationToken) { - if (string.IsNullOrEmpty(subject)) - { - throw new ArgumentException(SR.GetResourceString(SR.ID0198), nameof(subject)); - } + IQueryable query = Tokens.Include(token => token.Application).Include(token => token.Authorization); - if (string.IsNullOrEmpty(client)) + if (!string.IsNullOrEmpty(subject)) { - throw new ArgumentException(SR.GetResourceString(SR.ID0124), nameof(client)); + query = query.Where(token => token.Subject == subject); } - if (string.IsNullOrEmpty(status)) + if (!string.IsNullOrEmpty(client)) { - throw new ArgumentException(SR.GetResourceString(SR.ID0199), nameof(status)); - } + var key = ConvertIdentifierFromString(client); - var key = ConvertIdentifierFromString(client); - - return (from token in Tokens.Include(token => token.Application).Include(token => token.Authorization) - where token.Application!.Id!.Equals(key) && - token.Subject == subject && - token.Status == status - select token).AsAsyncEnumerable(cancellationToken); - } - - /// - public virtual IAsyncEnumerable FindAsync( - string subject, string client, - string status, string type, CancellationToken cancellationToken) - { - if (string.IsNullOrEmpty(subject)) - { - throw new ArgumentException(SR.GetResourceString(SR.ID0198), nameof(subject)); - } - - if (string.IsNullOrEmpty(client)) - { - throw new ArgumentException(SR.GetResourceString(SR.ID0124), nameof(client)); + query = query.Where(token => token.Application!.Id!.Equals(key)); } - if (string.IsNullOrEmpty(status)) + if (!string.IsNullOrEmpty(status)) { - throw new ArgumentException(SR.GetResourceString(SR.ID0199), nameof(status)); + query = query.Where(token => token.Status == status); } - if (string.IsNullOrEmpty(type)) + if (!string.IsNullOrEmpty(type)) { - throw new ArgumentException(SR.GetResourceString(SR.ID0200), nameof(type)); + query = query.Where(token => token.Type == type); } - var key = ConvertIdentifierFromString(client); - - return (from token in Tokens.Include(token => token.Application).Include(token => token.Authorization) - where token.Application!.Id!.Equals(key) && - token.Subject == subject && - token.Status == status && - token.Type == type - select token).AsAsyncEnumerable(cancellationToken); + return query.AsAsyncEnumerable(cancellationToken); } /// @@ -660,151 +606,37 @@ orderby token.Id } /// - public virtual async ValueTask RevokeAsync(string subject, string client, CancellationToken cancellationToken) + public virtual async ValueTask RevokeAsync(string? subject, string? client, string? status, string? type, CancellationToken cancellationToken) { - if (string.IsNullOrEmpty(subject)) - { - throw new ArgumentException(SR.GetResourceString(SR.ID0198), nameof(subject)); - } + IQueryable query = Tokens.Include(token => token.Application).Include(token => token.Authorization); - if (string.IsNullOrEmpty(client)) + if (!string.IsNullOrEmpty(subject)) { - throw new ArgumentException(SR.GetResourceString(SR.ID0124), nameof(client)); + query = query.Where(token => token.Subject == subject); } - var key = ConvertIdentifierFromString(client); - - List? exceptions = null; - - var result = 0L; - - foreach (var token in await (from token in Tokens - where token.Application!.Id!.Equals(key) && token.Subject == subject - select token).ToListAsync(cancellationToken)) + if (!string.IsNullOrEmpty(client)) { - token.Status = Statuses.Revoked; - - try - { - await Context.SaveChangesAsync(cancellationToken); - } + var key = ConvertIdentifierFromString(client); - catch (Exception exception) when (!OpenIddictHelpers.IsFatal(exception)) - { - // Reset the state of the entity to prevents future calls to SaveChangesAsync() from failing. - Context.Entry(token).State = EntityState.Unchanged; - - exceptions ??= []; - exceptions.Add(exception); - - continue; - } - - result++; + query = query.Where(token => token.Application!.Id!.Equals(key)); } - if (exceptions is not null) + if (!string.IsNullOrEmpty(status)) { - throw new AggregateException(SR.GetResourceString(SR.ID0249), exceptions); + query = query.Where(token => token.Status == status); } - return result; - } - - /// - public virtual async ValueTask RevokeAsync(string subject, string client, string status, CancellationToken cancellationToken) - { - if (string.IsNullOrEmpty(subject)) + if (!string.IsNullOrEmpty(type)) { - throw new ArgumentException(SR.GetResourceString(SR.ID0198), nameof(subject)); + query = query.Where(token => token.Type == type); } - if (string.IsNullOrEmpty(client)) - { - throw new ArgumentException(SR.GetResourceString(SR.ID0124), nameof(client)); - } - - if (string.IsNullOrEmpty(status)) - { - throw new ArgumentException(SR.GetResourceString(SR.ID0199), nameof(status)); - } - - var key = ConvertIdentifierFromString(client); - List? exceptions = null; var result = 0L; - foreach (var token in await (from token in Tokens - where token.Application!.Id!.Equals(key) && - token.Subject == subject && - token.Status == status - select token).ToListAsync(cancellationToken)) - { - token.Status = Statuses.Revoked; - - try - { - await Context.SaveChangesAsync(cancellationToken); - } - - catch (Exception exception) when (!OpenIddictHelpers.IsFatal(exception)) - { - // Reset the state of the entity to prevents future calls to SaveChangesAsync() from failing. - Context.Entry(token).State = EntityState.Unchanged; - - exceptions ??= []; - exceptions.Add(exception); - - continue; - } - - result++; - } - - if (exceptions is not null) - { - throw new AggregateException(SR.GetResourceString(SR.ID0249), exceptions); - } - - return result; - } - - /// - public virtual async ValueTask RevokeAsync(string subject, string client, string status, string type, CancellationToken cancellationToken) - { - if (string.IsNullOrEmpty(subject)) - { - throw new ArgumentException(SR.GetResourceString(SR.ID0198), nameof(subject)); - } - - if (string.IsNullOrEmpty(client)) - { - throw new ArgumentException(SR.GetResourceString(SR.ID0124), nameof(client)); - } - - if (string.IsNullOrEmpty(status)) - { - throw new ArgumentException(SR.GetResourceString(SR.ID0199), nameof(status)); - } - - if (string.IsNullOrEmpty(type)) - { - throw new ArgumentException(SR.GetResourceString(SR.ID0200), nameof(type)); - } - - var key = ConvertIdentifierFromString(client); - - List? exceptions = null; - - var result = 0L; - - foreach (var token in await (from token in Tokens - where token.Application!.Id!.Equals(key) && - token.Subject == subject && - token.Status == status && - token.Type == type - select token).ToListAsync(cancellationToken)) + foreach (var token in await query.ToListAsync(cancellationToken)) { token.Status = Statuses.Revoked; @@ -849,7 +681,7 @@ public virtual async ValueTask RevokeByApplicationIdAsync(string identifie var result = 0L; - foreach (var token in await (from token in Tokens + foreach (var token in await (from token in Tokens.Include(token => token.Application).Include(token => token.Authorization) where token.Application!.Id!.Equals(key) select token).ToListAsync(cancellationToken)) { @@ -896,7 +728,7 @@ public virtual async ValueTask RevokeByAuthorizationIdAsync(string identif var result = 0L; - foreach (var token in await (from token in Tokens + foreach (var token in await (from token in Tokens.Include(token => token.Application).Include(token => token.Authorization) where token.Authorization!.Id!.Equals(key) select token).ToListAsync(cancellationToken)) { @@ -941,7 +773,7 @@ public virtual async ValueTask RevokeBySubjectAsync(string subject, Cancel var result = 0L; - foreach (var token in await (from token in Tokens + foreach (var token in await (from token in Tokens.Include(token => token.Application).Include(token => token.Authorization) where token.Subject == subject select token).ToListAsync(cancellationToken)) { diff --git a/src/OpenIddict.EntityFrameworkCore/Stores/OpenIddictEntityFrameworkCoreAuthorizationStore.cs b/src/OpenIddict.EntityFrameworkCore/Stores/OpenIddictEntityFrameworkCoreAuthorizationStore.cs index 71bb8d541..c4eb5a47a 100644 --- a/src/OpenIddict.EntityFrameworkCore/Stores/OpenIddictEntityFrameworkCoreAuthorizationStore.cs +++ b/src/OpenIddict.EntityFrameworkCore/Stores/OpenIddictEntityFrameworkCoreAuthorizationStore.cs @@ -241,165 +241,50 @@ await strategy.ExecuteAsync(async () => } /// - public virtual IAsyncEnumerable FindAsync( - string subject, string client, CancellationToken cancellationToken) + public virtual async IAsyncEnumerable FindAsync( + string? subject, string? client, + string? status, string? type, + ImmutableArray? scopes, [EnumeratorCancellation] CancellationToken cancellationToken) { - if (string.IsNullOrEmpty(subject)) - { - throw new ArgumentException(SR.GetResourceString(SR.ID0198), nameof(subject)); - } - - if (string.IsNullOrEmpty(client)) - { - throw new ArgumentException(SR.GetResourceString(SR.ID0124), nameof(client)); - } - - // Note: due to a bug in Entity Framework Core's query visitor, the authorizations - // can't be filtered using authorization.Application.Id.Equals(key). To work around - // this issue, this query uses use an explicit join to apply the equality check. - // - // See https://github.com/openiddict/openiddict-core/issues/499 for more information. - - var key = ConvertIdentifierFromString(client); - - return (from authorization in Authorizations.Include(authorization => authorization.Application).AsTracking() - where authorization.Subject == subject - join application in Applications.AsTracking() on authorization.Application!.Id equals application.Id - where application.Id!.Equals(key) - select authorization).AsAsyncEnumerable(cancellationToken); - } - - /// - public virtual IAsyncEnumerable FindAsync( - string subject, string client, - string status, CancellationToken cancellationToken) - { - if (string.IsNullOrEmpty(subject)) - { - throw new ArgumentException(SR.GetResourceString(SR.ID0198), nameof(subject)); - } - - if (string.IsNullOrEmpty(client)) - { - throw new ArgumentException(SR.GetResourceString(SR.ID0124), nameof(client)); - } - - if (string.IsNullOrEmpty(status)) - { - throw new ArgumentException(SR.GetResourceString(SR.ID0199), nameof(status)); - } - - // Note: due to a bug in Entity Framework Core's query visitor, the authorizations - // can't be filtered using authorization.Application.Id.Equals(key). To work around - // this issue, this query uses use an explicit join to apply the equality check. - // - // See https://github.com/openiddict/openiddict-core/issues/499 for more information. + IQueryable query = Authorizations.Include(authorization => authorization.Application).AsTracking(); - var key = ConvertIdentifierFromString(client); - - return (from authorization in Authorizations.Include(authorization => authorization.Application).AsTracking() - where authorization.Subject == subject && authorization.Status == status - join application in Applications.AsTracking() on authorization.Application!.Id equals application.Id - where application.Id!.Equals(key) - select authorization).AsAsyncEnumerable(cancellationToken); - } - - /// - public virtual IAsyncEnumerable FindAsync( - string subject, string client, - string status, string type, CancellationToken cancellationToken) - { - if (string.IsNullOrEmpty(subject)) - { - throw new ArgumentException(SR.GetResourceString(SR.ID0198), nameof(subject)); - } - - if (string.IsNullOrEmpty(client)) - { - throw new ArgumentException(SR.GetResourceString(SR.ID0124), nameof(client)); - } - - if (string.IsNullOrEmpty(status)) + if (!string.IsNullOrEmpty(subject)) { - throw new ArgumentException(SR.GetResourceString(SR.ID0199), nameof(status)); + query = query.Where(authorization => authorization.Subject == subject); } - if (string.IsNullOrEmpty(type)) + if (!string.IsNullOrEmpty(client)) { - throw new ArgumentException(SR.GetResourceString(SR.ID0200), nameof(type)); - } - - // Note: due to a bug in Entity Framework Core's query visitor, the authorizations - // can't be filtered using authorization.Application.Id.Equals(key). To work around - // this issue, this query uses use an explicit join to apply the equality check. - // - // See https://github.com/openiddict/openiddict-core/issues/499 for more information. - - var key = ConvertIdentifierFromString(client); - - return (from authorization in Authorizations.Include(authorization => authorization.Application).AsTracking() - where authorization.Subject == subject && - authorization.Status == status && - authorization.Type == type - join application in Applications.AsTracking() on authorization.Application!.Id equals application.Id - where application.Id!.Equals(key) - select authorization).AsAsyncEnumerable(cancellationToken); - } - - /// - public virtual IAsyncEnumerable FindAsync( - string subject, string client, - string status, string type, - ImmutableArray scopes, CancellationToken cancellationToken) - { - if (string.IsNullOrEmpty(subject)) - { - throw new ArgumentException(SR.GetResourceString(SR.ID0198), nameof(subject)); - } + // Note: due to a bug in Entity Framework Core's query visitor, the authorizations + // can't be filtered using authorization.Application.Id.Equals(key). To work around + // this issue, this query uses use an explicit join to apply the equality check. + // + // See https://github.com/openiddict/openiddict-core/issues/499 for more information. + var key = ConvertIdentifierFromString(client); - if (string.IsNullOrEmpty(client)) - { - throw new ArgumentException(SR.GetResourceString(SR.ID0124), nameof(client)); + query = from authorization in query + join application in Applications.AsTracking() on authorization.Application!.Id equals application.Id + where application.Id!.Equals(key) + select authorization; } - if (string.IsNullOrEmpty(status)) + if (!string.IsNullOrEmpty(status)) { - throw new ArgumentException(SR.GetResourceString(SR.ID0199), nameof(status)); + query = query.Where(authorization => authorization.Status == status); } - if (string.IsNullOrEmpty(type)) + if (!string.IsNullOrEmpty(type)) { - throw new ArgumentException(SR.GetResourceString(SR.ID0200), nameof(type)); + query = query.Where(authorization => authorization.Type == type); } - return ExecuteAsync(cancellationToken); - - async IAsyncEnumerable ExecuteAsync([EnumeratorCancellation] CancellationToken cancellationToken) + await foreach (var authorization in query.AsAsyncEnumerable(cancellationToken)) { - // Note: due to a bug in Entity Framework Core's query visitor, the authorizations - // can't be filtered using authorization.Application.Id.Equals(key). To work around - // this issue, this query uses use an explicit join to apply the equality check. - // - // See https://github.com/openiddict/openiddict-core/issues/499 for more information. - - var key = ConvertIdentifierFromString(client); - - var authorizations = (from authorization in Authorizations.Include(authorization => authorization.Application).AsTracking() - where authorization.Subject == subject && - authorization.Status == status && - authorization.Type == type - join application in Applications.AsTracking() on authorization.Application!.Id equals application.Id - where application.Id!.Equals(key) - select authorization).AsAsyncEnumerable(cancellationToken); - - await foreach (var authorization in authorizations) + if (scopes is null || (await GetScopesAsync(authorization, cancellationToken)) + .ToHashSet(StringComparer.Ordinal) + .IsSupersetOf(scopes)) { - if ((await GetScopesAsync(authorization, cancellationToken)) - .ToHashSet(StringComparer.Ordinal) - .IsSupersetOf(scopes)) - { - yield return authorization; - } + yield return authorization; } } } @@ -805,194 +690,47 @@ orderby authorization.Id } /// - public virtual async ValueTask RevokeAsync(string subject, string client, CancellationToken cancellationToken) + public virtual async ValueTask RevokeAsync(string? subject, string? client, string? status, string? type, CancellationToken cancellationToken) { - if (string.IsNullOrEmpty(subject)) - { - throw new ArgumentException(SR.GetResourceString(SR.ID0198), nameof(subject)); - } + IQueryable query = Options.CurrentValue.DisableBulkOperations ? + Authorizations.Include(authorization => authorization.Application).AsTracking() : + Authorizations; - if (string.IsNullOrEmpty(client)) + if (!string.IsNullOrEmpty(subject)) { - throw new ArgumentException(SR.GetResourceString(SR.ID0124), nameof(client)); + query = query.Where(authorization => authorization.Subject == subject); } - var key = ConvertIdentifierFromString(client); - -#if SUPPORTS_BULK_DBSET_OPERATIONS - if (!Options.CurrentValue.DisableBulkOperations) + if (!string.IsNullOrEmpty(client)) { - return await ( - from authorization in Authorizations - where authorization.Subject == subject && authorization.Application!.Id!.Equals(key) - select authorization).ExecuteUpdateAsync(entity => entity.SetProperty( - authorization => authorization.Status, Statuses.Revoked), cancellationToken); - - // Note: calling DbContext.SaveChangesAsync() is not necessary - // with bulk update operations as they are executed immediately. - } -#endif - List? exceptions = null; - - var result = 0L; - - // Note: due to a bug in Entity Framework Core's query visitor, the authorizations - // can't be filtered using authorization.Application.Id.Equals(key). To work around - // this issue, this query uses use an explicit join to apply the equality check. - // - // See https://github.com/openiddict/openiddict-core/issues/499 for more information. - - foreach (var authorization in await (from authorization in Authorizations.AsTracking() - where authorization.Subject == subject - join application in Applications.AsTracking() on authorization.Application!.Id equals application.Id - where application.Id!.Equals(key) - select authorization).ToListAsync(cancellationToken)) - { - authorization.Status = Statuses.Revoked; - - try - { - await Context.SaveChangesAsync(cancellationToken); - } - - catch (Exception exception) when (!OpenIddictHelpers.IsFatal(exception)) - { - // Reset the state of the entity to prevents future calls to SaveChangesAsync() from failing. - Context.Entry(authorization).State = EntityState.Unchanged; - - exceptions ??= []; - exceptions.Add(exception); - - continue; - } - - result++; - } - - if (exceptions is not null) - { - throw new AggregateException(SR.GetResourceString(SR.ID0249), exceptions); - } - - return result; - } - - /// - public virtual async ValueTask RevokeAsync(string subject, string client, string status, CancellationToken cancellationToken) - { - if (string.IsNullOrEmpty(subject)) - { - throw new ArgumentException(SR.GetResourceString(SR.ID0198), nameof(subject)); - } - - if (string.IsNullOrEmpty(client)) - { - throw new ArgumentException(SR.GetResourceString(SR.ID0124), nameof(client)); - } - - if (string.IsNullOrEmpty(status)) - { - throw new ArgumentException(SR.GetResourceString(SR.ID0199), nameof(status)); - } - - var key = ConvertIdentifierFromString(client); - -#if SUPPORTS_BULK_DBSET_OPERATIONS - if (!Options.CurrentValue.DisableBulkOperations) - { - return await ( - from authorization in Authorizations - where authorization.Subject == subject && - authorization.Status == status && - authorization.Application!.Id!.Equals(key) - select authorization).ExecuteUpdateAsync(entity => entity.SetProperty( - authorization => authorization.Status, Statuses.Revoked), cancellationToken); - - // Note: calling DbContext.SaveChangesAsync() is not necessary - // with bulk update operations as they are executed immediately. - } -#endif - List? exceptions = null; - - var result = 0L; - - // Note: due to a bug in Entity Framework Core's query visitor, the authorizations - // can't be filtered using authorization.Application.Id.Equals(key). To work around - // this issue, this query uses use an explicit join to apply the equality check. - // - // See https://github.com/openiddict/openiddict-core/issues/499 for more information. - - foreach (var authorization in await (from authorization in Authorizations.AsTracking() - where authorization.Subject == subject && authorization.Status == status - join application in Applications.AsTracking() on authorization.Application!.Id equals application.Id - where application.Id!.Equals(key) - select authorization).ToListAsync(cancellationToken)) - { - authorization.Status = Statuses.Revoked; - - try - { - await Context.SaveChangesAsync(cancellationToken); - } - - catch (Exception exception) when (!OpenIddictHelpers.IsFatal(exception)) - { - // Reset the state of the entity to prevents future calls to SaveChangesAsync() from failing. - Context.Entry(authorization).State = EntityState.Unchanged; - - exceptions ??= []; - exceptions.Add(exception); - - continue; - } - - result++; - } - - if (exceptions is not null) - { - throw new AggregateException(SR.GetResourceString(SR.ID0249), exceptions); - } - - return result; - } - - /// - public virtual async ValueTask RevokeAsync(string subject, string client, string status, string type, CancellationToken cancellationToken) - { - if (string.IsNullOrEmpty(subject)) - { - throw new ArgumentException(SR.GetResourceString(SR.ID0198), nameof(subject)); - } + // Note: due to a bug in Entity Framework Core's query visitor, the authorizations + // can't be filtered using authorization.Application.Id.Equals(key). To work around + // this issue, this query uses use an explicit join to apply the equality check. + // + // See https://github.com/openiddict/openiddict-core/issues/499 for more information. + var key = ConvertIdentifierFromString(client); - if (string.IsNullOrEmpty(client)) - { - throw new ArgumentException(SR.GetResourceString(SR.ID0124), nameof(client)); + query = from authorization in query + join application in Applications.AsTracking() on authorization.Application!.Id equals application.Id + where application.Id!.Equals(key) + select authorization; } - if (string.IsNullOrEmpty(status)) + if (!string.IsNullOrEmpty(status)) { - throw new ArgumentException(SR.GetResourceString(SR.ID0199), nameof(status)); + query = query.Where(authorization => authorization.Status == status); } - if (string.IsNullOrEmpty(type)) + if (!string.IsNullOrEmpty(type)) { - throw new ArgumentException(SR.GetResourceString(SR.ID0200), nameof(type)); + query = query.Where(authorization => authorization.Type == type); } - var key = ConvertIdentifierFromString(client); - #if SUPPORTS_BULK_DBSET_OPERATIONS if (!Options.CurrentValue.DisableBulkOperations) { - return await ( - from authorization in Authorizations - where authorization.Subject == subject && - authorization.Status == status && - authorization.Type == type && - authorization.Application!.Id!.Equals(key) - select authorization).ExecuteUpdateAsync(entity => entity.SetProperty( - authorization => authorization.Status, Statuses.Revoked), cancellationToken); + return await query.ExecuteUpdateAsync(entity => entity.SetProperty( + authorization => authorization.Status, Statuses.Revoked), cancellationToken); // Note: calling DbContext.SaveChangesAsync() is not necessary // with bulk update operations as they are executed immediately. @@ -1008,11 +746,7 @@ from authorization in Authorizations // // See https://github.com/openiddict/openiddict-core/issues/499 for more information. - foreach (var authorization in await (from authorization in Authorizations.AsTracking() - where authorization.Subject == subject && authorization.Status == status && authorization.Type == type - join application in Applications.AsTracking() on authorization.Application!.Id equals application.Id - where application.Id!.Equals(key) - select authorization).ToListAsync(cancellationToken)) + foreach (var authorization in await query.ToListAsync(cancellationToken)) { authorization.Status = Statuses.Revoked; @@ -1076,7 +810,7 @@ from authorization in Authorizations // // See https://github.com/openiddict/openiddict-core/issues/499 for more information. - foreach (var authorization in await (from authorization in Authorizations.AsTracking() + foreach (var authorization in await (from authorization in Authorizations.Include(authorization => authorization.Application).AsTracking() join application in Applications.AsTracking() on authorization.Application!.Id equals application.Id where application.Id!.Equals(key) select authorization).ToListAsync(cancellationToken)) @@ -1135,7 +869,7 @@ from authorization in Authorizations var result = 0L; - foreach (var authorization in await (from authorization in Authorizations.AsTracking() + foreach (var authorization in await (from authorization in Authorizations.Include(authorization => authorization.Application).AsTracking() where authorization.Subject == subject select authorization).ToListAsync(cancellationToken)) { diff --git a/src/OpenIddict.EntityFrameworkCore/Stores/OpenIddictEntityFrameworkCoreTokenStore.cs b/src/OpenIddict.EntityFrameworkCore/Stores/OpenIddictEntityFrameworkCoreTokenStore.cs index 8c67c192e..a9ff14943 100644 --- a/src/OpenIddict.EntityFrameworkCore/Stores/OpenIddictEntityFrameworkCoreTokenStore.cs +++ b/src/OpenIddict.EntityFrameworkCore/Stores/OpenIddictEntityFrameworkCoreTokenStore.cs @@ -165,110 +165,44 @@ public virtual async ValueTask DeleteAsync(TToken token, CancellationToken cance } } - /// - public virtual IAsyncEnumerable FindAsync(string subject, string client, CancellationToken cancellationToken) - { - if (string.IsNullOrEmpty(subject)) - { - throw new ArgumentException(SR.GetResourceString(SR.ID0198), nameof(subject)); - } - - if (string.IsNullOrEmpty(client)) - { - throw new ArgumentException(SR.GetResourceString(SR.ID0124), nameof(client)); - } - - // Note: due to a bug in Entity Framework Core's query visitor, the tokens - // can't be filtered using token.Application.Id.Equals(key). To work around - // this issue, this query uses use an explicit join to apply the equality check. - // - // See https://github.com/openiddict/openiddict-core/issues/499 for more information. - - var key = ConvertIdentifierFromString(client); - - return (from token in Tokens.Include(token => token.Application).Include(token => token.Authorization).AsTracking() - where token.Subject == subject - join application in Applications.AsTracking() on token.Application!.Id equals application.Id - where application.Id!.Equals(key) - select token).AsAsyncEnumerable(cancellationToken); - } - /// public virtual IAsyncEnumerable FindAsync( - string subject, string client, - string status, CancellationToken cancellationToken) + string? subject, string? client, + string? status, string? type, CancellationToken cancellationToken) { - if (string.IsNullOrEmpty(subject)) - { - throw new ArgumentException(SR.GetResourceString(SR.ID0198), nameof(subject)); - } + IQueryable query = Tokens.Include(token => token.Application).Include(token => token.Authorization).AsTracking(); - if (string.IsNullOrEmpty(client)) + if (!string.IsNullOrEmpty(subject)) { - throw new ArgumentException(SR.GetResourceString(SR.ID0124), nameof(client)); + query = query.Where(token => token.Subject == subject); } - if (string.IsNullOrEmpty(status)) + if (!string.IsNullOrEmpty(client)) { - throw new ArgumentException(SR.GetResourceString(SR.ID0199), nameof(status)); - } - - // Note: due to a bug in Entity Framework Core's query visitor, the tokens - // can't be filtered using token.Application.Id.Equals(key). To work around - // this issue, this query uses use an explicit join to apply the equality check. - // - // See https://github.com/openiddict/openiddict-core/issues/499 for more information. - - var key = ConvertIdentifierFromString(client); + // Note: due to a bug in Entity Framework Core's query visitor, the authorizations + // can't be filtered using authorization.Application.Id.Equals(key). To work around + // this issue, this query uses use an explicit join to apply the equality check. + // + // See https://github.com/openiddict/openiddict-core/issues/499 for more information. + var key = ConvertIdentifierFromString(client); - return (from token in Tokens.Include(token => token.Application).Include(token => token.Authorization).AsTracking() - where token.Subject == subject && - token.Status == status - join application in Applications.AsTracking() on token.Application!.Id equals application.Id - where application.Id!.Equals(key) - select token).AsAsyncEnumerable(cancellationToken); - } - - /// - public virtual IAsyncEnumerable FindAsync( - string subject, string client, - string status, string type, CancellationToken cancellationToken) - { - if (string.IsNullOrEmpty(subject)) - { - throw new ArgumentException(SR.GetResourceString(SR.ID0198), nameof(subject)); + query = from authorization in query + join application in Applications.AsTracking() on authorization.Application!.Id equals application.Id + where application.Id!.Equals(key) + select authorization; } - if (string.IsNullOrEmpty(client)) + if (!string.IsNullOrEmpty(status)) { - throw new ArgumentException(SR.GetResourceString(SR.ID0124), nameof(client)); + query = query.Where(token => token.Status == status); } - if (string.IsNullOrEmpty(status)) + if (!string.IsNullOrEmpty(type)) { - throw new ArgumentException(SR.GetResourceString(SR.ID0199), nameof(status)); + query = query.Where(token => token.Type == type); } - if (string.IsNullOrEmpty(type)) - { - throw new ArgumentException(SR.GetResourceString(SR.ID0200), nameof(type)); - } - - // Note: due to a bug in Entity Framework Core's query visitor, the tokens - // can't be filtered using token.Application.Id.Equals(key). To work around - // this issue, this query uses use an explicit join to apply the equality check. - // - // See https://github.com/openiddict/openiddict-core/issues/499 for more information. - - var key = ConvertIdentifierFromString(client); - - return (from token in Tokens.Include(token => token.Application).Include(token => token.Authorization).AsTracking() - where token.Subject == subject && - token.Status == status && - token.Type == type - join application in Applications.AsTracking() on token.Application!.Id equals application.Id - where application.Id!.Equals(key) - select token).AsAsyncEnumerable(cancellationToken); + return query.AsAsyncEnumerable(cancellationToken); } /// @@ -754,192 +688,47 @@ orderby token.Id } /// - public virtual async ValueTask RevokeAsync(string subject, string client, CancellationToken cancellationToken) + public virtual async ValueTask RevokeAsync(string? subject, string? client, string? status, string ?type, CancellationToken cancellationToken) { - if (string.IsNullOrEmpty(subject)) - { - throw new ArgumentException(SR.GetResourceString(SR.ID0198), nameof(subject)); - } + IQueryable query = Options.CurrentValue.DisableBulkOperations ? + Tokens.Include(token => token.Application).Include(token => token.Authorization).AsTracking() : + Tokens; - if (string.IsNullOrEmpty(client)) + if (!string.IsNullOrEmpty(subject)) { - throw new ArgumentException(SR.GetResourceString(SR.ID0124), nameof(client)); + query = query.Where(token => token.Subject == subject); } - var key = ConvertIdentifierFromString(client); - -#if SUPPORTS_BULK_DBSET_OPERATIONS - if (!Options.CurrentValue.DisableBulkOperations) + if (!string.IsNullOrEmpty(client)) { - return await ( - from token in Tokens - where token.Subject == subject && token.Application!.Id!.Equals(key) - select token).ExecuteUpdateAsync(entity => entity.SetProperty( - token => token.Status, Statuses.Revoked), cancellationToken); + // Note: due to a bug in Entity Framework Core's query visitor, the authorizations + // can't be filtered using authorization.Application.Id.Equals(key). To work around + // this issue, this query uses use an explicit join to apply the equality check. + // + // See https://github.com/openiddict/openiddict-core/issues/499 for more information. + var key = ConvertIdentifierFromString(client); - // Note: calling DbContext.SaveChangesAsync() is not necessary - // with bulk update operations as they are executed immediately. + query = from authorization in query + join application in Applications.AsTracking() on authorization.Application!.Id equals application.Id + where application.Id!.Equals(key) + select authorization; } -#endif - List? exceptions = null; - var result = 0L; - - // Note: due to a bug in Entity Framework Core's query visitor, the tokens - // can't be filtered using token.Application.Id.Equals(key). To work around - // this issue, this query uses use an explicit join to apply the equality check. - // - // See https://github.com/openiddict/openiddict-core/issues/499 for more information. - - foreach (var token in await (from token in Tokens.AsTracking() - where token.Subject == subject - join application in Applications.AsTracking() on token.Application!.Id equals application.Id - where application.Id!.Equals(key) - select token).ToListAsync(cancellationToken)) + if (!string.IsNullOrEmpty(status)) { - token.Status = Statuses.Revoked; - - try - { - await Context.SaveChangesAsync(cancellationToken); - } - - catch (Exception exception) when (!OpenIddictHelpers.IsFatal(exception)) - { - // Reset the state of the entity to prevents future calls to SaveChangesAsync() from failing. - Context.Entry(token).State = EntityState.Unchanged; - - exceptions ??= []; - exceptions.Add(exception); - - continue; - } - - result++; - } - - if (exceptions is not null) - { - throw new AggregateException(SR.GetResourceString(SR.ID0249), exceptions); - } - - return result; - } - - /// - public virtual async ValueTask RevokeAsync(string subject, string client, string status, CancellationToken cancellationToken) - { - if (string.IsNullOrEmpty(subject)) - { - throw new ArgumentException(SR.GetResourceString(SR.ID0198), nameof(subject)); - } - - if (string.IsNullOrEmpty(client)) - { - throw new ArgumentException(SR.GetResourceString(SR.ID0124), nameof(client)); - } - - if (string.IsNullOrEmpty(status)) - { - throw new ArgumentException(SR.GetResourceString(SR.ID0199), nameof(status)); - } - - var key = ConvertIdentifierFromString(client); - -#if SUPPORTS_BULK_DBSET_OPERATIONS - if (!Options.CurrentValue.DisableBulkOperations) - { - return await ( - from token in Tokens - where token.Subject == subject && token.Status == status && token.Application!.Id!.Equals(key) - select token).ExecuteUpdateAsync(entity => entity.SetProperty( - token => token.Status, Statuses.Revoked), cancellationToken); - - // Note: calling DbContext.SaveChangesAsync() is not necessary - // with bulk update operations as they are executed immediately. - } -#endif - List? exceptions = null; - - var result = 0L; - - // Note: due to a bug in Entity Framework Core's query visitor, the tokens - // can't be filtered using token.Application.Id.Equals(key). To work around - // this issue, this query uses use an explicit join to apply the equality check. - // - // See https://github.com/openiddict/openiddict-core/issues/499 for more information. - - foreach (var token in await (from token in Tokens.AsTracking() - where token.Subject == subject && token.Status == status - join application in Applications.AsTracking() on token.Application!.Id equals application.Id - where application.Id!.Equals(key) - select token).ToListAsync(cancellationToken)) - { - token.Status = Statuses.Revoked; - - try - { - await Context.SaveChangesAsync(cancellationToken); - } - - catch (Exception exception) when (!OpenIddictHelpers.IsFatal(exception)) - { - // Reset the state of the entity to prevents future calls to SaveChangesAsync() from failing. - Context.Entry(token).State = EntityState.Unchanged; - - exceptions ??= []; - exceptions.Add(exception); - - continue; - } - - result++; + query = query.Where(token => token.Status == status); } - if (exceptions is not null) + if (!string.IsNullOrEmpty(type)) { - throw new AggregateException(SR.GetResourceString(SR.ID0249), exceptions); + query = query.Where(token => token.Type == type); } - return result; - } - - /// - public virtual async ValueTask RevokeAsync(string subject, string client, string status, string type, CancellationToken cancellationToken) - { - if (string.IsNullOrEmpty(subject)) - { - throw new ArgumentException(SR.GetResourceString(SR.ID0198), nameof(subject)); - } - - if (string.IsNullOrEmpty(client)) - { - throw new ArgumentException(SR.GetResourceString(SR.ID0124), nameof(client)); - } - - if (string.IsNullOrEmpty(status)) - { - throw new ArgumentException(SR.GetResourceString(SR.ID0199), nameof(status)); - } - - if (string.IsNullOrEmpty(type)) - { - throw new ArgumentException(SR.GetResourceString(SR.ID0200), nameof(type)); - } - - var key = ConvertIdentifierFromString(client); - #if SUPPORTS_BULK_DBSET_OPERATIONS if (!Options.CurrentValue.DisableBulkOperations) { - return await ( - from token in Tokens - where token.Subject == subject && - token.Status == status && - token.Type == type && - token.Application!.Id!.Equals(key) - select token).ExecuteUpdateAsync(entity => entity.SetProperty( - token => token.Status, Statuses.Revoked), cancellationToken); + return await query.ExecuteUpdateAsync(entity => entity.SetProperty( + token => token.Status, Statuses.Revoked), cancellationToken); // Note: calling DbContext.SaveChangesAsync() is not necessary // with bulk update operations as they are executed immediately. @@ -949,17 +738,7 @@ from token in Tokens var result = 0L; - // Note: due to a bug in Entity Framework Core's query visitor, the tokens - // can't be filtered using token.Application.Id.Equals(key). To work around - // this issue, this query uses use an explicit join to apply the equality check. - // - // See https://github.com/openiddict/openiddict-core/issues/499 for more information. - - foreach (var token in await (from token in Tokens.AsTracking() - where token.Subject == subject && token.Status == status && token.Type == type - join application in Applications.AsTracking() on token.Application!.Id equals application.Id - where application.Id!.Equals(key) - select token).ToListAsync(cancellationToken)) + foreach (var token in await query.ToListAsync(cancellationToken)) { token.Status = Statuses.Revoked; @@ -1023,7 +802,9 @@ from token in Tokens // // See https://github.com/openiddict/openiddict-core/issues/499 for more information. - foreach (var token in await (from token in Tokens.AsTracking() + foreach (var token in await (from token in Tokens.Include(token => token.Application) + .Include(token => token.Authorization) + .AsTracking() join application in Applications.AsTracking() on token.Application!.Id equals application.Id where application.Id!.Equals(key) select token).ToListAsync(cancellationToken)) @@ -1090,7 +871,9 @@ from token in Tokens // // See https://github.com/openiddict/openiddict-core/issues/499 for more information. - foreach (var token in await (from token in Tokens.AsTracking() + foreach (var token in await (from token in Tokens.Include(token => token.Application) + .Include(token => token.Authorization) + .AsTracking() join authorization in Authorizations.AsTracking() on token.Authorization!.Id equals authorization.Id where authorization.Id!.Equals(key) select token).ToListAsync(cancellationToken)) @@ -1149,7 +932,9 @@ from token in Tokens var result = 0L; - foreach (var token in await (from token in Tokens.AsTracking() + foreach (var token in await (from token in Tokens.Include(token => token.Application) + .Include(token => token.Authorization) + .AsTracking() where token.Subject == subject select token).ToListAsync(cancellationToken)) { diff --git a/src/OpenIddict.MongoDb/Stores/OpenIddictMongoDbAuthorizationStore.cs b/src/OpenIddict.MongoDb/Stores/OpenIddictMongoDbAuthorizationStore.cs index 142dafcfd..7f58382c0 100644 --- a/src/OpenIddict.MongoDb/Stores/OpenIddictMongoDbAuthorizationStore.cs +++ b/src/OpenIddict.MongoDb/Stores/OpenIddictMongoDbAuthorizationStore.cs @@ -103,159 +103,46 @@ await database.GetCollection(Options.CurrentValue.Tokens } /// - public virtual IAsyncEnumerable FindAsync( - string subject, string client, CancellationToken cancellationToken) + public virtual async IAsyncEnumerable FindAsync( + string? subject, string? client, + string? status, string? type, + ImmutableArray? scopes, [EnumeratorCancellation] CancellationToken cancellationToken) { - if (string.IsNullOrEmpty(subject)) - { - throw new ArgumentException(SR.GetResourceString(SR.ID0198), nameof(subject)); - } - - if (string.IsNullOrEmpty(client)) - { - throw new ArgumentException(SR.GetResourceString(SR.ID0124), nameof(client)); - } - - return ExecuteAsync(cancellationToken); - - async IAsyncEnumerable ExecuteAsync([EnumeratorCancellation] CancellationToken cancellationToken) - { - var database = await Context.GetDatabaseAsync(cancellationToken); - var collection = database.GetCollection(Options.CurrentValue.AuthorizationsCollectionName); - - await foreach (var authorization in collection.Find(authorization => - authorization.ApplicationId == ObjectId.Parse(client) && - authorization.Subject == subject).ToAsyncEnumerable(cancellationToken)) - { - yield return authorization; - } - } - } - - /// - public virtual IAsyncEnumerable FindAsync( - string subject, string client, - string status, CancellationToken cancellationToken) - { - if (string.IsNullOrEmpty(subject)) - { - throw new ArgumentException(SR.GetResourceString(SR.ID0198), nameof(subject)); - } - - if (string.IsNullOrEmpty(client)) - { - throw new ArgumentException(SR.GetResourceString(SR.ID0124), nameof(client)); - } - - if (string.IsNullOrEmpty(status)) - { - throw new ArgumentException(SR.GetResourceString(SR.ID0199), nameof(status)); - } + var database = await Context.GetDatabaseAsync(cancellationToken); + var collection = database.GetCollection(Options.CurrentValue.AuthorizationsCollectionName); - return ExecuteAsync(cancellationToken); + IQueryable query = collection.AsQueryable(); - async IAsyncEnumerable ExecuteAsync([EnumeratorCancellation] CancellationToken cancellationToken) + if (!string.IsNullOrEmpty(subject)) { - var database = await Context.GetDatabaseAsync(cancellationToken); - var collection = database.GetCollection(Options.CurrentValue.AuthorizationsCollectionName); - - await foreach (var authorization in collection.Find(authorization => - authorization.ApplicationId == ObjectId.Parse(client) && - authorization.Subject == subject && - authorization.Status == status).ToAsyncEnumerable(cancellationToken)) - { - yield return authorization; - } + query = query.Where(authorization => authorization.Subject == subject); } - } - /// - public virtual IAsyncEnumerable FindAsync( - string subject, string client, - string status, string type, CancellationToken cancellationToken) - { - if (string.IsNullOrEmpty(subject)) + if (!string.IsNullOrEmpty(client)) { - throw new ArgumentException(SR.GetResourceString(SR.ID0198), nameof(subject)); + query = query.Where(authorization => authorization.ApplicationId == ObjectId.Parse(client)); } - if (string.IsNullOrEmpty(client)) + if (!string.IsNullOrEmpty(status)) { - throw new ArgumentException(SR.GetResourceString(SR.ID0124), nameof(client)); + query = query.Where(authorization => authorization.Status == status); } - if (string.IsNullOrEmpty(status)) + if (!string.IsNullOrEmpty(type)) { - throw new ArgumentException(SR.GetResourceString(SR.ID0199), nameof(status)); + query = query.Where(authorization => authorization.Type == type); } - if (string.IsNullOrEmpty(type)) + if (scopes is ImmutableArray values) { - throw new ArgumentException(SR.GetResourceString(SR.ID0200), nameof(type)); - } - - return ExecuteAsync(cancellationToken); - - async IAsyncEnumerable ExecuteAsync([EnumeratorCancellation] CancellationToken cancellationToken) - { - var database = await Context.GetDatabaseAsync(cancellationToken); - var collection = database.GetCollection(Options.CurrentValue.AuthorizationsCollectionName); - - await foreach (var authorization in collection.Find(authorization => - authorization.ApplicationId == ObjectId.Parse(client) && - authorization.Subject == subject && - authorization.Status == status && - authorization.Type == type).ToAsyncEnumerable(cancellationToken)) - { - yield return authorization; - } - } - } - - /// - public virtual IAsyncEnumerable FindAsync( - string subject, string client, - string status, string type, - ImmutableArray scopes, CancellationToken cancellationToken) - { - if (string.IsNullOrEmpty(subject)) - { - throw new ArgumentException(SR.GetResourceString(SR.ID0198), nameof(subject)); - } - - if (string.IsNullOrEmpty(client)) - { - throw new ArgumentException(SR.GetResourceString(SR.ID0124), nameof(client)); - } - - if (string.IsNullOrEmpty(status)) - { - throw new ArgumentException(SR.GetResourceString(SR.ID0199), nameof(status)); + // Note: Enumerable.All() is deliberately used without the extension method syntax to ensure + // ImmutableArrayExtensions.All() (which is not supported by MongoDB) is not used instead. + query = query.Where(authorization => Enumerable.All(values, scope => authorization.Scopes!.Contains(scope))); } - if (string.IsNullOrEmpty(type)) + await foreach (var authorization in query.ToAsyncEnumerable(cancellationToken)) { - throw new ArgumentException(SR.GetResourceString(SR.ID0200), nameof(type)); - } - - return ExecuteAsync(cancellationToken); - - async IAsyncEnumerable ExecuteAsync([EnumeratorCancellation] CancellationToken cancellationToken) - { - var database = await Context.GetDatabaseAsync(cancellationToken); - var collection = database.GetCollection(Options.CurrentValue.AuthorizationsCollectionName); - - // Note: Enumerable.All() is deliberately used without the extension method syntax to ensure - // ImmutableArrayExtensions.All() (which is not supported by MongoDB) is not used instead. - await foreach (var authorization in collection.Find(authorization => - authorization.ApplicationId == ObjectId.Parse(client) && - authorization.Subject == subject && - authorization.Status == status && - authorization.Type == type && - Enumerable.All(scopes, scope => authorization.Scopes!.Contains(scope))).ToAsyncEnumerable(cancellationToken)) - { - yield return authorization; - } + yield return authorization; } } @@ -550,89 +437,35 @@ where authorization.CreationDate < threshold.UtcDateTime } /// - public virtual async ValueTask RevokeAsync(string subject, string client, CancellationToken cancellationToken) + public virtual async ValueTask RevokeAsync(string? subject, string? client, string? status, string? type, CancellationToken cancellationToken) { - if (string.IsNullOrEmpty(subject)) - { - throw new ArgumentException(SR.GetResourceString(SR.ID0198), nameof(subject)); - } - - if (string.IsNullOrEmpty(client)) - { - throw new ArgumentException(SR.GetResourceString(SR.ID0124), nameof(client)); - } - var database = await Context.GetDatabaseAsync(cancellationToken); var collection = database.GetCollection(Options.CurrentValue.AuthorizationsCollectionName); - return (await collection.UpdateManyAsync( - filter : authorization => authorization.Subject == subject && authorization.ApplicationId == ObjectId.Parse(client), - update : Builders.Update.Set(authorization => authorization.Status, Statuses.Revoked), - options : null, - cancellationToken: cancellationToken)).MatchedCount; - } - - /// - public virtual async ValueTask RevokeAsync(string subject, string client, string status, CancellationToken cancellationToken) - { - if (string.IsNullOrEmpty(subject)) - { - throw new ArgumentException(SR.GetResourceString(SR.ID0198), nameof(subject)); - } - - if (string.IsNullOrEmpty(client)) - { - throw new ArgumentException(SR.GetResourceString(SR.ID0124), nameof(client)); - } - - if (string.IsNullOrEmpty(status)) - { - throw new ArgumentException(SR.GetResourceString(SR.ID0199), nameof(status)); - } + var filter = Builders.Filter.Empty; - var database = await Context.GetDatabaseAsync(cancellationToken); - var collection = database.GetCollection(Options.CurrentValue.AuthorizationsCollectionName); - - return (await collection.UpdateManyAsync( - filter : authorization => authorization.ApplicationId == ObjectId.Parse(client) && - authorization.Subject == subject && - authorization.Status == status, - update : Builders.Update.Set(authorization => authorization.Status, Statuses.Revoked), - options : null, - cancellationToken: cancellationToken)).MatchedCount; - } - - /// - public virtual async ValueTask RevokeAsync(string subject, string client, string status, string type, CancellationToken cancellationToken) - { - if (string.IsNullOrEmpty(subject)) + if (!string.IsNullOrEmpty(subject)) { - throw new ArgumentException(SR.GetResourceString(SR.ID0198), nameof(subject)); + filter &= Builders.Filter.Where(authorization => authorization.Subject == subject); } - if (string.IsNullOrEmpty(client)) + if (!string.IsNullOrEmpty(client)) { - throw new ArgumentException(SR.GetResourceString(SR.ID0124), nameof(client)); + filter &= Builders.Filter.Where(authorization => authorization.ApplicationId == ObjectId.Parse(client)); } - if (string.IsNullOrEmpty(status)) + if (!string.IsNullOrEmpty(status)) { - throw new ArgumentException(SR.GetResourceString(SR.ID0199), nameof(status)); + filter &= Builders.Filter.Where(authorization => authorization.Status == status); } - if (string.IsNullOrEmpty(type)) + if (!string.IsNullOrEmpty(type)) { - throw new ArgumentException(SR.GetResourceString(SR.ID0200), nameof(type)); + filter &= Builders.Filter.Where(authorization => authorization.Type == type); } - var database = await Context.GetDatabaseAsync(cancellationToken); - var collection = database.GetCollection(Options.CurrentValue.AuthorizationsCollectionName); - return (await collection.UpdateManyAsync( - filter : authorization => authorization.ApplicationId == ObjectId.Parse(client) && - authorization.Subject == subject && - authorization.Status == status && - authorization.Type == type, + filter : filter, update : Builders.Update.Set(authorization => authorization.Status, Statuses.Revoked), options : null, cancellationToken: cancellationToken)).MatchedCount; diff --git a/src/OpenIddict.MongoDb/Stores/OpenIddictMongoDbTokenStore.cs b/src/OpenIddict.MongoDb/Stores/OpenIddictMongoDbTokenStore.cs index f14bd5ac6..ddead28ae 100644 --- a/src/OpenIddict.MongoDb/Stores/OpenIddictMongoDbTokenStore.cs +++ b/src/OpenIddict.MongoDb/Stores/OpenIddictMongoDbTokenStore.cs @@ -99,112 +99,38 @@ public virtual async ValueTask DeleteAsync(TToken token, CancellationToken cance } /// - public virtual IAsyncEnumerable FindAsync(string subject, - string client, CancellationToken cancellationToken) + public virtual async IAsyncEnumerable FindAsync( + string? subject, string? client, + string? status, string? type, [EnumeratorCancellation] CancellationToken cancellationToken) { - if (string.IsNullOrEmpty(subject)) - { - throw new ArgumentException(SR.GetResourceString(SR.ID0198), nameof(subject)); - } - - if (string.IsNullOrEmpty(client)) - { - throw new ArgumentException(SR.GetResourceString(SR.ID0124), nameof(client)); - } - - return ExecuteAsync(cancellationToken); - - async IAsyncEnumerable ExecuteAsync([EnumeratorCancellation] CancellationToken cancellationToken) - { - var database = await Context.GetDatabaseAsync(cancellationToken); - var collection = database.GetCollection(Options.CurrentValue.TokensCollectionName); - - await foreach (var token in collection.Find(token => - token.ApplicationId == ObjectId.Parse(client) && - token.Subject == subject).ToAsyncEnumerable(cancellationToken)) - { - yield return token; - } - } - } - - /// - public virtual IAsyncEnumerable FindAsync( - string subject, string client, - string status, CancellationToken cancellationToken) - { - if (string.IsNullOrEmpty(subject)) - { - throw new ArgumentException(SR.GetResourceString(SR.ID0198), nameof(subject)); - } - - if (string.IsNullOrEmpty(client)) - { - throw new ArgumentException(SR.GetResourceString(SR.ID0124), nameof(client)); - } - - if (string.IsNullOrEmpty(status)) - { - throw new ArgumentException(SR.GetResourceString(SR.ID0199), nameof(status)); - } - - return ExecuteAsync(cancellationToken); - - async IAsyncEnumerable ExecuteAsync([EnumeratorCancellation] CancellationToken cancellationToken) - { - var database = await Context.GetDatabaseAsync(cancellationToken); - var collection = database.GetCollection(Options.CurrentValue.TokensCollectionName); + var database = await Context.GetDatabaseAsync(cancellationToken); + var collection = database.GetCollection(Options.CurrentValue.TokensCollectionName); - await foreach (var token in collection.Find(token => - token.ApplicationId == ObjectId.Parse(client) && - token.Subject == subject && - token.Status == status).ToAsyncEnumerable(cancellationToken)) - { - yield return token; - } - } - } + IQueryable query = collection.AsQueryable(); - /// - public virtual IAsyncEnumerable FindAsync( - string subject, string client, - string status, string type, CancellationToken cancellationToken) - { - if (string.IsNullOrEmpty(subject)) + if (!string.IsNullOrEmpty(subject)) { - throw new ArgumentException(SR.GetResourceString(SR.ID0198), nameof(subject)); + query = query.Where(token => token.Subject == subject); } - if (string.IsNullOrEmpty(client)) + if (!string.IsNullOrEmpty(client)) { - throw new ArgumentException(SR.GetResourceString(SR.ID0124), nameof(client)); + query = query.Where(token => token.ApplicationId == ObjectId.Parse(client)); } - if (string.IsNullOrEmpty(status)) + if (!string.IsNullOrEmpty(status)) { - throw new ArgumentException(SR.GetResourceString(SR.ID0199), nameof(status)); + query = query.Where(token => token.Status == status); } - if (string.IsNullOrEmpty(type)) + if (!string.IsNullOrEmpty(type)) { - throw new ArgumentException(SR.GetResourceString(SR.ID0200), nameof(type)); + query = query.Where(token => token.Type == type); } - return ExecuteAsync(cancellationToken); - - async IAsyncEnumerable ExecuteAsync([EnumeratorCancellation] CancellationToken cancellationToken) + await foreach (var token in query.ToAsyncEnumerable(cancellationToken)) { - var database = await Context.GetDatabaseAsync(cancellationToken); - var collection = database.GetCollection(Options.CurrentValue.TokensCollectionName); - - await foreach (var token in collection.Find(token => - token.ApplicationId == ObjectId.Parse(client) && - token.Subject == subject && - token.Status == status && - token.Type == type).ToAsyncEnumerable(cancellationToken)) - { - yield return token; - } + yield return token; } } @@ -573,7 +499,7 @@ on token.AuthorizationId equals authorization.Id into authorizations where token.CreationDate < threshold.UtcDateTime where (token.Status != Statuses.Inactive && token.Status != Statuses.Valid) || token.ExpirationDate < DateTime.UtcNow || - authorizations.Any(authorization => authorization.Status != Statuses.Valid) + authorizations.Any(token => token.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 @@ -587,89 +513,35 @@ where token.CreationDate < threshold.UtcDateTime } /// - public virtual async ValueTask RevokeAsync(string subject, string client, CancellationToken cancellationToken) + public virtual async ValueTask RevokeAsync(string? subject, string? client, string? status, string? type, CancellationToken cancellationToken) { - if (string.IsNullOrEmpty(subject)) - { - throw new ArgumentException(SR.GetResourceString(SR.ID0198), nameof(subject)); - } - - if (string.IsNullOrEmpty(client)) - { - throw new ArgumentException(SR.GetResourceString(SR.ID0124), nameof(client)); - } - var database = await Context.GetDatabaseAsync(cancellationToken); var collection = database.GetCollection(Options.CurrentValue.TokensCollectionName); - return (await collection.UpdateManyAsync( - filter : token => token.ApplicationId == ObjectId.Parse(client) && token.Subject == subject, - update : Builders.Update.Set(token => token.Status, Statuses.Revoked), - options : null, - cancellationToken: cancellationToken)).MatchedCount; - } + var filter = Builders.Filter.Empty; - /// - public virtual async ValueTask RevokeAsync(string subject, string client, string status, CancellationToken cancellationToken) - { - if (string.IsNullOrEmpty(subject)) + if (!string.IsNullOrEmpty(subject)) { - throw new ArgumentException(SR.GetResourceString(SR.ID0198), nameof(subject)); - } - - if (string.IsNullOrEmpty(client)) - { - throw new ArgumentException(SR.GetResourceString(SR.ID0124), nameof(client)); - } - - if (string.IsNullOrEmpty(status)) - { - throw new ArgumentException(SR.GetResourceString(SR.ID0199), nameof(status)); - } - - var database = await Context.GetDatabaseAsync(cancellationToken); - var collection = database.GetCollection(Options.CurrentValue.TokensCollectionName); - - return (await collection.UpdateManyAsync( - filter : token => token.ApplicationId == ObjectId.Parse(client) && - token.Subject == subject && - token.Status == status, - update : Builders.Update.Set(token => token.Status, Statuses.Revoked), - options : null, - cancellationToken: cancellationToken)).MatchedCount; - } - - /// - public virtual async ValueTask RevokeAsync(string subject, string client, string status, string type, CancellationToken cancellationToken) - { - if (string.IsNullOrEmpty(subject)) - { - throw new ArgumentException(SR.GetResourceString(SR.ID0198), nameof(subject)); + filter &= Builders.Filter.Where(token => token.Subject == subject); } - if (string.IsNullOrEmpty(client)) + if (!string.IsNullOrEmpty(client)) { - throw new ArgumentException(SR.GetResourceString(SR.ID0124), nameof(client)); + filter &= Builders.Filter.Where(token => token.ApplicationId == ObjectId.Parse(client)); } - if (string.IsNullOrEmpty(status)) + if (!string.IsNullOrEmpty(status)) { - throw new ArgumentException(SR.GetResourceString(SR.ID0199), nameof(status)); + filter &= Builders.Filter.Where(token => token.Status == status); } - if (string.IsNullOrEmpty(type)) + if (!string.IsNullOrEmpty(type)) { - throw new ArgumentException(SR.GetResourceString(SR.ID0200), nameof(type)); + filter &= Builders.Filter.Where(token => token.Type == type); } - var database = await Context.GetDatabaseAsync(cancellationToken); - var collection = database.GetCollection(Options.CurrentValue.TokensCollectionName); - return (await collection.UpdateManyAsync( - filter : token => token.ApplicationId == ObjectId.Parse(client) && - token.Subject == subject && - token.Status == status && - token.Type == type, + filter : filter, update : Builders.Update.Set(token => token.Status, Statuses.Revoked), options : null, cancellationToken: cancellationToken)).MatchedCount; diff --git a/src/OpenIddict.Server.AspNetCore/OpenIddictServerAspNetCoreHandler.cs b/src/OpenIddict.Server.AspNetCore/OpenIddictServerAspNetCoreHandler.cs index cad90a06f..2dc257d69 100644 --- a/src/OpenIddict.Server.AspNetCore/OpenIddictServerAspNetCoreHandler.cs +++ b/src/OpenIddict.Server.AspNetCore/OpenIddictServerAspNetCoreHandler.cs @@ -156,12 +156,10 @@ protected override async Task HandleAuthenticateAsync() return AuthenticateResult.NoResult(); } - var properties = new AuthenticationProperties(new Dictionary - { - [Properties.Error] = context.Error, - [Properties.ErrorDescription] = context.ErrorDescription, - [Properties.ErrorUri] = context.ErrorUri - }); + var properties = CreateAuthenticationProperties(); + properties.Items[Properties.Error] = context.Error; + properties.Items[Properties.ErrorDescription] = context.ErrorDescription; + properties.Items[Properties.ErrorUri] = context.ErrorUri; return AuthenticateResult.Fail(SR.GetResourceString(SR.ID0113), properties); } @@ -199,6 +197,15 @@ OpenIddictServerEndpointType.Token when context.Request.IsRefreshTokenGrantType( _ => null }; + var properties = CreateAuthenticationProperties(principal); + + return AuthenticateResult.Success(new AuthenticationTicket( + principal ?? new ClaimsPrincipal(new ClaimsIdentity()), properties, + OpenIddictServerAspNetCoreDefaults.AuthenticationScheme)); + } + + AuthenticationProperties CreateAuthenticationProperties(ClaimsPrincipal? principal = null) + { var properties = new AuthenticationProperties { ExpiresUtc = principal?.GetExpirationDate(), @@ -325,9 +332,7 @@ OpenIddictServerEndpointType.Token when context.Request.IsRefreshTokenGrantType( properties.StoreTokens(tokens); } - return AuthenticateResult.Success(new AuthenticationTicket( - principal ?? new ClaimsPrincipal(new ClaimsIdentity()), properties, - OpenIddictServerAspNetCoreDefaults.AuthenticationScheme)); + return properties; } } diff --git a/src/OpenIddict.Server.Owin/OpenIddictServerOwinHandler.cs b/src/OpenIddict.Server.Owin/OpenIddictServerOwinHandler.cs index e47f85f35..eb70557f7 100644 --- a/src/OpenIddict.Server.Owin/OpenIddictServerOwinHandler.cs +++ b/src/OpenIddict.Server.Owin/OpenIddictServerOwinHandler.cs @@ -151,14 +151,12 @@ public override async Task InvokeAsync() return null; } - var properties = new AuthenticationProperties(new Dictionary - { - [Properties.Error] = context.Error, - [Properties.ErrorDescription] = context.ErrorDescription, - [Properties.ErrorUri] = context.ErrorUri - }); + var properties = CreateAuthenticationProperties(); + properties.Dictionary[Properties.Error] = context.Error; + properties.Dictionary[Properties.ErrorDescription] = context.ErrorDescription; + properties.Dictionary[Properties.ErrorUri] = context.ErrorUri; - return new AuthenticationTicket(null, properties); + return new AuthenticationTicket(new ClaimsIdentity(), properties); } else @@ -191,6 +189,13 @@ OpenIddictServerEndpointType.Token when context.Request.IsRefreshTokenGrantType( _ => null }; + var properties = CreateAuthenticationProperties(principal); + + return new AuthenticationTicket(principal?.Identity as ClaimsIdentity ?? new ClaimsIdentity(), properties); + } + + AuthenticationProperties CreateAuthenticationProperties(ClaimsPrincipal? principal = null) + { var properties = new AuthenticationProperties { ExpiresUtc = principal?.GetExpirationDate(), @@ -240,7 +245,7 @@ OpenIddictServerEndpointType.Token when context.Request.IsRefreshTokenGrantType( properties.Dictionary[Tokens.UserCode] = context.UserCode; } - return new AuthenticationTicket(principal?.Identity as ClaimsIdentity, properties); + return properties; } } diff --git a/src/OpenIddict.Server/OpenIddictServerHandlers.Protection.cs b/src/OpenIddict.Server/OpenIddictServerHandlers.Protection.cs index 4431087ef..c28299c03 100644 --- a/src/OpenIddict.Server/OpenIddictServerHandlers.Protection.cs +++ b/src/OpenIddict.Server/OpenIddictServerHandlers.Protection.cs @@ -1384,6 +1384,8 @@ Claims.Private.CreationDate or Claims.Private.ExpirationDate or Claims.Private.Scope when context.TokenType is TokenTypeHints.AccessToken => false, + Claims.AuthenticationMethodReference when context.TokenType is TokenTypeHints.IdToken => false, + _ => true }); @@ -1398,10 +1400,27 @@ Claims.Private.CreationDate or Claims.Private.ExpirationDate or var audiences = context.Principal.GetAudiences(); if (audiences.Any()) { - claims.Add(Claims.Audience, audiences.Length switch + claims.Add(Claims.Audience, audiences switch + { + [string audience] => audience, + _ => audiences.ToArray() + }); + } + } + + // Note: unlike other claims (e.g "aud"), the "amr" claim MUST be represented as a unique + // claim representing a JSON array, even if a single authentication method reference is + // present in the collection. To ensure an array is always returned, the "amr" claim is + // filtered out from the clone principal and manually added as a "string[]" claim value. + if (context.TokenType is TokenTypeHints.IdToken) + { + var methods = context.Principal.GetClaims(Claims.AuthenticationMethodReference); + if (methods.Any()) + { + claims.Add(Claims.AuthenticationMethodReference, methods switch { - 1 => audiences.ElementAt(0), - _ => audiences + [string method] => [method], + _ => methods.ToArray() }); } } diff --git a/src/OpenIddict.Server/OpenIddictServerHandlers.cs b/src/OpenIddict.Server/OpenIddictServerHandlers.cs index e7047ba36..27fecf6e5 100644 --- a/src/OpenIddict.Server/OpenIddictServerHandlers.cs +++ b/src/OpenIddict.Server/OpenIddictServerHandlers.cs @@ -2216,8 +2216,7 @@ Claims.Private.DeviceCodeId or Claims.Private.ExpirationDate or => values is [{ ValueType: ClaimValueTypes.String }], // The following claims MUST be represented as unique strings or array of strings. - Claims.AuthenticationMethodReference or Claims.Private.Audience or - Claims.Private.Presenter or Claims.Private.Resource + Claims.Private.Audience or Claims.Private.Presenter or Claims.Private.Resource => values.TrueForAll(static value => value.ValueType is ClaimValueTypes.String) || // Note: a unique claim using the special JSON_ARRAY claim value type is allowed // if the individual elements of the parsed JSON array are all string values. @@ -2225,6 +2224,17 @@ Claims.Private.Presenter or Claims.Private.Resource JsonSerializer.Deserialize(value) is { ValueKind: JsonValueKind.Array } element && OpenIddictHelpers.ValidateArrayElements(element, JsonValueKind.String)), + // Note: unlike other claims (e.g "aud"), the "amr" claim MUST be represented as a unique + // claim representing a JSON array, even if a single authentication method reference + // is present in the collection. To avoid forcing users to use the special JSON_ARRAY + // value type, string values are also allowed here and normalized to JSON arrays + // by OpenIddict when generating an identity token based on the specified principal. + Claims.AuthenticationMethodReference + => values.TrueForAll(static value => value.ValueType is ClaimValueTypes.String) || + (values is [{ ValueType: JsonClaimValueTypes.JsonArray, Value: string value }] && + JsonSerializer.Deserialize(value) is { ValueKind: JsonValueKind.Array } element && + OpenIddictHelpers.ValidateArrayElements(element, JsonValueKind.String)), + // The following claims MUST be represented as unique integers. Claims.Private.AccessTokenLifetime or Claims.Private.AuthorizationCodeLifetime or Claims.Private.DeviceCodeLifetime or Claims.Private.IdentityTokenLifetime or diff --git a/src/OpenIddict.Validation.AspNetCore/OpenIddictValidationAspNetCoreHandler.cs b/src/OpenIddict.Validation.AspNetCore/OpenIddictValidationAspNetCoreHandler.cs index 0e68dee49..b752b39da 100644 --- a/src/OpenIddict.Validation.AspNetCore/OpenIddictValidationAspNetCoreHandler.cs +++ b/src/OpenIddict.Validation.AspNetCore/OpenIddictValidationAspNetCoreHandler.cs @@ -154,12 +154,10 @@ protected override async Task HandleAuthenticateAsync() return AuthenticateResult.NoResult(); } - var properties = new AuthenticationProperties(new Dictionary - { - [Properties.Error] = context.Error, - [Properties.ErrorDescription] = context.ErrorDescription, - [Properties.ErrorUri] = context.ErrorUri - }); + var properties = CreateAuthenticationProperties(); + properties.Items[Properties.Error] = context.Error; + properties.Items[Properties.ErrorDescription] = context.ErrorDescription; + properties.Items[Properties.ErrorUri] = context.ErrorUri; return AuthenticateResult.Fail(SR.GetResourceString(SR.ID0113), properties); } @@ -177,6 +175,15 @@ protected override async Task HandleAuthenticateAsync() _ => null }; + var properties = CreateAuthenticationProperties(principal); + + return AuthenticateResult.Success(new AuthenticationTicket( + principal ?? new ClaimsPrincipal(new ClaimsIdentity()), properties, + OpenIddictValidationAspNetCoreDefaults.AuthenticationScheme)); + } + + AuthenticationProperties CreateAuthenticationProperties(ClaimsPrincipal? principal = null) + { var properties = new AuthenticationProperties { ExpiresUtc = principal?.GetExpirationDate(), @@ -208,9 +215,7 @@ protected override async Task HandleAuthenticateAsync() properties.StoreTokens(tokens); } - return AuthenticateResult.Success(new AuthenticationTicket( - principal ?? new ClaimsPrincipal(new ClaimsIdentity()), properties, - OpenIddictValidationAspNetCoreDefaults.AuthenticationScheme)); + return properties; } } diff --git a/src/OpenIddict.Validation.Owin/OpenIddictValidationOwinHandler.cs b/src/OpenIddict.Validation.Owin/OpenIddictValidationOwinHandler.cs index 8d81df9b3..3375fe34e 100644 --- a/src/OpenIddict.Validation.Owin/OpenIddictValidationOwinHandler.cs +++ b/src/OpenIddict.Validation.Owin/OpenIddictValidationOwinHandler.cs @@ -150,14 +150,12 @@ public override async Task InvokeAsync() return null; } - var properties = new AuthenticationProperties(new Dictionary - { - [Properties.Error] = context.Error, - [Properties.ErrorDescription] = context.ErrorDescription, - [Properties.ErrorUri] = context.ErrorUri - }); + var properties = CreateAuthenticationProperties(); + properties.Dictionary[Properties.Error] = context.Error; + properties.Dictionary[Properties.ErrorDescription] = context.ErrorDescription; + properties.Dictionary[Properties.ErrorUri] = context.ErrorUri; - return new AuthenticationTicket(null, properties); + return new AuthenticationTicket(new ClaimsIdentity(), properties); } else @@ -170,6 +168,13 @@ public override async Task InvokeAsync() _ => null }; + var properties = CreateAuthenticationProperties(principal); + + return new AuthenticationTicket(principal?.Identity as ClaimsIdentity ?? new ClaimsIdentity(), properties); + } + + AuthenticationProperties CreateAuthenticationProperties(ClaimsPrincipal? principal = null) + { var properties = new AuthenticationProperties { ExpiresUtc = principal?.GetExpirationDate(), @@ -184,7 +189,7 @@ public override async Task InvokeAsync() properties.Dictionary[TokenTypeHints.AccessToken] = context.AccessToken; } - return new AuthenticationTicket(principal?.Identity as ClaimsIdentity, properties); + return properties; } } diff --git a/test/OpenIddict.Server.AspNetCore.IntegrationTests/OpenIddictServerAspNetCoreIntegrationTests.cs b/test/OpenIddict.Server.AspNetCore.IntegrationTests/OpenIddictServerAspNetCoreIntegrationTests.cs index 4949218a2..631639310 100644 --- a/test/OpenIddict.Server.AspNetCore.IntegrationTests/OpenIddictServerAspNetCoreIntegrationTests.cs +++ b/test/OpenIddict.Server.AspNetCore.IntegrationTests/OpenIddictServerAspNetCoreIntegrationTests.cs @@ -18,6 +18,7 @@ using Xunit; using Xunit.Abstractions; using static OpenIddict.Server.OpenIddictServerEvents; +using static OpenIddict.Server.OpenIddictServerHandlers; using static OpenIddict.Server.OpenIddictServerHandlers.Protection; #if SUPPORTS_JSON_NODES @@ -134,6 +135,54 @@ public async Task ProcessAuthentication_ExpirationDateIsMappedToIssuedUtc() Assert.Equal(new DateTimeOffset(2120, 01, 01, 00, 00, 00, TimeSpan.Zero), properties.ExpiresUtc); } + [Fact] + public async Task ProcessAuthentication_CustomPropertiesAreAddedForErroredAuthenticationResults() + { + // Arrange + await using var server = await CreateServerAsync(options => + { + options.EnableDegradedMode(); + options.SetAuthorizationEndpointUris("/authenticate/properties"); + + options.UseAspNetCore() + .EnableErrorPassthrough() + .EnableAuthorizationEndpointPassthrough(); + + options.AddEventHandler(builder => + { + builder.UseInlineHandler(context => + { + context.RejectIdentityToken = true; + + context.Properties["custom_property"] = "value"; + + return default; + }); + + builder.SetOrder(EvaluateValidatedTokens.Descriptor.Order + 1); + }); + }); + + await using var client = await server.CreateClientAsync(); + + // Act + var response = await client.PostAsync("/authenticate/properties", new OpenIddictRequest + { + ClientId = "Fabrikam", + IdTokenHint = "id_token_hint", + Nonce = "n-0S6_WzA2Mj", + RedirectUri = "http://www.fabrikam.com/path", + ResponseType = "id_token", + Scope = Scopes.OpenId + }); + + // Assert + var properties = new AuthenticationProperties(response.GetParameters() + .ToDictionary(parameter => parameter.Key, parameter => (string?) parameter.Value)); + + Assert.Equal("value", properties.Items["custom_property"]); + } + [Fact] public async Task ProcessChallenge_ImportsAuthenticationProperties() { diff --git a/test/OpenIddict.Server.Owin.IntegrationTests/OpenIddictServerOwinIntegrationTests.cs b/test/OpenIddict.Server.Owin.IntegrationTests/OpenIddictServerOwinIntegrationTests.cs index e170b01c0..67fb892db 100644 --- a/test/OpenIddict.Server.Owin.IntegrationTests/OpenIddictServerOwinIntegrationTests.cs +++ b/test/OpenIddict.Server.Owin.IntegrationTests/OpenIddictServerOwinIntegrationTests.cs @@ -16,6 +16,7 @@ using Xunit; using Xunit.Abstractions; using static OpenIddict.Server.OpenIddictServerEvents; +using static OpenIddict.Server.OpenIddictServerHandlers; using static OpenIddict.Server.OpenIddictServerHandlers.Protection; namespace OpenIddict.Server.Owin.IntegrationTests; @@ -128,6 +129,54 @@ public async Task ProcessAuthentication_ExpirationDateIsMappedToIssuedUtc() Assert.Equal(new DateTimeOffset(2120, 01, 01, 00, 00, 00, TimeSpan.Zero), properties.ExpiresUtc); } + [Fact] + public async Task ProcessAuthentication_CustomPropertiesAreAddedForErroredAuthenticationResults() + { + // Arrange + await using var server = await CreateServerAsync(options => + { + options.EnableDegradedMode(); + options.SetAuthorizationEndpointUris("/authenticate/properties"); + + options.UseOwin() + .EnableErrorPassthrough() + .EnableAuthorizationEndpointPassthrough(); + + options.AddEventHandler(builder => + { + builder.UseInlineHandler(context => + { + context.RejectIdentityToken = true; + + context.Properties["custom_property"] = "value"; + + return default; + }); + + builder.SetOrder(EvaluateValidatedTokens.Descriptor.Order + 1); + }); + }); + + await using var client = await server.CreateClientAsync(); + + // Act + var response = await client.PostAsync("/authenticate/properties", new OpenIddictRequest + { + ClientId = "Fabrikam", + IdTokenHint = "id_token_hint", + Nonce = "n-0S6_WzA2Mj", + RedirectUri = "http://www.fabrikam.com/path", + ResponseType = "id_token", + Scope = Scopes.OpenId + }); + + // Assert + var properties = new AuthenticationProperties(response.GetParameters() + .ToDictionary(parameter => parameter.Key, parameter => (string?) parameter.Value)); + + Assert.Equal("value", properties.Dictionary["custom_property"]); + } + [Fact] public async Task ProcessChallenge_ImportsAuthenticationProperties() { @@ -642,7 +691,7 @@ protected override ValueTask CreateServer else if (context.Request.Path == new PathString("/authenticate")) { var result = await context.Authentication.AuthenticateAsync(OpenIddictServerOwinDefaults.AuthenticationType); - if (result?.Identity is null) + if (result?.Identity is not { IsAuthenticated: true }) { return; } diff --git a/test/OpenIddict.Validation.Owin.IntegrationTests/OpenIddictValidationOwinIntegrationTests.cs b/test/OpenIddict.Validation.Owin.IntegrationTests/OpenIddictValidationOwinIntegrationTests.cs index 525a8b54e..4297f7d24 100644 --- a/test/OpenIddict.Validation.Owin.IntegrationTests/OpenIddictValidationOwinIntegrationTests.cs +++ b/test/OpenIddict.Validation.Owin.IntegrationTests/OpenIddictValidationOwinIntegrationTests.cs @@ -162,7 +162,7 @@ protected override ValueTask CreateSe if (context.Request.Path == new PathString("/authenticate")) { var result = await context.Authentication.AuthenticateAsync(OpenIddictValidationOwinDefaults.AuthenticationType); - if (result?.Identity is null) + if (result?.Identity is not { IsAuthenticated: true }) { context.Authentication.Challenge(OpenIddictValidationOwinDefaults.AuthenticationType); return;