Browse Source

Incorporate the changes committed in the rel/4.0.0-preview3 branch

pull/1508/head
Kévin Chalet 4 years ago
parent
commit
db9b5493f9
  1. 13
      src/OpenIddict.Abstractions/Managers/IOpenIddictApplicationManager.cs
  2. 3
      src/OpenIddict.Abstractions/OpenIddictResources.resx
  3. 42
      src/OpenIddict.Core/Managers/OpenIddictApplicationManager.cs
  4. 3
      src/OpenIddict.Server/OpenIddictServerHandlers.Session.cs
  5. 80
      test/OpenIddict.Server.IntegrationTests/OpenIddictServerIntegrationTests.Session.cs

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

@ -450,6 +450,19 @@ public interface IOpenIddictApplicationManager
/// </returns> /// </returns>
ValueTask<bool> ValidateClientSecretAsync(object application, string secret, CancellationToken cancellationToken = default); ValueTask<bool> ValidateClientSecretAsync(object application, string secret, CancellationToken cancellationToken = default);
/// <summary>
/// Validates the post_logout_redirect_uri to ensure it's associated with an application.
/// </summary>
/// <param name="application">The application.</param>
/// <param name="address">The address that should be compared to one of the post_logout_redirect_uri stored in the database.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <remarks>Note: if no client_id parameter is specified in logout requests, this method may not be called.</remarks>
/// <returns>
/// A <see cref="ValueTask"/> that can be used to monitor the asynchronous operation,
/// whose result returns a boolean indicating whether the post_logout_redirect_uri was valid.
/// </returns>
ValueTask<bool> ValidatePostLogoutRedirectUriAsync(object application, string address, CancellationToken cancellationToken = default);
/// <summary> /// <summary>
/// Validates the redirect_uri to ensure it's associated with an application. /// Validates the redirect_uri to ensure it's associated with an application.
/// </summary> /// </summary>

3
src/OpenIddict.Abstractions/OpenIddictResources.resx

@ -2359,6 +2359,9 @@ This may indicate that the hashed entry is corrupted or malformed.</value>
<data name="ID6201" xml:space="preserve"> <data name="ID6201" xml:space="preserve">
<value>The post-logout redirection request was successfully validated.</value> <value>The post-logout redirection request was successfully validated.</value>
</data> </data>
<data name="ID6202" xml:space="preserve">
<value>Client validation failed because '{PostLogoutRedirectUri}' was not a valid post_logout_redirect_uri for {Client}.</value>
</data>
<data name="ID8000" xml:space="preserve"> <data name="ID8000" xml:space="preserve">
<value>https://documentation.openiddict.com/errors/{0}</value> <value>https://documentation.openiddict.com/errors/{0}</value>
</data> </data>

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

@ -1289,6 +1289,44 @@ public class OpenIddictApplicationManager<TApplication> : IOpenIddictApplication
return true; return true;
} }
/// <summary>
/// Validates the post_logout_redirect_uri to ensure it's associated with an application.
/// </summary>
/// <param name="application">The application.</param>
/// <param name="address">The address that should be compared to one of the post_logout_redirect_uri stored in the database.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <remarks>Note: if no client_id parameter is specified in logout requests, this method may not be called.</remarks>
/// <returns>
/// A <see cref="ValueTask"/> that can be used to monitor the asynchronous operation,
/// whose result returns a boolean indicating whether the post_logout_redirect_uri was valid.
/// </returns>
public virtual async ValueTask<bool> ValidatePostLogoutRedirectUriAsync(
TApplication application, string address, CancellationToken cancellationToken = default)
{
if (application is null)
{
throw new ArgumentNullException(nameof(application));
}
if (string.IsNullOrEmpty(address))
{
throw new ArgumentException(SR.GetResourceString(SR.ID0143), nameof(address));
}
foreach (var uri in await Store.GetPostLogoutRedirectUrisAsync(application, cancellationToken))
{
// Note: the post_logout_redirect_uri must be compared using case-sensitive "Simple String Comparison".
if (string.Equals(uri, address, StringComparison.Ordinal))
{
return true;
}
}
Logger.LogInformation(SR.GetResourceString(SR.ID6202), address, await GetClientIdAsync(application, cancellationToken));
return false;
}
/// <summary> /// <summary>
/// Validates the redirect_uri to ensure it's associated with an application. /// Validates the redirect_uri to ensure it's associated with an application.
/// </summary> /// </summary>
@ -1681,6 +1719,10 @@ public class OpenIddictApplicationManager<TApplication> : IOpenIddictApplication
ValueTask<bool> IOpenIddictApplicationManager.ValidateClientSecretAsync(object application, string secret, CancellationToken cancellationToken) ValueTask<bool> IOpenIddictApplicationManager.ValidateClientSecretAsync(object application, string secret, CancellationToken cancellationToken)
=> ValidateClientSecretAsync((TApplication) application, secret, cancellationToken); => ValidateClientSecretAsync((TApplication) application, secret, cancellationToken);
/// <inheritdoc/>
ValueTask<bool> IOpenIddictApplicationManager.ValidatePostLogoutRedirectUriAsync(object application, string address, CancellationToken cancellationToken)
=> ValidatePostLogoutRedirectUriAsync((TApplication) application, address, cancellationToken);
/// <inheritdoc/> /// <inheritdoc/>
ValueTask<bool> IOpenIddictApplicationManager.ValidateRedirectUriAsync(object application, string address, CancellationToken cancellationToken) ValueTask<bool> IOpenIddictApplicationManager.ValidateRedirectUriAsync(object application, string address, CancellationToken cancellationToken)
=> ValidateRedirectUriAsync((TApplication) application, address, cancellationToken); => ValidateRedirectUriAsync((TApplication) application, address, cancellationToken);

