Skip to content

Commit

Permalink
epic.
Browse files Browse the repository at this point in the history
  • Loading branch information
dibiancoj committed Feb 25, 2024
1 parent 843cc62 commit 04a2302
Show file tree
Hide file tree
Showing 6 changed files with 144 additions and 11 deletions.
2 changes: 1 addition & 1 deletion Src/LibraryCore.ApiClient/LibraryCore.ApiClient.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
<Description>Simplified library to make api calls using http client</Description>
<RepositoryUrl>https://github.com/dibiancoj/LibraryCore</RepositoryUrl>
<RepositoryType>git</RepositoryType>
<Version>9.1.0</Version>
<Version>9.2.0</Version>
<Title>LibraryCore - Api Client</Title>
<IsTrimmable>true</IsTrimmable>
<IsAotCompatible Condition="$([MSBuild]::IsTargetFrameworkCompatible('$(TargetFramework)', 'net7.0'))">true</IsAotCompatible>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using LibraryCore.ApiClient;
using LibraryCore.Aot.Json;
using LibraryCore.ApiClient;
using LibraryCore.ApiClient.ExtensionMethods;
using LibraryCore.Shared;
using Microsoft.IdentityModel.Tokens;
Expand All @@ -8,6 +9,7 @@
using System.Security.Claims;
using System.Security.Cryptography;
using System.Text.Json.Serialization;
using System.Text.Json.Serialization.Metadata;

namespace LibraryCore.Healthcare.Epic.Authentication;

Expand All @@ -26,13 +28,23 @@ public static async Task<EpicClientCredentialsAuthorizationToken> TokenAsync(Htt
string tokenEndPointUrl,
string clientAssertion,
CancellationToken cancellationToken = default)
{
return await TokenAsync(httpClient, tokenEndPointUrl, clientAssertion, ResolveJsonType.ResolveJsonTypeInfo<EpicClientCredentialsAuthorizationToken>(), cancellationToken);
}

public static async Task<EpicClientCredentialsAuthorizationToken> TokenAsync(HttpClient httpClient,
string tokenEndPointUrl,
string clientAssertion,
JsonTypeInfo<EpicClientCredentialsAuthorizationToken> jsonTypeInfo,
CancellationToken cancellationToken = default)
{
var request = new FluentRequest(HttpMethod.Post, tokenEndPointUrl)
.AddFormsUrlEncodedBody(new("grant_type", "client_credentials"),
new("client_assertion_type", "urn:ietf:params:oauth:client-assertion-type:jwt-bearer"),
new("client_assertion", clientAssertion));
.AddFormsUrlEncodedBody(new("grant_type", "client_credentials"),
new("client_assertion_type", "urn:ietf:params:oauth:client-assertion-type:jwt-bearer"),
new("client_assertion", clientAssertion));

return await httpClient.SendRequestToJsonAsync(request, jsonTypeInfo, cancellationToken: cancellationToken) ?? throw new Exception("Can't Deserialize Token");

return await httpClient.SendRequestToJsonAsync<EpicClientCredentialsAuthorizationToken>(request, cancellationToken: cancellationToken) ?? throw new Exception("Can't Deserialize Token");
}

/// <summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
using LibraryCore.Shared;
using Microsoft.Extensions.Caching.Memory;
using System.Diagnostics.CodeAnalysis;
using System.Text.Json.Serialization.Metadata;

namespace LibraryCore.Healthcare.Fhir.MessageHandlers.AuthenticationHandler.TokenBearerProviders.Implementations;

