Skip to content

Commit fdc74b1

Browse files
committed
chore: gateway
1 parent 8c64ca0 commit fdc74b1

File tree

12 files changed

+255
-7
lines changed

12 files changed

+255
-7
lines changed

gateway/strdata-dev.yaml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
services:
22
- name: strdata
3-
host: strdss-dev-backend.b0471a-dev.svc
3+
host: dev.strdata.gov.bc.ca
44
tags: [ns.strdata]
5-
port: 8080
6-
protocol: http
5+
port: 443
6+
protocol: https
77
retries: 0
88
routes:
99
- name: strdata

helm/main/values-dev.yaml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@ global:
2020
'BCEID_CACHE_LIFESPAN': '600'
2121
'GEOCODER_URL': 'https://geocodertst.api.gov.bc.ca'
2222
'ADDRESS_SCORE': '90'
23+
'APS_AUTHORITY': 'https://dev.loginproxy.gov.bc.ca/auth/realms/apigw'
24+
'APS_GW_JWT_JWKS_URL': 'https://aps-jwks-upstream-jwt.api.gov.bc.ca/certs'
2325

2426
nameOverride: strdss-dev
2527
fullnameOverride: strdss-dev

helm/main/values-prod.yaml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@ global:
2020
'BCEID_CACHE_LIFESPAN': '600'
2121
'GEOCODER_URL': 'https://geocoder.api.gov.bc.ca'
2222
'ADDRESS_SCORE': '90'
23+
'APS_AUTHORITY': 'https://loginproxy.gov.bc.ca/auth/realms/apigw'
24+
'APS_GW_JWT_JWKS_URL': 'https://aps-jwks-upstream-jwt.api.gov.bc.ca/certs'
2325

2426
nameOverride: strdss-prod
2527
fullnameOverride: strdss-prod

helm/main/values-test.yaml

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,9 @@ global:
2020
'BCEID_CACHE_LIFESPAN': '600'
2121
'GEOCODER_URL': 'https://geocodertst.api.gov.bc.ca'
2222
'ADDRESS_SCORE': '90'
23-
23+
'APS_AUTHORITY': 'https://test.loginproxy.gov.bc.ca/auth/realms/apigw'
24+
'APS_GW_JWT_JWKS_URL': 'https://aps-jwks-upstream-jwt.api.gov.bc.ca/certs'
25+
2426
nameOverride: strdss-test
2527
fullnameOverride: strdss-test
2628