3
src/OpenIddict.Server/OpenIddictServerHandlers.Session.cs

@ -486,8 +486,7 @@ public static partial class OpenIddictServerHandlers
var application = await _applicationManager.FindByClientIdAsync(context.ClientId) ?? var application = await _applicationManager.FindByClientIdAsync(context.ClientId) ??
throw new InvalidOperationException(SR.GetResourceString(SR.ID0032)); throw new InvalidOperationException(SR.GetResourceString(SR.ID0032));
var addresses = await _applicationManager.GetPostLogoutRedirectUrisAsync(application); if (!await _applicationManager.ValidatePostLogoutRedirectUriAsync(application, context.PostLogoutRedirectUri))
if (!addresses.Contains(context.PostLogoutRedirectUri, StringComparer.Ordinal))
{ {
context.Logger.LogInformation(SR.GetResourceString(SR.ID6128), context.PostLogoutRedirectUri); context.Logger.LogInformation(SR.GetResourceString(SR.ID6128), context.PostLogoutRedirectUri);

80
test/OpenIddict.Server.IntegrationTests/OpenIddictServerIntegrationTests.Session.cs

@ -216,6 +216,81 @@ public abstract partial class OpenIddictServerIntegrationTests
Mock.Get(manager).Verify(manager => manager.FindByPostLogoutRedirectUriAsync("http://www.fabrikam.com/path", It.IsAny<CancellationToken>()), Times.Once()); Mock.Get(manager).Verify(manager => manager.FindByPostLogoutRedirectUriAsync("http://www.fabrikam.com/path", It.IsAny<CancellationToken>()), Times.Once());
} }
[Fact]
public async Task ValidateLogoutRequest_RequestIsRejectedWhenPostLogoutRedirectUriForExplicitClientIsInvalid()
{
// Arrange
var application = new OpenIddictApplication();
var manager = CreateApplicationManager(mock =>
{
mock.Setup(manager => manager.FindByClientIdAsync("Fabrikam", It.IsAny<CancellationToken>()))
.ReturnsAsync(application);
mock.Setup(manager => manager.ValidatePostLogoutRedirectUriAsync(application, "http://www.fabrikam.com/path", It.IsAny<CancellationToken>()))
.ReturnsAsync(false);
});
await using var server = await CreateServerAsync(options =>
{
options.Services.AddSingleton(manager);
options.Configure(options => options.IgnoreEndpointPermissions = false);
});
await using var client = await server.CreateClientAsync();
// Act
var response = await client.PostAsync("/connect/logout", new OpenIddictRequest
{
ClientId = "Fabrikam",
PostLogoutRedirectUri = "http://www.fabrikam.com/path"
});
// Assert
Assert.Equal(Errors.InvalidRequest, response.Error);
Assert.Equal(SR.FormatID2052(Parameters.PostLogoutRedirectUri), response.ErrorDescription);
Assert.Equal(SR.FormatID8000(SR.ID2052), response.ErrorUri);
Mock.Get(manager).Verify(manager => manager.FindByClientIdAsync("Fabrikam", It.IsAny<CancellationToken>()), Times.AtLeastOnce());
Mock.Get(manager).Verify(manager => manager.ValidatePostLogoutRedirectUriAsync(application,
"http://www.fabrikam.com/path", It.IsAny<CancellationToken>()), Times.Once());
}
[Fact]
public async Task ValidateLogoutRequest_RequestIsRejectedWhenPostLogoutRedirectUriForImplicitClientIsInvalid()
{
// Arrange
var manager = CreateApplicationManager(mock =>
{
mock.Setup(manager => manager.FindByPostLogoutRedirectUriAsync("http://www.fabrikam.com/path", It.IsAny<CancellationToken>()))
.Returns(AsyncEnumerable.Empty<OpenIddictApplication>());
});
await using var server = await CreateServerAsync(options =>
{
options.Services.AddSingleton(manager);
options.Configure(options => options.IgnoreEndpointPermissions = false);
});
await using var client = await server.CreateClientAsync();
// Act
var response = await client.PostAsync("/connect/logout", new OpenIddictRequest
{
PostLogoutRedirectUri = "http://www.fabrikam.com/path"
});
// Assert
Assert.Equal(Errors.InvalidRequest, response.Error);
Assert.Equal(SR.FormatID2052(Parameters.PostLogoutRedirectUri), response.ErrorDescription);
Assert.Equal(SR.FormatID8000(SR.ID2052), response.ErrorUri);
Mock.Get(manager).Verify(manager => manager.FindByPostLogoutRedirectUriAsync(
"http://www.fabrikam.com/path", It.IsAny<CancellationToken>()), Times.Once());
}
[Fact] [Fact]
public async Task ValidateLogoutRequest_RequestIsRejectedWhenNoMatchingApplicationIsGrantedEndpointPermission() public async Task ValidateLogoutRequest_RequestIsRejectedWhenNoMatchingApplicationIsGrantedEndpointPermission()
{ {
@ -502,8 +577,8 @@ public abstract partial class OpenIddictServerIntegrationTests
mock.Setup(manager => manager.FindByClientIdAsync("Fabrikam", It.IsAny<CancellationToken>())) mock.Setup(manager => manager.FindByClientIdAsync("Fabrikam", It.IsAny<CancellationToken>()))
.ReturnsAsync(application); .ReturnsAsync(application);
mock.Setup(manager => manager.GetPostLogoutRedirectUrisAsync(application, It.IsAny<CancellationToken>())) mock.Setup(manager => manager.ValidatePostLogoutRedirectUriAsync(application, "http://www.fabrikam.com/path", It.IsAny<CancellationToken>()))
.ReturnsAsync(ImmutableArray.Create("http://www.fabrikam.com/path")); .ReturnsAsync(true);
mock.Setup(manager => manager.HasPermissionAsync(application, mock.Setup(manager => manager.HasPermissionAsync(application,
Permissions.Endpoints.Logout, It.IsAny<CancellationToken>())) Permissions.Endpoints.Logout, It.IsAny<CancellationToken>()))
@ -564,6 +639,7 @@ public abstract partial class OpenIddictServerIntegrationTests
Assert.Equal("af0ifjsldkj", response.State); Assert.Equal("af0ifjsldkj", response.State);
Mock.Get(manager).Verify(manager => manager.FindByClientIdAsync("Fabrikam", It.IsAny<CancellationToken>()), Times.AtLeastOnce()); Mock.Get(manager).Verify(manager => manager.FindByClientIdAsync("Fabrikam", It.IsAny<CancellationToken>()), Times.AtLeastOnce());
Mock.Get(manager).Verify(manager => manager.ValidatePostLogoutRedirectUriAsync(application, "http://www.fabrikam.com/path", It.IsAny<CancellationToken>()), Times.Once());
Mock.Get(manager).Verify(manager => manager.HasPermissionAsync(application, Permissions.Endpoints.Logout, It.IsAny<CancellationToken>()), Times.Once()); Mock.Get(manager).Verify(manager => manager.HasPermissionAsync(application, Permissions.Endpoints.Logout, It.IsAny<CancellationToken>()), Times.Once());
} }

Loading…
Cancel
Save