Expand All @@ -14,8 +15,18 @@ public class EpicClientCredentialsBearerTokenProvider(IMemoryCache memoryCache,
HttpClient httpClient,
string tokenEndPointUrl,
string rawPrivateKeyContentInPemFile,
string clientId) : IFhirBearerTokenProvider
string clientId,
JsonTypeInfo<EpicClientCredentialsAuthorizationToken> jsonTypeInfo) : IFhirBearerTokenProvider
{
public EpicClientCredentialsBearerTokenProvider(IMemoryCache memoryCache,
HttpClient httpClient,
string tokenEndPointUrl,
string rawPrivateKeyContentInPemFile,
string clientId)
: this(memoryCache, httpClient, tokenEndPointUrl, rawPrivateKeyContentInPemFile, clientId, Aot.Json.ResolveJsonType.ResolveJsonTypeInfo<EpicClientCredentialsAuthorizationToken>())
{
}

private static TimeSpan CacheBufferTimePeriod { get; } = new TimeSpan(0, 1, 0);

public async ValueTask<string> AccessTokenAsync(CancellationToken cancellationToken = default)
Expand All @@ -24,7 +35,7 @@ public async ValueTask<string> AccessTokenAsync(CancellationToken cancellationTo
{
var clientAssertion = ClientCredentialsAuthentication.CreateEpicClientAssertionJwtToken(rawPrivateKeyContentInPemFile, clientId, tokenEndPointUrl);

var tokenResult = await ClientCredentialsAuthentication.TokenAsync(httpClient, tokenEndPointUrl, clientAssertion, cancellationToken);
var tokenResult = await ClientCredentialsAuthentication.TokenAsync(httpClient, tokenEndPointUrl, clientAssertion, jsonTypeInfo, cancellationToken);

//giving it a minute buffer so we don't get too close to the expiration
var buffer = tokenResult.ExpiresIn <= CacheBufferTimePeriod.TotalSeconds ?
Expand Down
10 changes: 7 additions & 3 deletions Src/LibraryCore.Healthcare/LibraryCore.Healthcare.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
<Description>.NetCore Common Utilities For Healthcare</Description>
<RepositoryUrl>https://github.com/dibiancoj/LibraryCore</RepositoryUrl>
<RepositoryType>git</RepositoryType>
<Version>9.0.2</Version>
<Version>9.1.0</Version>
<Title>LibraryCore - Healthcare</Title>
<IsAotCompatible Condition="$([MSBuild]::IsTargetFrameworkCompatible('$(TargetFramework)', 'net7.0'))">false</IsAotCompatible>
<IsTrimmable>true</IsTrimmable>
Expand All @@ -28,10 +28,14 @@

<ItemGroup>
<PackageReference Include="Hl7.Fhir.R4" Version="5.6.1" />
<PackageReference Include="LibraryCore.ApiClient" Version="9.0.1" />
<PackageReference Include="LibraryCore.Caching" Version="9.0.1" />
<PackageReference Include="LibraryCore.ApiClient" Version="9.1.0" />
<PackageReference Include="LibraryCore.Caching" Version="9.0.2" />
<PackageReference Include="LibraryCore.Core" Version="9.0.1" />
<PackageReference Include="System.Text.Json" Version="8.0.2" />
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\LibraryCore.Shared\LibraryCore.Aot.Json\LibraryCore.Aot.Json.csproj" />
</ItemGroup>

</Project>
Original file line number Diff line number Diff line change
@@ -1,11 +1,19 @@
using LibraryCore.Healthcare.Epic.Authentication;
using LibraryCore.Shared;
using Moq.Protected;
using System.IdentityModel.Tokens.Jwt;
using System.Text;
using System.Text.Json;
using System.Text.Json.Serialization;

namespace LibraryCore.Tests.Healthcare.Epic.Authentication;

[JsonSourceGenerationOptions(WriteIndented = true)]
[JsonSerializable(typeof(EpicClientCredentialsAuthorizationToken))]
internal partial class JsonContext : JsonSerializerContext
{
}

public class ClientCredentialsAuthenticationTest
{
internal const string PrivateKeyTester = """
Expand Down Expand Up @@ -78,6 +86,44 @@ public async Task ClientCredentialsTokenAsync()
Assert.Contains(result.Scopes, x => x == "def");
}

[Trait(ErrorMessages.AotUnitTestTraitName, ErrorMessages.AotUnitTestTraitValue)]
[Fact]
public async Task ClientCredentialsTokenAsync_Aot()
{
const string clientAssertion = "abc";
var mockHttpHandler = new Mock<HttpMessageHandler>();
var httpClient = new HttpClient(mockHttpHandler.Object);

var mockResponse = new HttpResponseMessage
{
StatusCode = System.Net.HttpStatusCode.OK,

Content = new StringContent(JsonSerializer.Serialize(new EpicClientCredentialsAuthorizationToken("Bearer", "Abcdef", "abc def", 90)), Encoding.UTF8, "application/json")
};

mockHttpHandler
.Protected()
.Setup<Task<HttpResponseMessage>>(
"SendAsync",
ItExpr.Is<HttpRequestMessage>(msg => CheckBodyExpression(msg)),
ItExpr.IsAny<CancellationToken>())
.ReturnsAsync(mockResponse);

var result = await ClientCredentialsAuthentication.TokenAsync(httpClient, "https://mytokenendpoint", clientAssertion, JsonContext.Default.EpicClientCredentialsAuthorizationToken);

mockHttpHandler
.Protected()
.Verify(
nameof(HttpClient.SendAsync),
Times.Once(),
ItExpr.Is<HttpRequestMessage>(msg => CheckBodyExpression(msg)),
ItExpr.IsAny<CancellationToken>());

Assert.Equal(2, result.Scopes.Count);
Assert.Contains(result.Scopes, x => x == "abc");
Assert.Contains(result.Scopes, x => x == "def");
}

private static bool CheckBodyExpression(HttpRequestMessage msg)
{
const string expectedBody = "grant_type=client_credentials&client_assertion_type=urn%3Aietf%3Aparams%3Aoauth%3Aclient-assertion-type%3Ajwt-bearer&client_assertion=abc";
Expand Down
Original file line number Diff line number Diff line change
@@ -1,14 +1,22 @@
using LibraryCore.Healthcare.Epic.Authentication;
using LibraryCore.Healthcare.Fhir.MessageHandlers.AuthenticationHandler.TokenBearerProviders.Implementations;
using LibraryCore.Shared;
using LibraryCore.Tests.Healthcare.Epic.Authentication;
using Microsoft.Extensions.Caching.Memory;
using Microsoft.Extensions.Options;
using Moq.Protected;
using System.Text;
using System.Text.Json;
using System.Text.Json.Serialization;

namespace LibraryCore.Tests.Healthcare.Fhir.HttpClientHandlers.TokenBearerProviders;

[JsonSourceGenerationOptions(WriteIndented = true)]
[JsonSerializable(typeof(EpicClientCredentialsAuthorizationToken))]
internal partial class JsonContextClientCredentailsTokenProvider : JsonSerializerContext
{
}

public class EpicClientCredentialsBearerTokenProviderTest
{
[Fact]
Expand Down Expand Up @@ -60,4 +68,56 @@ public async Task CachingEpicClientCredsTest()
ItExpr.Is<HttpRequestMessage>(msg => true),
ItExpr.IsAny<CancellationToken>());
}

[Trait(ErrorMessages.AotUnitTestTraitName, ErrorMessages.AotUnitTestTraitValue)]
[Fact]
public async Task CachingEpicClientCredsTest_Aot()
{
var mockHttpHandler = new Mock<HttpMessageHandler>();
var httpClient = new HttpClient(mockHttpHandler.Object);

var mockResponse1 = new HttpResponseMessage(System.Net.HttpStatusCode.OK)
{
Content = new StringContent(JsonSerializer.Serialize(new EpicClientCredentialsAuthorizationToken("Bearer", "Abcdef", "abc def", 5)), Encoding.UTF8, "application/json")
};

var mockResponse2 = new HttpResponseMessage(System.Net.HttpStatusCode.OK)
{
Content = new StringContent(JsonSerializer.Serialize(new EpicClientCredentialsAuthorizationToken("Bearer", "zzzzzz", "abc def", 5)), Encoding.UTF8, "application/json")
};

mockHttpHandler
.Protected()
.SetupSequence<Task<HttpResponseMessage>>(
"SendAsync",
ItExpr.Is<HttpRequestMessage>(msg => true),
ItExpr.IsAny<CancellationToken>())
.ReturnsAsync(mockResponse1)
.ReturnsAsync(mockResponse2);

var memoryCache = new MemoryCache(Options.Create(new MemoryCacheOptions()));

var tokenProvider = new EpicClientCredentialsBearerTokenProvider(memoryCache,
httpClient,
"http://www.fhir.token.com",
ClientCredentialsAuthenticationTest.PrivateKeyTester,
"ClientAbc",
JsonContextClientCredentailsTokenProvider.Default.EpicClientCredentialsAuthorizationToken);

Assert.Equal("Abcdef", await tokenProvider.AccessTokenAsync());
Assert.Equal("Abcdef", await tokenProvider.AccessTokenAsync());

await Task.Delay(TimeSpan.FromSeconds(6));

Assert.Equal("zzzzzz", await tokenProvider.AccessTokenAsync());
Assert.Equal("zzzzzz", await tokenProvider.AccessTokenAsync());

mockHttpHandler
.Protected()
.Verify<Task<HttpResponseMessage>>(
"SendAsync",
Times.Exactly(2),
ItExpr.Is<HttpRequestMessage>(msg => true),
ItExpr.IsAny<CancellationToken>());
}
}

0 comments on commit 04a2302

Please sign in to comment.