helm/main/values-uat.yaml

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,9 @@ global:
2020
'BCEID_CACHE_LIFESPAN': '600'
2121
'GEOCODER_URL': 'https://geocodertst.api.gov.bc.ca'
2222
'ADDRESS_SCORE': '90'
23-
23+
'APS_AUTHORITY': 'https://test.loginproxy.gov.bc.ca/auth/realms/apigw'
24+
'APS_GW_JWT_JWKS_URL': 'https://aps-jwks-upstream-jwt.api.gov.bc.ca/certs'
25+
2426
nameOverride: strdss-uat
2527
fullnameOverride: strdss-uat
2628

Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
using Microsoft.AspNetCore.Authentication.JwtBearer;
2+
using Microsoft.IdentityModel.Tokens;
3+
using StrDss.Common;
4+
using StrDss.Model;
5+
using StrDss.Service;
6+
using System.IdentityModel.Tokens.Jwt;
7+
using System.Security.Cryptography;
8+
9+
namespace StrDss.Api.Authentication
10+
{
11+
public class ApsJwtBearerEvents : JwtBearerEvents
12+
{
13+
private ICurrentUser _currentUser;
14+
private IUserService _userService;
15+
private IRoleService _roleService;
16+
private readonly ILogger<StrDssLogger> _logger;
17+
private readonly IMemoryCacheService _memoryCache;
18+
19+
public ApsJwtBearerEvents(ICurrentUser currentUser, IUserService userService, IRoleService roleService, ILogger<StrDssLogger> logger, IMemoryCacheService memoryCache) : base()
20+
{
21+
_currentUser = currentUser;
22+
_userService = userService;
23+
_roleService = roleService;
24+
_logger = logger;
25+
_memoryCache = memoryCache;
26+
}
27+
28+
public override async Task TokenValidated(TokenValidatedContext context)
29+
{
30+
_currentUser.LoadApsSession(context!.Principal!);
31+
32+
var (user, permissions) = await _userService.GetUserByGuidAsync(_currentUser.UserGuid);
33+
34+
if (user == null)
35+
{
36+
var errorMessage = $"{_currentUser.DisplayName} is not registered.";
37+
context.Response.StatusCode = 401;
38+
context.Fail($"Unauthorized: {errorMessage}");
39+
return;
40+
}
41+
42+
_currentUser.Id = user.UserIdentityId;
43+
_currentUser.IsActive = user.IsEnabled;
44+
_currentUser.AccessRequestStatus = user.AccessRequestStatusCd;
45+
_currentUser.AccessRequestRequired = _currentUser.AccessRequestStatus == AccessRequestStatuses.Denied;
46+
_currentUser.OrganizationType = user.RepresentedByOrganization?.OrganizationType ?? "";
47+
_currentUser.OrganizationId = user.RepresentedByOrganizationId ?? 0;
48+
_currentUser.OrganizationName = user.RepresentedByOrganization?.OrganizationNm ?? "";
49+
_currentUser.TermsAcceptanceDtm = user.TermsAcceptanceDtm;
50+
51+
if (user.IsEnabled)
52+
{
53+
_currentUser.Permissions = permissions;
54+
55+
foreach (var permission in permissions)
56+
{
57+
_currentUser.AddClaim(context!.Principal!, StrDssClaimTypes.Permission, permission);
58+
}
59+
}
60+
61+
if (!context.Request.Headers.TryGetValue("GW-JWT", out var jwtToken))
62+
{
63+
_logger.LogWarning($"{_currentUser.UserName}: GW-JWT is missing");
64+
context.Response.StatusCode = 401;
65+
context.Fail("Unauthorized");
66+
return;
67+
}
68+
69+
var publicKey = await _memoryCache.GetPublicKeyAsync(false);
70+
var gwJwt = jwtToken.ToString();
71+
72+
if (gwJwt.IsEmpty())
73+
{
74+
_logger.LogWarning($"{_currentUser.UserName}: GW-JWT is empty");
75+
context.Response.StatusCode = 401;
76+
context.Fail("Unauthorized");
77+
return;
78+
}
79+
80+
if (!ValidateJwtToken(gwJwt, publicKey))
81+
{
82+
publicKey = await _memoryCache.GetPublicKeyAsync(true);
83+
84+
if (!ValidateJwtToken(gwJwt, publicKey))
85+
{
86+
_logger.LogWarning($"{_currentUser.UserName}: GW-JWT is invalid");
87+
context.Response.StatusCode = 401;
88+
context.Fail("Unauthorized");
89+
return;
90+
}
91+
}
92+
}
93+
94+
private static bool ValidateJwtToken(string jwtToken, JsonWebKey publicKey)
95+
{
96+
var validationParameters = new TokenValidationParameters
97+
{
98+
ValidateIssuer = false,
99+
ValidateAudience = false,
100+
IssuerSigningKey = new RsaSecurityKey(new RSAParameters
101+
{
102+
Modulus = Base64UrlEncoder.DecodeBytes(publicKey.N),
103+
Exponent = Base64UrlEncoder.DecodeBytes(publicKey.E)
104+
}),
105+
ValidateLifetime = true,
106+
};
107+
108+
var handler = new JwtSecurityTokenHandler();
109+
try
110+
{
111+
SecurityToken validatedToken;
112+
handler.ValidateToken(jwtToken, validationParameters, out validatedToken);
113+
return true;
114+
}
115+
catch (SecurityTokenException)
116+
{
117+
return false;
118+
}
119+
}
120+
}
121+
}

server/StrDss.Api/Program.cs

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,8 @@
9696
builder.Services.AddHttpClients(builder.Configuration);
9797
builder.Services.AddBceidSoapClient(builder.Configuration);
9898

99+
builder.Services.AddMemoryCache();
100+
99101
var mappingConfig = new MapperConfiguration(cfg =>
100102
{
101103
cfg.AddProfile(new EntityToModelProfile());
@@ -108,8 +110,9 @@
108110
builder.Services.AddSingleton(mapper);
109111

110112
builder.Services.AddScoped<KcJwtBearerEvents>();
113+
builder.Services.AddScoped<ApsJwtBearerEvents>();
111114

112-
//var strDssAuthScheme = "str_dss";
115+
var apsAuthScheme = "aps";
113116

114117
//Authentication
115118
builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
@@ -127,12 +130,26 @@
127130
ValidAlgorithms = new List<string>() { "RS256" },
128131
};
129132
})
133+
.AddJwtBearer(apsAuthScheme, options =>
134+
{
135+
options.Authority = builder.Configuration.GetValue<string>("APS_AUTHORITY");
136+
options.IncludeErrorDetails = true;
137+
options.EventsType = typeof(ApsJwtBearerEvents);
138+
options.RequireHttpsMetadata = !builder.Environment.IsDevelopment();
139+
options.TokenValidationParameters = new TokenValidationParameters
140+
{
141+
ValidateIssuerSigningKey = true,
142+
ValidateIssuer = false,
143+
ValidateAudience = false,
144+
ValidAlgorithms = new List<string>() { "RS256" },
145+
};
146+
})
130147
;
131148

