Skip to content

Commit

Permalink
Add prefill capability to localtest (#8563)
Browse files Browse the repository at this point in the history
* Add prefill capability to localtest

* use System.text.json

* Fix LGTM warnings

* Improve debug information

* Reorder codelines to keep definition closer to usage

* Fix typo.

* Increase Loadbalancer timeout for localtest

* Get orgnumber for OrgToken from cdn

Also lots of cleanup

* Ensure that prefill upload is only availibel for LocalAppMode == http

* Update src/development/LocalTest/Services/LocalApp/Implementation/LocalAppHttp.cs

* Update src/development/LocalTest/Services/Authentication/Implementation/AuthenticationService.cs

Co-authored-by: Ivar <[email protected]>
  • Loading branch information
ivarne and ivarne authored Dec 7, 2022
1 parent ae3d753 commit 7a6a316
Show file tree
Hide file tree
Showing 14 changed files with 292 additions and 84 deletions.
6 changes: 6 additions & 0 deletions loadbalancer/nginx.conf
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,12 @@ http {

client_max_body_size 50M;

# Set timeout to 1 hour (helps when debugging)
proxy_connect_timeout 3600;
proxy_send_timeout 3600;
proxy_read_timeout 3600;
send_timeout 3600;

sendfile on;

upstream localtest {
Expand Down
26 changes: 26 additions & 0 deletions src/Clients/CdnAltinnOrgs/AltinnOrgsClient.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
#nullable enable
using System.Net.Http;
using System.Text.Json;
using System.Threading.Tasks;

namespace LocalTest.Clients.CdnAltinnOrgs;

/// <summary>
/// Access data from https://altinncdn.no/orgs/altinn-orgs.json
/// </summary>
public class AltinnOrgsClient
{
private static JsonSerializerOptions JSON_OPTIONS = new JsonSerializerOptions(JsonSerializerDefaults.Web);
private readonly HttpClient _client;

public AltinnOrgsClient(HttpClient client)
{
_client = client;
}

public async Task<CdnOrgs> GetCdnOrgs()
{
var orgsJson = await _client.GetByteArrayAsync("https://altinncdn.no/orgs/altinn-orgs.json");
return JsonSerializer.Deserialize<CdnOrgs>(orgsJson, JSON_OPTIONS) ?? throw new JsonException("altinn-orgs respones was \"null\"");
}
}
42 changes: 42 additions & 0 deletions src/Clients/CdnAltinnOrgs/Models.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
#nullable enable
using System.Collections.Generic;
using System.Text.Json.Serialization;

namespace LocalTest.Clients.CdnAltinnOrgs;

public class CdnOrgs
{
[JsonPropertyName("orgs")]
public Dictionary<string,CdnOrg>? Orgs { get; set; }
}

public class CdnOrg
{
[JsonPropertyName("name")]
public CdnOrgName? Name { get; set; }

[JsonPropertyName("logo")]
public string? Logo { get; set; }

[JsonPropertyName("orgnr")]
public string? Orgnr { get; set; }

[JsonPropertyName("homepage")]
public string? Homepage { get; set; }

[JsonPropertyName("environments")]
public List<string>? Environments { get; set; }

}

public class CdnOrgName
{
[JsonPropertyName("nb")]
public string? Nb { get; set; }

[JsonPropertyName("nn")]
public string? Nn { get; set; }

[JsonPropertyName("en")]
public string? En { get; set; }
}
4 changes: 2 additions & 2 deletions src/Configuration/Authentication/GeneralSettings.cs
Original file line number Diff line number Diff line change
Expand Up @@ -105,11 +105,11 @@ public string GetClaimsIdentity
/// <summary>
/// Gets the jwt cookie validity time from kubernetes environment variables and appsettings if environment variable is not set
/// </summary>
public string GetJwtCookieValidityTime
public int GetJwtCookieValidityTime
{
get
{
return Environment.GetEnvironmentVariable("GeneralSettings__GetJwtCookieValidityTime") ?? JwtCookieValidityTime;
return Convert.ToInt32(Environment.GetEnvironmentVariable("GeneralSettings__GetJwtCookieValidityTime") ?? JwtCookieValidityTime);
}
}

Expand Down
10 changes: 5 additions & 5 deletions src/Controllers/Authentication/AuthenticationController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ public async Task<ActionResult> RefreshJWTCookie()
ClaimsPrincipal principal = HttpContext.User;
_logger.LogInformation("Refreshing token....");

string token = _authenticationService.GenerateToken(principal, Convert.ToInt32(_generalSettings.JwtCookieValidityTime));
string token = _authenticationService.GenerateToken(principal);
_logger.LogInformation("End of refreshing token");
return await Task.FromResult(Ok(token));
}
Expand All @@ -69,8 +69,8 @@ public async Task<ActionResult> GenerateOrgToken(
identity.AddClaims(claims);
ClaimsPrincipal principal = new ClaimsPrincipal(identity);

string token = _authenticationService.GenerateToken(principal, Convert.ToInt32(_generalSettings.JwtCookieValidityTime));
string token = _authenticationService.GenerateToken(principal);

return await Task.FromResult(Ok(token));
}

Expand All @@ -91,8 +91,8 @@ public async Task<ActionResult> GenerateAppToken(
identity.AddClaims(claims);
ClaimsPrincipal principal = new ClaimsPrincipal(identity);

string token = _authenticationService.GenerateToken(principal, Convert.ToInt32(_generalSettings.JwtCookieValidityTime));
string token = _authenticationService.GenerateToken(principal);

return await Task.FromResult(Ok(token));
}
}
Expand Down
87 changes: 44 additions & 43 deletions src/Controllers/HomeController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@ public async Task<IActionResult> Index()
model.StaticTestDataPath = _localPlatformSettings.LocalTestingStaticTestDataPath;
model.LocalAppUrl = _localPlatformSettings.LocalAppUrl;
var defaultAuthLevel = _localPlatformSettings.LocalAppMode == "http" ? await GetAppAuthLevel(model.TestApps) : 2;
model.AppModeIsHttp = _localPlatformSettings.LocalAppMode == "http";
model.AuthenticationLevels = GetAuthenticationLevels(defaultAuthLevel);
model.LocalFrontendUrl = HttpContext.Request.Cookies[FRONTEND_URL_COOKIE_NAME];

Expand Down Expand Up @@ -111,21 +112,9 @@ public async Task<ActionResult> LogInTestUser(StartAppModel startAppModel)
if (startAppModel.AuthenticationLevel != "-1")
{
UserProfile profile = await _userProfileService.GetUser(startAppModel.UserId);
int authenticationLevel = Convert.ToInt32(startAppModel.AuthenticationLevel);

List<Claim> claims = new List<Claim>();
string issuer = _generalSettings.Hostname;
claims.Add(new Claim(ClaimTypes.NameIdentifier, profile.UserId.ToString(), ClaimValueTypes.String, issuer));
claims.Add(new Claim(AltinnCoreClaimTypes.UserId, profile.UserId.ToString(), ClaimValueTypes.String, issuer));
claims.Add(new Claim(AltinnCoreClaimTypes.UserName, profile.UserName, ClaimValueTypes.String, issuer));
claims.Add(new Claim(AltinnCoreClaimTypes.PartyID, profile.PartyId.ToString(), ClaimValueTypes.Integer32, issuer));
claims.Add(new Claim(AltinnCoreClaimTypes.AuthenticationLevel, startAppModel.AuthenticationLevel, ClaimValueTypes.Integer32, issuer));
claims.AddRange(await _claimsService.GetCustomClaims(profile.UserId, issuer));

ClaimsIdentity identity = new ClaimsIdentity(_generalSettings.GetClaimsIdentity);
identity.AddClaims(claims);
ClaimsPrincipal principal = new ClaimsPrincipal(identity);

string token = _authenticationService.GenerateToken(principal, int.Parse(_generalSettings.GetJwtCookieValidityTime));
string token = await _authenticationService.GenerateTokenForProfile(profile, authenticationLevel);
CreateJwtCookieAndAppendToResponse(token);
}

Expand All @@ -134,6 +123,43 @@ public async Task<ActionResult> LogInTestUser(StartAppModel startAppModel)
// Ensure that the documentstorage in LocalTestingStorageBasePath is updated with the most recent app data
await _applicationRepository.Update(app);

if(_localPlatformSettings.LocalAppMode == "http")
{
// Instantiate a prefill if a file attachment exists.
var prefill = Request.Form.Files.FirstOrDefault();
if (prefill != null)
{
var instance = new Instance{
AppId = app.Id,
Org = app.Org,
InstanceOwner = new(),
DataValues = new(),
};

var owner = prefill.FileName.Split(".")[0];
if (owner.Length == 9)
{
instance.InstanceOwner.OrganisationNumber = owner;
}
else if (owner.Length == 12)
{
instance.InstanceOwner.PersonNumber = owner;
}
else
{
throw new Exception($"instance owner must be specified as part of the prefill filename. 9 digigts for OrganisationNumber, 12 for PersonNumber (eg 897069631.xml, not {prefill.FileName})");
}

var xmlDataId = app.DataTypes.First(dt => dt.AppLogic is not null).Id;

using var reader = new StreamReader(prefill.OpenReadStream());
var content = await reader.ReadToEndAsync();
var newInstance = await _localApp.Instantiate(app.Id, instance, content, xmlDataId);

return Redirect($"{_generalSettings.GetBaseUrl}/{app.Id}/#/instance/{newInstance.Id}");
}
}

return Redirect($"/{app.Id}/");
}

