Skip to content

Commit 572edfb

Browse files
feat: E2E Authentication (#1877)
* Use JWT Auth against API Frontend * Fix unit test * back end auth and adding workgroups to bearer token * adding auth extension * Authentication * Update auth * Update Config stuff * sonar qube issues * remove jwt override and update tsconfig * re add missing session object * Adding Access Token Headers * adding role objects * fix web tests * Add user info tfvar to env varibles * fix headers * Role Manager * Sonar Cloud issues * data attributes and test fix * add attribute to all functions * permissions bypass * register permissions middleware * Sonar qube * update docker compose * update tf config * add unit tests * Addressing Copilot Comments
1 parent b8253d8 commit 572edfb

39 files changed

Lines changed: 1167 additions & 27 deletions

Directory.Packages.props

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,9 +27,11 @@
2727
<PackageVersion Include="Microsoft.Extensions.Azure" Version="1.12.0" />
2828
<PackageVersion Include="Microsoft.Extensions.Options.DataAnnotations" Version="9.0.6" />
2929
<PackageVersion Include="Microsoft.Identity.Client" Version="4.74.0" />
30-
<PackageVersion Include="Microsoft.IdentityModel.Tokens" Version="8.12.1" />
30+
<PackageVersion Include="Microsoft.IdentityModel.Protocols.OpenIdConnect" Version="8.17.0" />
31+
<PackageVersion Include="Microsoft.IdentityModel.Tokens" Version="8.17.0" />
3132
<PackageVersion Include="Hl7.Fhir.R4" Version="5.11.4" />
32-
<PackageVersion Include="System.IdentityModel.Tokens.Jwt" Version="8.12.1" />
33+
<PackageVersion Include="System.Configuration.ConfigurationManager" Version="10.0.5" />
34+
<PackageVersion Include="System.IdentityModel.Tokens.Jwt" Version="8.17.0" />
3335
<PackageVersion Include="Microsoft.Extensions.Hosting" Version="9.0.6" />
3436
<PackageVersion Include="System.Linq.Dynamic.Core" Version="1.7.1" />
3537
<PackageVersion Include="ParquetSharp" Version="17.0.0-beta1" />

application/CohortManager/compose.core.yaml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -247,6 +247,9 @@ services:
247247
- ExceptionManagementDataServiceURL=http://exception-management-data-service:7911/api/ExceptionManagementDataService/
248248
- GPPracticeDataServiceURL=http://gppractice-data-service:7999/api/GPPracticeDataService/
249249
- AcceptableLatencyThresholdMs=500
250+
- BypassAuthentication=true
251+
- AuthClientId=MockClientId-123
252+
- UserInfoUrl=http://wiremock:8080/userinfo
250253

251254
# Screening Validation Service
252255
static-validation:

application/CohortManager/src/Functions/DemographicServices/DemographicDurableFunction/DemographicDurableFunction.csproj

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,6 @@
1616
<PackageReference Include="Microsoft.Azure.Functions.Worker.Extensions.Http.AspNetCore" />
1717
<PackageReference Include="Contrib.Grpc.Core.M1" />
1818
<PackageReference Include="Microsoft.Extensions.Diagnostics.HealthChecks" />
19-
<PackageReference Include="Contrib.Grpc.Core.M1" />
2019
<PackageReference Include="Microsoft.Azure.Functions.Worker.Sdk" />
2120
</ItemGroup>
2221
<ItemGroup>

application/CohortManager/src/Functions/Functions.sln

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -155,6 +155,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MappingUtilitiesTests", "..
155155
EndProject
156156
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "HttpClientFunctionTests", "..\..\..\..\tests\UnitTests\SharedTests\HttpClientFunctionTests\HttpClientFunctionTests.csproj", "{0D98B37C-3F9D-46D0-B1D6-EBF2BE14A5B3}"
157157
EndProject
158+
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AuthenticationTests", "..\..\..\..\tests\UnitTests\SharedTests\AuthenticationTests\AuthenticationTests.csproj", "{9B8A2D7E-2DB2-4B97-A90C-68EA513A6C55}"
159+
EndProject
158160
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "HttpParserTests", "..\..\..\..\tests\UnitTests\SharedTests\HttpParserTests\HttpParserTests.csproj", "{E85D4E82-FBE0-42F1-8513-D5B2F515ADCD}"
159161
EndProject
160162
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FileNameParserTests", "..\..\..\..\tests\UnitTests\SharedTests\FileNameParserTests\FileNameParserTests.csproj", "{5B21A7B3-0B5C-4036-BB86-6C4629EE2983}"
@@ -1005,6 +1007,18 @@ Global
10051007
{0D98B37C-3F9D-46D0-B1D6-EBF2BE14A5B3}.Release|x64.Build.0 = Release|Any CPU
10061008
{0D98B37C-3F9D-46D0-B1D6-EBF2BE14A5B3}.Release|x86.ActiveCfg = Release|Any CPU
10071009
{0D98B37C-3F9D-46D0-B1D6-EBF2BE14A5B3}.Release|x86.Build.0 = Release|Any CPU
1010+
{9B8A2D7E-2DB2-4B97-A90C-68EA513A6C55}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
1011+
{9B8A2D7E-2DB2-4B97-A90C-68EA513A6C55}.Debug|Any CPU.Build.0 = Debug|Any CPU
1012+
{9B8A2D7E-2DB2-4B97-A90C-68EA513A6C55}.Debug|x64.ActiveCfg = Debug|Any CPU
1013+
{9B8A2D7E-2DB2-4B97-A90C-68EA513A6C55}.Debug|x64.Build.0 = Debug|Any CPU
1014+
{9B8A2D7E-2DB2-4B97-A90C-68EA513A6C55}.Debug|x86.ActiveCfg = Debug|Any CPU
1015+
{9B8A2D7E-2DB2-4B97-A90C-68EA513A6C55}.Debug|x86.Build.0 = Debug|Any CPU
1016+
{9B8A2D7E-2DB2-4B97-A90C-68EA513A6C55}.Release|Any CPU.ActiveCfg = Release|Any CPU
1017+
{9B8A2D7E-2DB2-4B97-A90C-68EA513A6C55}.Release|Any CPU.Build.0 = Release|Any CPU
1018+
{9B8A2D7E-2DB2-4B97-A90C-68EA513A6C55}.Release|x64.ActiveCfg = Release|Any CPU
1019+
{9B8A2D7E-2DB2-4B97-A90C-68EA513A6C55}.Release|x64.Build.0 = Release|Any CPU
1020+
{9B8A2D7E-2DB2-4B97-A90C-68EA513A6C55}.Release|x86.ActiveCfg = Release|Any CPU
1021+
{9B8A2D7E-2DB2-4B97-A90C-68EA513A6C55}.Release|x86.Build.0 = Release|Any CPU
10081022
{E85D4E82-FBE0-42F1-8513-D5B2F515ADCD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
10091023
{E85D4E82-FBE0-42F1-8513-D5B2F515ADCD}.Debug|Any CPU.Build.0 = Debug|Any CPU
10101024
{E85D4E82-FBE0-42F1-8513-D5B2F515ADCD}.Debug|x64.ActiveCfg = Debug|Any CPU
@@ -1587,6 +1601,7 @@ Global
15871601
{5F23FBB7-8794-4F38-8F93-244643F8523B} = {A1000001-0001-0001-0001-000000000005}
15881602
{4AB81A2E-7D29-4401-B865-FCA1C133544F} = {A1000001-0001-0001-0001-000000000005}
15891603
{0D98B37C-3F9D-46D0-B1D6-EBF2BE14A5B3} = {A1000001-0001-0001-0001-000000000005}
1604+
{9B8A2D7E-2DB2-4B97-A90C-68EA513A6C55} = {A1000001-0001-0001-0001-000000000005}
15901605
{E85D4E82-FBE0-42F1-8513-D5B2F515ADCD} = {A1000001-0001-0001-0001-000000000005}
15911606
{5B21A7B3-0B5C-4036-BB86-6C4629EE2983} = {A1000001-0001-0001-0001-000000000005}
15921607
{2423447D-FAF7-4356-BBE1-8B0A9F5DD86E} = {A1000001-0001-0001-0001-000000000005}
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
namespace Common;
2+
3+
using System.Text.Json;
4+
using Microsoft.Azure.Functions.Worker;
5+
6+
public static class AuthHelper
7+
{
8+
public static bool TryGetIdTokenFromHeaders(FunctionContext context, out string token)
9+
{
10+
return TryGetBearerTokenFromHeaders(context, "Authorization", out token);
11+
}
12+
13+
public static bool TryGetAccessTokenFromHeaders(FunctionContext context, out string accessToken)
14+
{
15+
return TryGetBearerTokenFromHeaders(context, "X-Access-Token", out accessToken);
16+
}
17+
18+
private static bool TryGetBearerTokenFromHeaders(FunctionContext context, string headerName, out string token)
19+
{
20+
token = null!;
21+
22+
context.BindingContext.BindingData.TryGetValue("Headers", out var headersObj);
23+
24+
if(headersObj is not string headersStr)
25+
{
26+
return false;
27+
}
28+
29+
var headers = JsonSerializer.Deserialize<Dictionary<string, string>>(headersStr);
30+
if(headers == null)
31+
{
32+
return false;
33+
}
34+
35+
if(!headers.TryGetValue(headerName, out var authHeader) || !authHeader.StartsWith("Bearer "))
36+
{
37+
return false;
38+
}
39+
40+
token = authHeader.Substring("Bearer ".Length).Trim();
41+
return true;
42+
}
43+
}
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
namespace Common;
2+
3+
[AttributeUsage(AttributeTargets.Method)]
4+
public class AuthenticationAttribute : Attribute
5+
{
6+
public Role[]? Roles { get; }
7+
public bool RequiresAuthentication => Roles != null && Roles.Length > 0;
8+
public AuthenticationAttribute(Role role)
9+
{
10+
Roles = new[] { role };
11+
}
12+
public AuthenticationAttribute(Role[] roles)
13+
{
14+
Roles = roles;
15+
}
16+
public AuthenticationAttribute()
17+
{
18+
Roles = null;
19+
}
20+
}
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
namespace Common;
2+
3+
using System.Runtime.CompilerServices;
4+
using System.Text.Json;
5+
using Hl7.FhirPath.Sprache;
6+
using Microsoft.Extensions.Logging;
7+
using Microsoft.Extensions.Options;
8+
9+
public class Cis2UserService : ICis2UserService
10+
{
11+
private readonly ILogger<Cis2UserService> _logger;
12+
private readonly IHttpClientFunction _httpClient;
13+
private readonly AuthConfig _authConfig;
14+
15+
public Cis2UserService(ILogger<Cis2UserService> logger, IHttpClientFunction httpClient, IOptions<AuthConfig> authConfig)
16+
{
17+
_logger = logger;
18+
_httpClient = httpClient;
19+
_authConfig = authConfig.Value;
20+
}
21+
22+
public async Task<Cis2User?> GetUserFromToken(string token)
23+
{
24+
try{
25+
_httpClient.SetBearerToken(token);
26+
var response = await _httpClient.SendGetOrThrowAsync(_authConfig.UserInfoUrl);
27+
if(response == null)
28+
{
29+
_logger.LogError("Failed to get user info from token, response is null");
30+
return null;
31+
}
32+
var cis2User = JsonSerializer.Deserialize<Cis2User>(response);
33+
34+
return cis2User;
35+
}
36+
catch(Exception ex)
37+
{
38+
_logger.LogError(ex, "Failed to get user info from token, message: {Message}", ex.Message);
39+
return null;
40+
}
41+
}
42+
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
namespace Common;
2+
3+
using System.ComponentModel.DataAnnotations;
4+
5+
public class AuthConfig
6+
{
7+
[Required, Url]
8+
public required string AuthMetaDataUrl { get; init; }
9+
[Required]
10+
public required string AuthClientId { get; init; }
11+
[Required, Url]
12+
public required string UserInfoUrl { get; init; }
13+
public bool ByPassAuthentication { get; init; } = false;
14+
15+
}
16+
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
namespace Common;
2+
3+
using System.ComponentModel.DataAnnotations;
4+
5+
public class RoleConfig
6+
{
7+
[Required]
8+
public required string CohortManagerUserWorkgroupId { get; init; }
9+
[Required]
10+
public required string CohortManagerDummyGpRemovalWorkgroupId { get; init; }
11+
}
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
namespace Common;
2+
3+
using Microsoft.Azure.Functions.Worker.Http;
4+
5+
public interface IAuthenticationService
6+
{
7+
Task<bool> ValidateTokenAsync(string token);
8+
}

0 commit comments

Comments
 (0)