Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 22 additions & 0 deletions Client.UnitTests/Impl/Builder/CamundaCloudTokenProviderTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -123,4 +123,26 @@ public async Task ShouldNotThrowObjectDisposedExceptionWhenTokenExpires()
// then
Assert.AreEqual(2, MessageHandlerStub.RequestCount);
}

[Test]
public void ShouldThrowArgumentExceptionForInvalidAccessTokenDueDateTolerance()
{
// given
var builder = new CamundaCloudTokenProviderBuilder();

// then
Assert.Throws<ArgumentException>(() => builder
.UseAccessTokenDueDateTolerance(TimeSpan.FromSeconds(-2)));
}

[Test]
public void ShouldNotThrowArgumentExceptionForValidAccessTokenDueDateTolerance()
{
// given
var builder = new CamundaCloudTokenProviderBuilder();

// then
Assert.DoesNotThrow(() => builder
.UseAccessTokenDueDateTolerance(TimeSpan.FromSeconds(2)));
}
}
62 changes: 62 additions & 0 deletions Client.UnitTests/Impl/Misc/PersistedAccessTokenCacheTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,48 @@ public async Task ShouldResolveNewTokenAfterExpiry()
Assert.AreEqual(2, fetchCounter);
}

[Test]
public async Task ShouldResolveNewTokenDuringDueDateTolerancePeriod()
{
// given
var expectedDueDateTolerance = TimeSpan.FromSeconds(30);
var fetchCounter = 0;
var accessTokenCache = new PersistedAccessTokenCache(
Path.Combine(tempPath, TestContext.CurrentContext.Test.Name),
() => Task.FromResult(new AccessToken("token-" + fetchCounter++,
DateTimeOffset.UtcNow.AddSeconds(15).ToUnixTimeMilliseconds())),
dueDateTolerance: expectedDueDateTolerance);

// when
_ = await accessTokenCache.Get("test");
var token = await accessTokenCache.Get("test");

// then
Assert.AreEqual("token-1", token);
Assert.AreEqual(2, fetchCounter);
}

[Test]
public async Task ShouldNotResolveNewTokenBeforeDueDateTolerancePeriod()
{
// given
var expectedDueDateTolerance = TimeSpan.FromSeconds(30);
var fetchCounter = 0;
var accessTokenCache = new PersistedAccessTokenCache(
Path.Combine(tempPath, TestContext.CurrentContext.Test.Name),
() => Task.FromResult(new AccessToken("token-" + fetchCounter++,
DateTimeOffset.UtcNow.AddSeconds(45).ToUnixTimeMilliseconds())),
dueDateTolerance: expectedDueDateTolerance);

// when
_ = await accessTokenCache.Get("test");
var token = await accessTokenCache.Get("test");

// then
Assert.AreEqual("token-0", token);
Assert.AreEqual(1, fetchCounter);
}

[Test]
public async Task ShouldReflectTokenOnDiskAfterExpiry()
{
Expand Down Expand Up @@ -163,6 +205,26 @@ public async Task ShouldPersistTokenToDisk()
Assert.That(content, Does.Contain(audience));
}

[Test]
public async Task ShouldNotPersistTokenToDiskWhenDisabled()
{
// given
var audience = "test";
var fetchCounter = 0;
var path = Path.Combine(tempPath, TestContext.CurrentContext.Test.Name);
var accessTokenCache = new PersistedAccessTokenCache(path,
() => Task.FromResult(new AccessToken("token-" + fetchCounter++,
DateTimeOffset.UtcNow.AddDays(-1).ToUnixTimeMilliseconds())),
persistedCredentialsCacheEnabled: false);

// when
_ = await accessTokenCache.Get(audience);

// then
var persistedCacheDirectoryExists = Directory.Exists(path);
Assert.IsFalse(persistedCacheDirectoryExists);
}