Expand All @@ -151,19 +177,8 @@ public async Task<ActionResult> GetTestUserToken(int userId)
return NotFound();
}

List<Claim> claims = new List<Claim>();
string issuer = _generalSettings.Hostname;
claims.Add(new Claim(AltinnCoreClaimTypes.UserId, profile.UserId.ToString(), ClaimValueTypes.String, issuer));
claims.Add(new Claim(AltinnCoreClaimTypes.UserName, profile.UserName, ClaimValueTypes.String, issuer));
claims.Add(new Claim(AltinnCoreClaimTypes.PartyID, profile.PartyId.ToString(), ClaimValueTypes.Integer32, issuer));
claims.Add(new Claim(AltinnCoreClaimTypes.AuthenticationLevel, "2", ClaimValueTypes.Integer32, issuer));

ClaimsIdentity identity = new ClaimsIdentity(_generalSettings.GetClaimsIdentity);
identity.AddClaims(claims);
ClaimsPrincipal principal = new ClaimsPrincipal(identity);

// Create a test token with long duration
string token = _authenticationService.GenerateToken(principal, 1337);
string token = await _authenticationService.GenerateTokenForProfile(profile, 2);
return Ok(token);
}

Expand All @@ -172,26 +187,12 @@ public async Task<ActionResult> GetTestUserToken(int userId)
/// </summary>
/// <param name="id"></param>
/// <returns></returns>
public async Task<ActionResult> GetTestOrgToken(string id, [FromQuery] string orgNumber = "")
public async Task<ActionResult> GetTestOrgToken(string id, [FromQuery] string orgNumber = null)
{
List<Claim> claims = new List<Claim>();
string issuer = _generalSettings.Hostname;
claims.Add(new Claim(AltinnCoreClaimTypes.Org, id.ToLower(), ClaimValueTypes.String, issuer));
claims.Add(new Claim(AltinnCoreClaimTypes.AuthenticationLevel, "2", ClaimValueTypes.Integer32, issuer));
claims.Add(new Claim("urn:altinn:scope", "altinn:serviceowner/instances.read", ClaimValueTypes.String, issuer));
if (!string.IsNullOrEmpty(orgNumber))
{
claims.Add(new Claim(AltinnCoreClaimTypes.OrgNumber, orgNumber, ClaimValueTypes.String, issuer));
}

ClaimsIdentity identity = new ClaimsIdentity(_generalSettings.GetClaimsIdentity);
identity.AddClaims(claims);
ClaimsPrincipal principal = new ClaimsPrincipal(identity);

// Create a test token with long duration
string token = _authenticationService.GenerateToken(principal, 1337);
string token = await _authenticationService.GenerateTokenForOrg(id, orgNumber);

return await Task.FromResult(Ok(token));
return Ok(token);
}

