Browse Source

Backport the post_logout_redirect_uri handling changes to OpenIddict 1.x

pull/553/head
Kévin Chalet 9 years ago
parent
commit
f034344bea
  1. 72
      src/OpenIddict.Core/Managers/OpenIddictApplicationManager.cs
  2. 19
      src/OpenIddict.Core/Stores/IOpenIddictApplicationStore.cs
  3. 24
      src/OpenIddict.EntityFrameworkCore/Stores/OpenIddictApplicationStore.cs
  4. 20
      src/OpenIddict/OpenIddictProvider.Session.cs
  5. 22
      test/OpenIddict.Tests/OpenIddictProviderTests.Session.cs

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

@ -154,17 +154,31 @@ namespace OpenIddict.Core
}
/// <summary>
/// Retrieves an application using its post_logout_redirect_uri.
/// Retrieves all the applications associated with the specified post_logout_redirect_uri.
/// </summary>
/// <param name="url">The post_logout_redirect_uri associated with the application.</param>
/// <param name="address">The post_logout_redirect_uri associated with the applications.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation, whose result
/// returns the client application corresponding to the post_logout_redirect_uri.
/// returns the client applications corresponding to the specified post_logout_redirect_uri.
/// </returns>
public virtual Task<TApplication> FindByLogoutRedirectUri(string url, CancellationToken cancellationToken)
public virtual Task<TApplication[]> FindByLogoutRedirectUri(string address, CancellationToken cancellationToken)
{
return Store.FindByLogoutRedirectUri(url, cancellationToken);
return Store.FindByLogoutRedirectUri(address, cancellationToken);
}
/// <summary>
/// Retrieves all the applications associated with the specified redirect_uri.
/// </summary>
/// <param name="address">The redirect_uri associated with the applications.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation, whose result
/// returns the client applications corresponding to the specified redirect_uri.
/// </returns>
public virtual Task<TApplication[]> FindByRedirectUri(string address, CancellationToken cancellationToken)
{
return Store.FindByRedirectUri(address, cancellationToken);
}
/// <summary>
@ -469,9 +483,8 @@ namespace OpenIddict.Core
var address = await Store.GetRedirectUriAsync(application, cancellationToken);
if (!string.IsNullOrEmpty(address))
{
Uri uri;
// Ensure the redirect_uri is a valid and absolute URL.
if (!Uri.TryCreate(address, UriKind.Absolute, out uri))
if (!Uri.TryCreate(address, UriKind.Absolute, out Uri uri))
{
throw new ArgumentException("The redirect_uri must be an absolute URL.");
}
@ -487,9 +500,8 @@ namespace OpenIddict.Core
address = await Store.GetLogoutRedirectUriAsync(application, cancellationToken);
if (!string.IsNullOrEmpty(address))
{
Uri uri;
// Ensure the post_logout_redirect_uri is a valid and absolute URL.
if (!Uri.TryCreate(address, UriKind.Absolute, out uri))
if (!Uri.TryCreate(address, UriKind.Absolute, out Uri uri))
{
throw new ArgumentException("The post_logout_redirect_uri must be an absolute URL.");
}
@ -502,6 +514,34 @@ namespace OpenIddict.Core
}
}
/// <summary>
/// Validates the specified post_logout_redirect_uri.
/// </summary>
/// <param name="address">The address that should be compared to 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>
/// <returns>
/// A <see cref="Task"/> 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 Task<bool> ValidateLogoutRedirectUriAsync(string address, CancellationToken cancellationToken)
{
// Warning: SQL engines like Microsoft SQL Server are known to use case-insensitive lookups by default.
// To ensure a case-sensitive comparison is used, string.Equals(Ordinal) is manually called here.
foreach (var application in await Store.FindByLogoutRedirectUri(address, cancellationToken))
{
// Note: the post_logout_redirect_uri must be compared using case-sensitive "Simple String Comparison".
if (string.Equals(address, await Store.GetLogoutRedirectUriAsync(application, cancellationToken), StringComparison.Ordinal))
{
return true;
}
}
Logger.LogWarning("Client validation failed because '{PostLogoutRedirectUri}' " +
"was not a valid post_logout_redirect_uri.", address);
return false;
}
/// <summary>
/// Validates the redirect_uri associated with an application.
/// </summary>
@ -519,15 +559,17 @@ namespace OpenIddict.Core
throw new ArgumentNullException(nameof(application));
}
if (!string.Equals(address, await Store.GetRedirectUriAsync(application, cancellationToken), StringComparison.Ordinal))
// Note: the redirect_uri must be compared using case-sensitive "Simple String Comparison".
// See http://openid.net/specs/openid-connect-core-1_0.html#AuthRequest for more information.
if (string.Equals(address, await Store.GetRedirectUriAsync(application, cancellationToken), StringComparison.Ordinal))
{
Logger.LogWarning("Client validation failed because {RedirectUri} was not a valid redirect_uri " +
"for {Client}", address, await GetDisplayNameAsync(application, cancellationToken));
return false;
return true;
}
return true;
Logger.LogWarning("Client validation failed because '{RedirectUri}' was not a valid redirect_uri " +
"for '{Client}'.", address, await GetDisplayNameAsync(application, cancellationToken));
return false;
}
/// <summary>