[Test]
public async Task ShouldPersistMultipleTokenToDisk()
{
Expand Down
18 changes: 18 additions & 0 deletions Client/Api/Builder/ICamundaCloudClientBuilder.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using Microsoft.Extensions.Logging;
using System;

namespace Zeebe.Client.Api.Builder;

Expand Down Expand Up @@ -82,6 +83,23 @@ public interface ICamundaCloudClientBuilderFinalStep
/// <returns>the fluent ICamundaCloudClientBuilderFinalStep.</returns>
ICamundaCloudClientBuilderFinalStep UsePersistedStoragePath(string path);

/// <summary>
/// Disables credentials cache persitence to file system.
/// </summary>
/// <returns>the fluent ICamundaCloudClientBuilderFinalStep.</returns>
ICamundaCloudClientBuilderFinalStep DisableCredentialsCachePersistence();

/// <summary>
/// Use AccessToken due date tolerance to refresh token before due date.
/// To compensate network latency and prevent server side rejection when the
/// AccessToken due date is too close to UTC now, this option allows you to
/// add a tolerance period to refresh the token slightly before due date.
/// e.g. TimeSpan.FromSeconds(2) will refresh the token 2 seconds before due date.
/// </summary>
/// <param name="tolerance">The tolerance to apply to token due date</param>
/// <returns>The final step in building a CamundaCloudTokenProvider.</returns>
ICamundaCloudClientBuilderFinalStep UseAccessTokenDueDateTolerance(TimeSpan tolerance);

/// <summary>
/// The IZeebeClient, which is setup entirely to talk with the defined Camunda Cloud cluster.
/// </summary>
Expand Down
18 changes: 18 additions & 0 deletions Client/Api/Builder/ICamundaCloudTokenProviderBuilder.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using Microsoft.Extensions.Logging;
using System;
using Zeebe.Client.Impl.Builder;

namespace Zeebe.Client.Api.Builder;
Expand Down Expand Up @@ -66,6 +67,23 @@ public interface ICamundaCloudTokenProviderBuilderFinalStep
/// <returns>The final step in building a CamundaCloudTokenProvider.</returns>
ICamundaCloudTokenProviderBuilderFinalStep UsePath(string path);

/// <summary>
/// Disables credentials cache persitence to file system.
/// </summary>
/// <returns>The final step in building a CamundaCloudTokenProvider.</returns>
ICamundaCloudTokenProviderBuilderFinalStep DisableCredentialsCachePersistence();

/// <summary>
/// Use AccessToken due date tolerance to refresh token before due date.
/// To compensate network latency and prevent server side rejection when the
/// AccessToken due date is too close to UTC now, this option allows you to
/// add a tolerance period to refresh the token slightly before due date.
/// e.g. TimeSpan.FromSeconds(2) will refresh the token 2 seconds before due date.
/// </summary>
/// <param name="tolerance">The tolerance to apply to token due date</param>
/// <returns>The final step in building a CamundaCloudTokenProvider.</returns>
ICamundaCloudTokenProviderBuilderFinalStep UseAccessTokenDueDateTolerance(TimeSpan tolerance);

/// <summary>
/// Builds the CamundaCloudTokenProvider, which can be used by the ZeebeClient to
/// communicate with the Camunda Cloud.
Expand Down
12 changes: 12 additions & 0 deletions Client/Impl/Builder/CamundaCloudClientBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,18 @@ public ICamundaCloudClientBuilderFinalStep UsePersistedStoragePath(string path)
return this;
}

public ICamundaCloudClientBuilderFinalStep DisableCredentialsCachePersistence()
{
_ = camundaCloudTokenProviderBuilder.DisableCredentialsCachePersistence();
return this;
}

public ICamundaCloudClientBuilderFinalStep UseAccessTokenDueDateTolerance(TimeSpan tolerance)
{
_ = camundaCloudTokenProviderBuilder.UseAccessTokenDueDateTolerance(tolerance);
return this;
}

public IZeebeClient Build()
{
return ZeebeClient.Builder()
Expand Down
8 changes: 6 additions & 2 deletions Client/Impl/Builder/CamundaCloudTokenProvider.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
using System.Collections.Generic;
using System.IO;
using System.Net.Http;
using System.Security.Cryptography;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Extensions.Logging;
Expand Down Expand Up @@ -31,10 +32,13 @@ internal CamundaCloudTokenProvider(
string clientSecret,
string audience,
string path = null,
ILoggerFactory loggerFactory = null)
ILoggerFactory loggerFactory = null,
bool persistedCredentialsCacheEnabled = true,
TimeSpan accessTokenDueDateTolerance = default)
{
persistedAccessTokenCache = new PersistedAccessTokenCache(path ?? ZeebeRootPath, FetchAccessToken,
loggerFactory?.CreateLogger<PersistedAccessTokenCache>());
loggerFactory?.CreateLogger<PersistedAccessTokenCache>(),
persistedCredentialsCacheEnabled, accessTokenDueDateTolerance);
logger = loggerFactory?.CreateLogger<CamundaCloudTokenProvider>();
this.authServer = authServer;
this.clientId = clientId;
Expand Down
25 changes: 24 additions & 1 deletion Client/Impl/Builder/CamundaCloudTokenProviderBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ public class CamundaCloudTokenProviderBuilder :
private string clientSecret;
private ILoggerFactory loggerFactory;
private string path;
private bool persistedCredentialsCacheEnabled = true;
private TimeSpan accessTokenDueDateTolerance = TimeSpan.Zero;

/// <inheritdoc />
public ICamundaCloudTokenProviderBuilder UseLoggerFactory(ILoggerFactory loggerFactory)
Expand Down Expand Up @@ -50,6 +52,25 @@ public ICamundaCloudTokenProviderBuilderFinalStep UsePath(string path)
return this;
}

/// <inheritdoc />
public ICamundaCloudTokenProviderBuilderFinalStep DisableCredentialsCachePersistence()
{
this.persistedCredentialsCacheEnabled = false;
return this;
}

/// <inheritdoc />
public ICamundaCloudTokenProviderBuilderFinalStep UseAccessTokenDueDateTolerance(TimeSpan tolerance)
{
if (tolerance < TimeSpan.Zero)
{
throw new ArgumentException("AccessToken due date tolerance must be a positive time span", nameof(tolerance));
}

this.accessTokenDueDateTolerance = tolerance;
return this;
}

/// <inheritdoc />
public CamundaCloudTokenProvider Build()
{
Expand All @@ -59,7 +80,9 @@ public CamundaCloudTokenProvider Build()
clientSecret,
audience,
path,
loggerFactory);
loggerFactory,
persistedCredentialsCacheEnabled,
accessTokenDueDateTolerance);
}

/// <inheritdoc />
Expand Down
Loading