/// <summary>
Expand Down
5 changes: 5 additions & 0 deletions src/Models/StartAppModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -89,5 +89,10 @@ public class StartAppModel
/// List of possible authentication levels
/// </summary>
public IEnumerable<SelectListItem> AuthenticationLevels { get; set; }

/// <summary>
/// Modify site conditionally on the app mode
/// </summary>
public bool AppModeIsHttp { get; set; }
}
}
108 changes: 87 additions & 21 deletions src/Services/Authentication/Implementation/AuthenticationService.cs
Original file line number Diff line number Diff line change
@@ -1,39 +1,105 @@
#nullable enable
using System;
using System.Collections.Generic;
using System.IdentityModel.Tokens.Jwt;
using System.Security.Claims;
using System.Security.Cryptography.X509Certificates;

using System.Threading.Tasks;
using AuthSettings = Altinn.Platform.Authentication.Configuration.GeneralSettings;
using Altinn.Platform.Profile.Models;
using LocalTest.Clients.CdnAltinnOrgs;
using LocalTest.Services.Authentication.Interface;

using Microsoft.Extensions.Options;
using Microsoft.IdentityModel.Tokens;
using LocalTest.Configuration;
using AltinnCore.Authentication.Constants;
using Altinn.Platform.Authorization.Services.Interface;

namespace LocalTest.Services.Authentication.Implementation;

