Browse Source

Update OpenIddictServerBuilder to allow calling SetAccessTokenLifetime/SetAuthorizationCodeLifetime/SetIdentityTokenLifetime/SetRefreshTokenLifetime with null values

pull/650/head
Kévin Chalet 8 years ago
parent
commit
a65deb4463
  1. 4
      src/OpenIddict.Server/Internal/OpenIddictServerProvider.Helpers.cs
  2. 12
      src/OpenIddict.Server/OpenIddictServerBuilder.cs
  3. 134
      test/OpenIddict.Server.Tests/Internal/OpenIddictServerProviderTests.cs
  4. 64
      test/OpenIddict.Server.Tests/OpenIddictServerBuilderTests.cs

4
src/OpenIddict.Server/Internal/OpenIddictServerProvider.Helpers.cs

@ -504,14 +504,14 @@ namespace OpenIddict.Server
{
// Note: the request cancellation token is deliberately not used here to ensure the caller
// cannot prevent this operation from being executed by resetting the TCP connection.
var date = options.SystemClock.UtcNow + lifetime;
var date = options.SystemClock.UtcNow + lifetime.Value;
await _tokenManager.ExtendAsync(token, date);
_logger.LogInformation("The expiration date of the refresh token '{Identifier}' " +
"was automatically updated: {Date}.", identifier, date);
}
else
else if (await _tokenManager.GetExpirationDateAsync(token) != null)
{
// Note: the request cancellation token is deliberately not used here to ensure the caller
// cannot prevent this operation from being executed by resetting the TCP connection.

12
src/OpenIddict.Server/OpenIddictServerBuilder.cs

@ -701,29 +701,32 @@ namespace Microsoft.Extensions.DependencyInjection
/// a new access token by making a grant_type=refresh_token token request
/// or a prompt=none authorization request, depending on the selected flow.
/// Using long-lived access tokens or tokens that never expire is not recommended.
/// While discouraged, <c>null</c> can be specified to issue tokens that never expire.
/// </summary>
/// <param name="lifetime">The access token lifetime.</param>
/// <returns>The <see cref="OpenIddictServerBuilder"/>.</returns>
public OpenIddictServerBuilder SetAccessTokenLifetime(TimeSpan lifetime)
public OpenIddictServerBuilder SetAccessTokenLifetime([CanBeNull] TimeSpan? lifetime)
=> Configure(options => options.AccessTokenLifetime = lifetime);
/// <summary>
/// Sets the authorization code lifetime, after which client applications
/// are unable to send a grant_type=authorization_code token request.
/// Using short-lived authorization codes is strongly recommended.
/// While discouraged, <c>null</c> can be specified to issue codes that never expire.
/// </summary>
/// <param name="lifetime">The authorization code lifetime.</param>
/// <returns>The <see cref="OpenIddictServerBuilder"/>.</returns>
public OpenIddictServerBuilder SetAuthorizationCodeLifetime(TimeSpan lifetime)
public OpenIddictServerBuilder SetAuthorizationCodeLifetime([CanBeNull] TimeSpan? lifetime)
=> Configure(options => options.AuthorizationCodeLifetime = lifetime);
/// <summary>
/// Sets the identity token lifetime, after which client
/// applications should refuse processing identity tokens.
/// While discouraged, <c>null</c> can be specified to issue tokens that never expire.
/// </summary>
/// <param name="lifetime">The identity token lifetime.</param>
/// <returns>The <see cref="OpenIddictServerBuilder"/>.</returns>
public OpenIddictServerBuilder SetIdentityTokenLifetime(TimeSpan lifetime)
public OpenIddictServerBuilder SetIdentityTokenLifetime([CanBeNull] TimeSpan? lifetime)
=> Configure(options => options.IdentityTokenLifetime = lifetime);
/// <summary>
@ -731,10 +734,11 @@ namespace Microsoft.Extensions.DependencyInjection
/// a new authorization from the user. When sliding expiration is enabled,
/// a new refresh token is always issued to the client application,
/// which prolongs the validity period of the refresh token.
/// While discouraged, <c>null</c> can be specified to issue tokens that never expire.
/// </summary>
/// <param name="lifetime">The refresh token lifetime.</param>
/// <returns>The <see cref="OpenIddictServerBuilder"/>.</returns>
public OpenIddictServerBuilder SetRefreshTokenLifetime(TimeSpan lifetime)
public OpenIddictServerBuilder SetRefreshTokenLifetime([CanBeNull] TimeSpan? lifetime)
=> Configure(options => options.RefreshTokenLifetime = lifetime);
/// <summary>

134
test/OpenIddict.Server.Tests/Internal/OpenIddictServerProviderTests.cs

@ -1005,7 +1005,7 @@ namespace OpenIddict.Server.Tests
new AuthenticationProperties(),
OpenIddictServerDefaults.AuthenticationScheme);
ticket.SetProperty(OpenIddictConstants.Properties.InternalTokenId, "3E228451-1555-46F7-A471-951EFBA23A56");
ticket.SetProperty(OpenIddictConstants.Properties.InternalTokenId, "60FFF7EA-F98E-437B-937E-5073CC313103");
ticket.SetTokenUsage(OpenIdConnectConstants.TokenUsages.RefreshToken);
ticket.SetScopes(OpenIdConnectConstants.Scopes.OpenId, OpenIdConnectConstants.Scopes.OfflineAccess);
@ -1063,6 +1063,138 @@ namespace OpenIddict.Server.Tests
It.IsAny<CancellationToken>()), Times.Never());
}
[Fact]
public async Task ProcessSigninResponse_DoesNotUpdateExpirationDateWhenAlreadyNull()
{
// Arrange
var ticket = new AuthenticationTicket(
new ClaimsPrincipal(),
new AuthenticationProperties(),
OpenIddictServerDefaults.AuthenticationScheme);
ticket.SetProperty(OpenIddictConstants.Properties.InternalTokenId, "60FFF7EA-F98E-437B-937E-5073CC313103");
ticket.SetTokenUsage(OpenIdConnectConstants.TokenUsages.RefreshToken);
ticket.SetScopes(OpenIdConnectConstants.Scopes.OpenId, OpenIdConnectConstants.Scopes.OfflineAccess);
var format = new Mock<ISecureDataFormat<AuthenticationTicket>>();
format.Setup(mock => mock.Protect(It.IsAny<AuthenticationTicket>()))
.Returns("8xLOxBtZp8");
format.Setup(mock => mock.Unprotect("8xLOxBtZp8"))
.Returns(ticket);
var token = new OpenIddictToken();
var manager = CreateTokenManager(instance =>
{
instance.Setup(mock => mock.FindByIdAsync("60FFF7EA-F98E-437B-937E-5073CC313103", It.IsAny<CancellationToken>()))
.ReturnsAsync(token);
instance.Setup(mock => mock.IsRedeemedAsync(token, It.IsAny<CancellationToken>()))
.ReturnsAsync(false);
instance.Setup(mock => mock.IsValidAsync(token, It.IsAny<CancellationToken>()))
.ReturnsAsync(true);
instance.Setup(mock => mock.GetExpirationDateAsync(token, It.IsAny<CancellationToken>()))
.Returns(new ValueTask<DateTimeOffset?>(result: null));
});
var server = CreateAuthorizationServer(builder =>
{
builder.Services.AddSingleton(manager);
builder.Configure(options =>
{
options.SystemClock = Mock.Of<ISystemClock>(mock => mock.UtcNow ==
new DateTimeOffset(2017, 01, 05, 00, 00, 00, TimeSpan.Zero));
options.RefreshTokenLifetime = null;
options.RefreshTokenFormat = format.Object;
});
});
var client = new OpenIdConnectClient(server.CreateClient());
// Act
var response = await client.PostAsync(TokenEndpoint, new OpenIdConnectRequest
{
GrantType = OpenIdConnectConstants.GrantTypes.RefreshToken,
RefreshToken = "8xLOxBtZp8"
});
// Assert
Assert.Null(response.RefreshToken);
Mock.Get(manager).Verify(mock => mock.ExtendAsync(token, null, It.IsAny<CancellationToken>()), Times.Never());
}
[Fact]
public async Task ProcessSigninResponse_SetsExpirationDateToNullWhenLifetimeIsNull()
{
// Arrange
var ticket = new AuthenticationTicket(
new ClaimsPrincipal(),
new AuthenticationProperties(),
OpenIddictServerDefaults.AuthenticationScheme);
ticket.SetProperty(OpenIddictConstants.Properties.InternalTokenId, "60FFF7EA-F98E-437B-937E-5073CC313103");
ticket.SetTokenUsage(OpenIdConnectConstants.TokenUsages.RefreshToken);
ticket.SetScopes(OpenIdConnectConstants.Scopes.OpenId, OpenIdConnectConstants.Scopes.OfflineAccess);
var format = new Mock<ISecureDataFormat<AuthenticationTicket>>();
format.Setup(mock => mock.Protect(It.IsAny<AuthenticationTicket>()))
.Returns("8xLOxBtZp8");
format.Setup(mock => mock.Unprotect("8xLOxBtZp8"))
.Returns(ticket);
var token = new OpenIddictToken();
var manager = CreateTokenManager(instance =>
{
instance.Setup(mock => mock.FindByIdAsync("60FFF7EA-F98E-437B-937E-5073CC313103", It.IsAny<CancellationToken>()))
.ReturnsAsync(token);
instance.Setup(mock => mock.IsRedeemedAsync(token, It.IsAny<CancellationToken>()))
.ReturnsAsync(false);
instance.Setup(mock => mock.IsValidAsync(token, It.IsAny<CancellationToken>()))
.ReturnsAsync(true);
instance.Setup(mock => mock.GetExpirationDateAsync(token, It.IsAny<CancellationToken>()))
.Returns(new ValueTask<DateTimeOffset?>(DateTimeOffset.Now + TimeSpan.FromDays(1)));
});
var server = CreateAuthorizationServer(builder =>
{
builder.Services.AddSingleton(manager);
builder.Configure(options =>
{
options.SystemClock = Mock.Of<ISystemClock>(mock => mock.UtcNow ==
new DateTimeOffset(2017, 01, 05, 00, 00, 00, TimeSpan.Zero));
options.RefreshTokenLifetime = null;
options.RefreshTokenFormat = format.Object;
});
});
var client = new OpenIdConnectClient(server.CreateClient());
// Act
var response = await client.PostAsync(TokenEndpoint, new OpenIdConnectRequest
{
GrantType = OpenIdConnectConstants.GrantTypes.RefreshToken,
RefreshToken = "8xLOxBtZp8"
});
// Assert
Assert.Null(response.RefreshToken);
Mock.Get(manager).Verify(mock => mock.ExtendAsync(token, null, It.IsAny<CancellationToken>()), Times.Once());
}
[Fact]
public async Task ProcessSigninResponse_IgnoresErrorWhenExtendingLifetimeOfExistingTokenFailed()
{

64
test/OpenIddict.Server.Tests/OpenIddictServerBuilderTests.cs

@ -601,6 +601,22 @@ namespace OpenIddict.Server.Tests
Assert.Equal(TimeSpan.FromMinutes(42), options.AccessTokenLifetime);
}
[Fact]
public void SetAccessTokenLifetime_AccessTokenLifetimeCanBeSetToNull()
{
// Arrange
var services = CreateServices();
var builder = CreateBuilder(services);
// Act
builder.SetAccessTokenLifetime(null);
var options = GetOptions(services);
// Assert
Assert.Null(options.AccessTokenLifetime);
}
[Fact]
public void SetAuthorizationCodeLifetime_DefaultAuthorizationCodeLifetimeIsReplaced()
{
@ -617,6 +633,22 @@ namespace OpenIddict.Server.Tests
Assert.Equal(TimeSpan.FromMinutes(42), options.AuthorizationCodeLifetime);
}
[Fact]
public void SetAuthorizationCodeLifetime_AuthorizationCodeLifetimeCanBeSetToNull()
{
// Arrange
var services = CreateServices();
var builder = CreateBuilder(services);
// Act
builder.SetAuthorizationCodeLifetime(null);
var options = GetOptions(services);
// Assert
Assert.Null(options.AuthorizationCodeLifetime);
}
[Fact]
public void SetIdentityTokenLifetime_DefaultIdentityTokenLifetimeIsReplaced()
{
@ -633,6 +665,22 @@ namespace OpenIddict.Server.Tests
Assert.Equal(TimeSpan.FromMinutes(42), options.IdentityTokenLifetime);
}
[Fact]
public void SetIdentityTokenLifetime_IdentityTokenLifetimeCanBeSetToNull()
{
// Arrange
var services = CreateServices();
var builder = CreateBuilder(services);
// Act
builder.SetIdentityTokenLifetime(null);
var options = GetOptions(services);
// Assert
Assert.Null(options.IdentityTokenLifetime);
}
[Fact]
public void SetRefreshTokenLifetime_DefaultRefreshTokenLifetimeIsReplaced()
{
@ -649,6 +697,22 @@ namespace OpenIddict.Server.Tests
Assert.Equal(TimeSpan.FromMinutes(42), options.RefreshTokenLifetime);
}
[Fact]
public void SetRefreshTokenLifetime_RefreshTokenLifetimeCanBeSetToNull()
{
// Arrange
var services = CreateServices();
var builder = CreateBuilder(services);
// Act
builder.SetRefreshTokenLifetime(null);
var options = GetOptions(services);
// Assert
Assert.Null(options.RefreshTokenLifetime);
}
[Fact]
public void SetIssuer_AddressIsReplaced()
{

Loading…
Cancel
Save