19
src/OpenIddict.Core/Stores/IOpenIddictApplicationStore.cs

@ -60,15 +60,26 @@ namespace OpenIddict.Core
Task<TApplication> FindByClientIdAsync(string identifier, CancellationToken cancellationToken);
/// <summary>
/// Retrieves an application using its post_logout_redirect_uri.
/// Retrieves all the applications associated with the specified post_logout_redirect_uri.
/// </summary>
/// <param name="url">The post_logout_redirect_uri associated with the application.</param>
/// <param name="address">The post_logout_redirect_uri associated with the applications.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation, whose result
/// returns the client application corresponding to the post_logout_redirect_uri.
/// returns the client applications corresponding to the specified post_logout_redirect_uri.
/// </returns>
Task<TApplication> FindByLogoutRedirectUri(string url, CancellationToken cancellationToken);
Task<TApplication[]> FindByLogoutRedirectUri(string address, CancellationToken cancellationToken);
/// <summary>
/// Retrieves all the applications associated with the specified redirect_uri.
/// </summary>
/// <param name="address">The redirect_uri associated with the applications.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation, whose result
/// returns the client applications corresponding to the specified redirect_uri.
/// </returns>
Task<TApplication[]> FindByRedirectUri(string address, CancellationToken cancellationToken);
/// <summary>
/// Retrieves the client identifier associated with an application.

24
src/OpenIddict.EntityFrameworkCore/Stores/OpenIddictApplicationStore.cs

@ -156,17 +156,31 @@ namespace OpenIddict.EntityFrameworkCore
}
/// <summary>
/// Retrieves an application using its post_logout_redirect_uri.
/// Retrieves all the applications associated with the specified post_logout_redirect_uri.
/// </summary>
/// <param name="url">The post_logout_redirect_uri associated with the application.</param>
/// <param name="address">The post_logout_redirect_uri associated with the applications.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation, whose result
/// returns the client application corresponding to the post_logout_redirect_uri.
/// returns the client applications corresponding to the specified post_logout_redirect_uri.
/// </returns>
public virtual Task<TApplication> FindByLogoutRedirectUri(string url, CancellationToken cancellationToken)
public virtual Task<TApplication[]> FindByLogoutRedirectUri(string address, CancellationToken cancellationToken)
{
return Applications.SingleOrDefaultAsync(application => application.LogoutRedirectUri == url, cancellationToken);
return Applications.Where(application => application.LogoutRedirectUri == address).ToArrayAsync(cancellationToken);
}
/// <summary>
/// Retrieves all the applications associated with the specified redirect_uri.
/// </summary>
/// <param name="address">The redirect_uri associated with the applications.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation, whose result
/// returns the client applications corresponding to the specified redirect_uri.
/// </returns>
public virtual Task<TApplication[]> FindByRedirectUri(string address, CancellationToken cancellationToken)
{
return Applications.Where(application => application.RedirectUri == address).ToArrayAsync(cancellationToken);
}
/// <summary>

20
src/OpenIddict/OpenIddictProvider.Session.cs

