Skip to content

Commit 7f72628

Browse files
committed
fix: evaluate client creds token expiry period in seconds instead of ms
1 parent 163630f commit 7f72628

File tree

9 files changed

+124
-19
lines changed

9 files changed

+124
-19
lines changed

.github/PULL_REQUEST_TEMPLATE.md

Lines changed: 0 additions & 1 deletion
This file was deleted.

.github/SECURITY.md

Lines changed: 0 additions & 8 deletions
This file was deleted.

.openapi-generator/FILES

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,6 @@
33
.github/CODEOWNERS
44
.github/ISSUE_TEMPLATES/bug_report.md
55
.github/ISSUE_TEMPLATES/feature_request.md
6-
.github/PULL_REQUEST_TEMPLATE.md
7-
.github/SECURITY.md
86
.github/workflows/main.yaml
97
.github/workflows/semgrep.yaml
108
.gitignore

CHANGELOG.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,10 @@
11
# Changelog
22

3+
## v0.2.4
4+
5+
### [0.2.4](https://github.com/openfga/dotnet-sdk/compare/v0.2.3...v0.2.4) (2023-05-01)
6+
- fix: client credentials token expiry period was being evaluated as ms instead of seconds, leading to token refreshes on every call
7+
38
## v0.2.3
49

510
### [0.2.3](https://github.com/openfga/dotnet-sdk/compare/v0.2.2...v0.2.3) (2023-04-13)

src/OpenFga.Sdk.Test/Api/OpenFgaApiTests.cs