132149
builder.Services.AddAuthorization(options =>
133150
{
134151
var defaultAuthorizationPolicyBuilder = new AuthorizationPolicyBuilder(
135-
JwtBearerDefaults.AuthenticationScheme);
152+
JwtBearerDefaults.AuthenticationScheme, apsAuthScheme);
136153
defaultAuthorizationPolicyBuilder =
137154
defaultAuthorizationPolicyBuilder.RequireAuthenticatedUser();
138155
options.DefaultPolicy = defaultAuthorizationPolicyBuilder.Build();

server/StrDss.Data/Repositories/UserRepository.cs

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ public interface IUserRepository
1313
Task<PagedDto<UserListtDto>> GetUserListAsync(string status, string search, long? orgranizationId, int pageSize, int pageNumber, string orderBy, string direction);
1414
Task CreateUserAsync(UserCreateDto dto);
1515
Task<(UserDto? user, List<string> permissions)> GetUserAndPermissionsByGuidAsync(Guid guid);
16+
Task<(UserDto? user, List<string> permissions)> GetUserAndPermissionsByDisplayNameAsync(string displayName);
1617
Task<UserDto?> GetUserById(long id);
1718
Task<UserDto?> GetUserByGuid(Guid guid);
1819
Task UpdateUserAsync(UserDto dto);
@@ -93,6 +94,30 @@ public async Task CreateUserAsync(UserCreateDto dto)
9394
return (user, permssions);
9495
}
9596

97+
public async Task<(UserDto? user, List<string> permissions)> GetUserAndPermissionsByDisplayNameAsync(string displayName)
98+
{
99+
var query = await _dbSet.AsNoTracking()
100+
.Include(x => x.DssUserRoleAssignments)
101+
.Include(x => x.RepresentedByOrganization)
102+
.FirstOrDefaultAsync(x => x.DisplayNm == displayName);
103+
104+
if (query == null)
105+
return (null, new List<string>());
106+
107+
var user = _mapper.Map<UserDto>(query);
108+
109+
var roles = query.DssUserRoleAssignments.Select(x => x.UserRoleCd).ToList();
110+
111+
var permssions = _dbContext.DssUserRoles
112+
.Where(x => roles.Contains(x.UserRoleCd))
113+
.SelectMany(x => x.DssUserRolePrivileges)
114+
.ToLookup(x => x.UserPrivilegeCd)
115+
.Select(x => x.First())
116+
.Select(x => x.UserPrivilegeCd)
117+
.ToList();
118+
119+
return (user, permssions);
120+
}
96121
public async Task<UserDto?> GetUserById(long id)
97122
{
98123
var entity = await _dbSet.AsNoTracking()
@@ -215,6 +240,8 @@ public async Task CreateApsUserAsync(ApsUserCreateDto dto)
215240
dto.FamilyNm = dto.DisplayNm;
216241

217242
var userEntity = _mapper.Map<DssUserIdentity>(dto);
243+
244+
userEntity.UserGuid = Guid.NewGuid();
218245

219246
var roleCds = dto.RoleCds.Distinct();
220247

server/StrDss.Hangfire/Program.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,8 @@
106106
var mapper = mappingConfig.CreateMapper();
107107
builder.Services.AddSingleton(mapper);
108108

109+
builder.Services.AddMemoryCache();
110+
109111
builder.Services
110112
.AddHangfire(configuration => configuration
111113
.SetDataCompatibilityLevel(CompatibilityLevel.Version_170)

server/StrDss.Model/CurrentUser.cs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ public interface ICurrentUser
2626
public string OrganizationName { get; set; }
2727
public DateTime? TermsAcceptanceDtm { get; set; }
2828
void LoadUserSession(ClaimsPrincipal user);
29+
void LoadApsSession(ClaimsPrincipal user);
2930
void AddClaim(ClaimsPrincipal user, string claimType, string value);
3031
}
3132

@@ -87,6 +88,17 @@ public void LoadUserSession(ClaimsPrincipal user)
8788
FullName = CommonUtils.GetFullName(FirstName, LastName);
8889
}
8990

91+
public void LoadApsSession(ClaimsPrincipal user)
92+
{
93+
if (user == null)
94+
return;
95+
96+
var textInfo = new CultureInfo("en-US", false).TextInfo;
97+
98+
IdentityProviderNm = StrDssIdProviders.Aps;
99+
DisplayName = user.GetCustomClaim(StrDssClaimTypes.ClientId);
100+
}
101+
90102
public void AddClaim(ClaimsPrincipal user, string claimType, string value)
91103
{
92104
if (user == null || claimType.IsEmpty() || value.IsEmpty() || user.HasClaim(claimType, value)) return;

0 commit comments

Comments
 (0)