@ -88,21 +88,17 @@ namespace OpenIddict
var logger = context.HttpContext.RequestServices.GetRequiredService<ILogger<OpenIddictProvider<TApplication, TAuthorization, TScope, TToken>>>();
// If an optional post_logout_redirect_uri was provided, validate it.
if (!string.IsNullOrEmpty(context.PostLogoutRedirectUri))
if (!string.IsNullOrEmpty(context.PostLogoutRedirectUri) &&
!await applications.ValidateLogoutRedirectUriAsync(context.PostLogoutRedirectUri, context.HttpContext.RequestAborted))
{
var application = await applications.FindByLogoutRedirectUri(context.PostLogoutRedirectUri, context.HttpContext.RequestAborted);
if (application == null)
{
logger.LogError("The logout request was rejected because the client application corresponding " +
"to the specified post_logout_redirect_uri was not found in the database: " +
"'{PostLogoutRedirectUri}'.", context.PostLogoutRedirectUri);
logger.LogError("The logout request was rejected because the specified post_logout_redirect_uri " +
"was invalid: '{PostLogoutRedirectUri}'.", context.PostLogoutRedirectUri);
context.Reject(
error: OpenIdConnectConstants.Errors.InvalidClient,
description: "Invalid post_logout_redirect_uri.");
context.Reject(
error: OpenIdConnectConstants.Errors.InvalidClient,
description: "Invalid post_logout_redirect_uri.");
return;
}
return;
}
context.Validate();

22
test/OpenIddict.Tests/OpenIddictProviderTests.Session.cs

@ -64,8 +64,8 @@ namespace OpenIddict.Tests
// Arrange
var manager = CreateApplicationManager(instance =>
{
instance.Setup(mock => mock.FindByLogoutRedirectUri("http://www.fabrikam.com/path", It.IsAny<CancellationToken>()))
.ReturnsAsync(value: null);
instance.Setup(mock => mock.ValidateLogoutRedirectUriAsync("http://www.fabrikam.com/path", It.IsAny<CancellationToken>()))
.ReturnsAsync(false);
});
var server = CreateAuthorizationServer(builder =>
@ -85,7 +85,7 @@ namespace OpenIddict.Tests
Assert.Equal(OpenIdConnectConstants.Errors.InvalidClient, response.Error);
Assert.Equal("Invalid post_logout_redirect_uri.", response.ErrorDescription);
Mock.Get(manager).Verify(mock => mock.FindByLogoutRedirectUri("http://www.fabrikam.com/path", It.IsAny<CancellationToken>()), Times.Once());
Mock.Get(manager).Verify(mock => mock.ValidateLogoutRedirectUriAsync("http://www.fabrikam.com/path", It.IsAny<CancellationToken>()), Times.Once());
}
[Fact]
@ -98,10 +98,8 @@ namespace OpenIddict.Tests
{
builder.Services.AddSingleton(CreateApplicationManager(instance =>
{
var application = new OpenIddictApplication();
instance.Setup(mock => mock.FindByLogoutRedirectUri("http://www.fabrikam.com/path", It.IsAny<CancellationToken>()))
.ReturnsAsync(application);
instance.Setup(mock => mock.ValidateLogoutRedirectUriAsync("http://www.fabrikam.com/path", It.IsAny<CancellationToken>()))
.ReturnsAsync(true);
}));
builder.Services.AddSingleton(cache.Object);
@ -137,10 +135,8 @@ namespace OpenIddict.Tests
{
builder.Services.AddSingleton(CreateApplicationManager(instance =>
{
var application = new OpenIddictApplication();
instance.Setup(mock => mock.FindByLogoutRedirectUri("http://www.fabrikam.com/path", It.IsAny<CancellationToken>()))
.ReturnsAsync(application);
instance.Setup(mock => mock.ValidateLogoutRedirectUriAsync("http://www.fabrikam.com/path", It.IsAny<CancellationToken>()))
.ReturnsAsync(true);
}));
});
@ -165,8 +161,8 @@ namespace OpenIddict.Tests
{
builder.Services.AddSingleton(CreateApplicationManager(instance =>
{
instance.Setup(mock => mock.FindByLogoutRedirectUri("http://www.fabrikam.com/path", It.IsAny<CancellationToken>()))
.ReturnsAsync(value: null);
instance.Setup(mock => mock.ValidateLogoutRedirectUriAsync("http://www.fabrikam.com/path", It.IsAny<CancellationToken>()))
.ReturnsAsync(false);
}));
builder.EnableAuthorizationEndpoint("/logout-status-code-middleware");

Loading…
Cancel
Save