Lines changed: 116 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -303,7 +303,7 @@ public async Task ExchangeCredentialsTest() {
303303

304304
var mockHandler = new Mock<HttpMessageHandler>(MockBehavior.Strict);
305305
mockHandler.Protected()
306-
.Setup<Task<HttpResponseMessage>>(
306+
.SetupSequence<Task<HttpResponseMessage>>(
307307
"SendAsync",
308308
ItExpr.Is<HttpRequestMessage>(req =>
309309
req.RequestUri == new Uri($"https://{config.Credentials.Config.ApiTokenIssuer}/oauth/token") &&
@@ -314,7 +314,15 @@ public async Task ExchangeCredentialsTest() {
314314
StatusCode = HttpStatusCode.OK,
315315
Content = Utils.CreateJsonStringContent(new OAuth2Client.AccessTokenResponse() {
316316
AccessToken = "some-token",
317-
ExpiresIn = 20000,
317+
ExpiresIn = 86400,
318+
TokenType = "Bearer"
319+
}),
320+
})
321+
.ReturnsAsync(new HttpResponseMessage() {
322+
StatusCode = HttpStatusCode.OK,
323+
Content = Utils.CreateJsonStringContent(new OAuth2Client.AccessTokenResponse() {
324+
AccessToken = "some-token",
325+
ExpiresIn = 86400,
318326
TokenType = "Bearer"
319327
}),
320328
});
@@ -326,11 +334,16 @@ public async Task ExchangeCredentialsTest() {
326334
req.Headers.Contains("Authorization") &&
327335
req.Headers.Authorization.Equals(new AuthenticationHeaderValue("Bearer", "some-token")));
328336
mockHandler.Protected()
329-
.Setup<Task<HttpResponseMessage>>(
337+
.SetupSequence<Task<HttpResponseMessage>>(
330338
"SendAsync",
331339
readAuthorizationModelsMockExpression,
332340
ItExpr.IsAny<CancellationToken>()
333341
)
342+
.ReturnsAsync(new HttpResponseMessage() {
343+
StatusCode = HttpStatusCode.OK,
344+
Content = Utils.CreateJsonStringContent(
345+
new ReadAuthorizationModelsResponse() { AuthorizationModels = { } }),
346+
})
334347
.ReturnsAsync(new HttpResponseMessage() {
335348
StatusCode = HttpStatusCode.OK,
336349
Content = Utils.CreateJsonStringContent(
@@ -341,6 +354,7 @@ public async Task ExchangeCredentialsTest() {
341354
var openFgaApi = new OpenFgaApi(config, httpClient);
342355

343356
var response = await openFgaApi.ReadAuthorizationModels(null, null);
357+
var response2 = await openFgaApi.ReadAuthorizationModels(null, null);
344358

345359
mockHandler.Protected().Verify(
346360
"SendAsync",
@@ -352,7 +366,105 @@ public async Task ExchangeCredentialsTest() {
352366
);
353367
mockHandler.Protected().Verify(
354368
"SendAsync",
355-
Times.Exactly(1),
369+
Times.Exactly(2),
370+
readAuthorizationModelsMockExpression,
371+
ItExpr.IsAny<CancellationToken>()
372+
);
373+
mockHandler.Protected().Verify(
374+
"SendAsync",
375+
Times.Exactly(0),
376+
ItExpr.Is<HttpRequestMessage>(req =>
377+
req.RequestUri == new Uri($"{config.BasePath}/stores/{config.StoreId}/check") &&
378+
req.Method == HttpMethod.Post),
379+
ItExpr.IsAny<CancellationToken>()
380+
);
381+
}
382+
383+
/// <summary>
384+
/// Test that a network call is issued to get the token at the first request if client id is provided, and then again before the next call if expired
385+
/// </summary>
386+
[Fact]
387+
public async Task ExchangeCredentialsAfterExpiryTest() {
388+
var config = new Configuration.Configuration() {
389+
StoreId = _storeId,
390+
ApiHost = _host,
391+
Credentials = new Credentials() {
392+
Method = CredentialsMethod.ClientCredentials,
393+
Config = new CredentialsConfig() {
394+
ClientId = "some-id",
395+
ClientSecret = "some-secret",
396+
ApiTokenIssuer = "tokenissuer.fga.example",
397+
ApiAudience = "some-audience",
398+
}
399+
}
400+
};
401+
402+
var mockHandler = new Mock<HttpMessageHandler>(MockBehavior.Strict);
403+
mockHandler.Protected()
404+
.SetupSequence<Task<HttpResponseMessage>>(
405+
"SendAsync",
406+
ItExpr.Is<HttpRequestMessage>(req =>
407+
req.RequestUri == new Uri($"https://{config.Credentials.Config.ApiTokenIssuer}/oauth/token") &&
408+
req.Method == HttpMethod.Post),
409+
ItExpr.IsAny<CancellationToken>()
410+
)
411+
.ReturnsAsync(new HttpResponseMessage() {
412+
StatusCode = HttpStatusCode.OK,
413+
Content = Utils.CreateJsonStringContent(new OAuth2Client.AccessTokenResponse() {
414+
AccessToken = "some-token",
415+
ExpiresIn = 1,
416+
TokenType = "Bearer"
417+
}),
418+
})
419+
.ReturnsAsync(new HttpResponseMessage() {
420+
StatusCode = HttpStatusCode.OK,
421+
Content = Utils.CreateJsonStringContent(new OAuth2Client.AccessTokenResponse() {
422+
AccessToken = "some-token",
423+
ExpiresIn = 86400,
424+
TokenType = "Bearer"
425+
}),
426+
});
427+
428+
var readAuthorizationModelsMockExpression = ItExpr.Is<HttpRequestMessage>(req =>
429+
req.RequestUri.ToString()
430+
.StartsWith($"{config.BasePath}/stores/{config.StoreId}/authorization-models") &&
431+
req.Method == HttpMethod.Get &&
432+
req.Headers.Contains("Authorization") &&
433+
req.Headers.Authorization.Equals(new AuthenticationHeaderValue("Bearer", "some-token")));
434+
mockHandler.Protected()
435+
.SetupSequence<Task<HttpResponseMessage>>(
436+
"SendAsync",
437+
readAuthorizationModelsMockExpression,
438+
ItExpr.IsAny<CancellationToken>()
439+
)
440+
.ReturnsAsync(new HttpResponseMessage() {
441+
StatusCode = HttpStatusCode.OK,
442+
Content = Utils.CreateJsonStringContent(
443+
new ReadAuthorizationModelsResponse() { AuthorizationModels = { } }),
444+
})
445+
.ReturnsAsync(new HttpResponseMessage() {
446+
StatusCode = HttpStatusCode.OK,
447+
Content = Utils.CreateJsonStringContent(
448+
new ReadAuthorizationModelsResponse() { AuthorizationModels = { } }),
449+
});
450+
451+
var httpClient = new HttpClient(mockHandler.Object);
452+
var openFgaApi = new OpenFgaApi(config, httpClient);
453+
454+
var response = await openFgaApi.ReadAuthorizationModels(null, null);
455+
var response2 = await openFgaApi.ReadAuthorizationModels(null, null);
456+
457+
mockHandler.Protected().Verify(
458+
"SendAsync",
459+
Times.Exactly(2),
460+
ItExpr.Is<HttpRequestMessage>(req =>
461+
req.RequestUri == new Uri($"https://{config.Credentials.Config.ApiTokenIssuer}/oauth/token") &&
462+
req.Method == HttpMethod.Post),
463+
ItExpr.IsAny<CancellationToken>()
464+
);
465+
mockHandler.Protected().Verify(
466+
"SendAsync",
467+
Times.Exactly(2),
356468
readAuthorizationModelsMockExpression,
357469
ItExpr.IsAny<CancellationToken>()
358470
);

src/OpenFga.Sdk/ApiClient/OAuth2Client.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -131,7 +131,7 @@ private async Task ExchangeTokenAsync(CancellationToken cancellationToken = defa
131131

132132
_authToken = new AuthToken() {
133133
AccessToken = accessTokenResponse.AccessToken,
134-
ExpiresAt = DateTime.Now + TimeSpan.FromMilliseconds(accessTokenResponse.ExpiresIn)
134+
ExpiresAt = DateTime.Now + TimeSpan.FromSeconds(accessTokenResponse.ExpiresIn)
135135
};
136136
}
137137

src/OpenFga.Sdk/Client/Client.cs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,6 @@ public class OpenFgaClient : IDisposable {
2626
private string CLIENT_METHOD_HEADER = "X-OpenFGA-Client-Method";
2727

2828
private readonly int DEFAULT_MAX_METHOD_PARALLEL_REQS = 10;
29-
private readonly int DEFAULT_WAIT_TIME_BETWEEN_CHUNKS_IN_MS = 100;
3029

3130
public OpenFgaClient(
3231
ClientConfiguration configuration,

src/OpenFga.Sdk/Configuration/Configuration.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ public void IsValid() {
6060
/// Version of the package.
6161
/// </summary>
6262
/// <value>Version of the package.</value>
63-
public const string Version = "0.2.3";
63+
public const string Version = "0.2.4";
6464

6565
#endregion Constants
6666

src/OpenFga.Sdk/OpenFga.Sdk.csproj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
<Description>.NET SDK for OpenFGA</Description>
1313
<Copyright>OpenFGA</Copyright>
1414
<RootNamespace>OpenFga.Sdk</RootNamespace>
15-
<Version>0.2.3</Version>
15+
<Version>0.2.4</Version>
1616
<DocumentationFile>bin\$(Configuration)\$(TargetFramework)\OpenFga.Sdk.xml</DocumentationFile>
1717
<PackageLicenseExpression>Apache-2.0</PackageLicenseExpression>
1818
<PackageReadmeFile>README.md</PackageReadmeFile>

0 commit comments

Comments
 (0)