namespace LocalTest.Services.Authentication.Implementation
public class AuthenticationService : IAuthentication
{
public class AuthenticationService : IAuthentication
private readonly AltinnOrgsClient _orgsClient;
private readonly AuthSettings _authSettings;
private readonly GeneralSettings _generalSettings;
private readonly IClaims _claimsService;

public AuthenticationService(AltinnOrgsClient orgsClient, IOptions<AuthSettings> authSettings, IOptions<GeneralSettings> generalSettings, IClaims claimsService)
{
_orgsClient = orgsClient;
_authSettings = authSettings.Value;
_generalSettings = generalSettings.Value;
_claimsService = claimsService;
}
///<inheritdoc/>
public string GenerateToken(ClaimsPrincipal principal)
{
///<inheritdoc/>
public string GenerateToken(ClaimsPrincipal principal, int cookieValidityTime)
List<X509Certificate2> certificates = new List<X509Certificate2>
{
List<X509Certificate2> certificates = new List<X509Certificate2>
{
new X509Certificate2("jwtselfsignedcert.pfx", "qwer1234") // lgtm [cs/hardcoded-credentials]
};
new X509Certificate2("jwtselfsignedcert.pfx", "qwer1234") // lgtm [cs/hardcoded-credentials]
};

TimeSpan tokenExpiry = new TimeSpan(0, cookieValidityTime, 0);
TimeSpan tokenExpiry = new TimeSpan(0, _authSettings.GetJwtCookieValidityTime, 0);

JwtSecurityTokenHandler tokenHandler = new JwtSecurityTokenHandler();
SecurityTokenDescriptor tokenDescriptor = new SecurityTokenDescriptor
{
Subject = new ClaimsIdentity(principal.Identity),
Expires = DateTime.UtcNow.AddSeconds(tokenExpiry.TotalSeconds),
SigningCredentials = new X509SigningCredentials(certificates[0])
};
JwtSecurityTokenHandler tokenHandler = new JwtSecurityTokenHandler();
SecurityTokenDescriptor tokenDescriptor = new SecurityTokenDescriptor
{
Subject = new ClaimsIdentity(principal.Identity),
Expires = DateTime.UtcNow.AddSeconds(tokenExpiry.TotalSeconds),
SigningCredentials = new X509SigningCredentials(certificates[0])
};

SecurityToken token = tokenHandler.CreateToken(tokenDescriptor);
string serializedToken = tokenHandler.WriteToken(token);

SecurityToken token = tokenHandler.CreateToken(tokenDescriptor);
string serializedToken = tokenHandler.WriteToken(token);
return serializedToken;
}

/// <inheritdoc />
public async Task<string> GenerateTokenForOrg(string org, string? orgNumber = null)
{
if (orgNumber is null)
{
var orgs = await _orgsClient.GetCdnOrgs();
orgNumber = (orgs.Orgs?.TryGetValue(org, out var value) == true ? value : null)?.Orgnr;
}

return serializedToken;
List<Claim> claims = new List<Claim>();
string issuer = _generalSettings.Hostname;
claims.Add(new Claim(AltinnCoreClaimTypes.Org, org.ToLower(), ClaimValueTypes.String, issuer));
// 3 is the default level for altinn tokens form Maskinporten
claims.Add(new Claim(AltinnCoreClaimTypes.AuthenticationLevel, "3", ClaimValueTypes.Integer32, issuer));
claims.Add(new Claim("urn:altinn:scope", "altinn:serviceowner/instances.read", ClaimValueTypes.String, issuer));
if (!string.IsNullOrEmpty(orgNumber))
{
claims.Add(new Claim(AltinnCoreClaimTypes.OrgNumber, orgNumber, ClaimValueTypes.String, issuer));
}

ClaimsIdentity identity = new ClaimsIdentity(_generalSettings.GetClaimsIdentity);
identity.AddClaims(claims);
ClaimsPrincipal principal = new ClaimsPrincipal(identity);

// Create a test token with long duration
return GenerateToken(principal);
}

/// <inheritdoc />
public async Task<string> GenerateTokenForProfile(UserProfile profile, int authenticationLevel)
{
List<Claim> claims = new List<Claim>();
string issuer = _generalSettings.Hostname;
claims.Add(new Claim(ClaimTypes.NameIdentifier, profile.UserId.ToString(), ClaimValueTypes.String, issuer));
claims.Add(new Claim(AltinnCoreClaimTypes.UserId, profile.UserId.ToString(), ClaimValueTypes.String, issuer));
claims.Add(new Claim(AltinnCoreClaimTypes.UserName, profile.UserName, ClaimValueTypes.String, issuer));
claims.Add(new Claim(AltinnCoreClaimTypes.PartyID, profile.PartyId.ToString(), ClaimValueTypes.Integer32, issuer));
claims.Add(new Claim(AltinnCoreClaimTypes.AuthenticationLevel, authenticationLevel.ToString(), ClaimValueTypes.Integer32, issuer));
claims.AddRange(await _claimsService.GetCustomClaims(profile.UserId, issuer));

ClaimsIdentity identity = new ClaimsIdentity(_generalSettings.GetClaimsIdentity);
identity.AddClaims(claims);
ClaimsPrincipal principal = new ClaimsPrincipal(identity);

return GenerateToken(principal);
}
}

Loading

0 comments on commit 7a6a316

Please sign in to comment.