Skip to content

Commit

Permalink
New page for easily finding the Urls for localtest ApiTokens
Browse files Browse the repository at this point in the history
  • Loading branch information
ivarne committed Nov 23, 2024
1 parent 2a0b396 commit 3a23f1b
Show file tree
Hide file tree
Showing 9 changed files with 154 additions and 25 deletions.
2 changes: 1 addition & 1 deletion src/Configuration/LocalPlatformSettings.cs
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ public string LocalTestingStaticTestDataPath
}
}

public string LocalFrontendHostname { get; set; }
public string LocalFrontendHostname { get; set; } = "localhost";

public string LocalFrontendProtocol { get; set; } = "http";

Expand Down
72 changes: 59 additions & 13 deletions src/Controllers/HomeController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ public async Task<IActionResult> Index()
model.Org = model.TestApps[0].Value?.Split("/").FirstOrDefault();
model.App = model.TestApps[0].Value?.Split("/").LastOrDefault();
}
model.TestUsers = await GetTestUsersForList();
model.TestUsers = await GetTestUsersAndPartiesSelectList();
model.UserSelect = Request.Cookies["Localtest_User.Party_Select"];
var defaultAuthLevel = await GetAppAuthLevel(model.AppModeIsHttp, model.TestApps);
model.AuthenticationLevels = GetAuthenticationLevels(defaultAuthLevel);
Expand Down Expand Up @@ -112,6 +112,7 @@ public IActionResult Error()
/// <summary>
/// Method that logs inn test user
/// </summary>
/// <param name="action">Set to "reauthenticate" if you want to set cookies with no redirect</param>
/// <param name="startAppModel">An object with information about app and user.</param>
/// <returns>Redirects to returnUrl</returns>
[HttpPost]
Expand Down Expand Up @@ -165,7 +166,7 @@ public async Task<ActionResult> LogInTestUser(string action, StartAppModel start

using var reader = new StreamReader(prefill.OpenReadStream());
var content = await reader.ReadToEndAsync();
var token = await _authenticationService.GenerateTokenForOrg(app.Id.Split("/")[0]);
var token = await _authenticationService.GenerateTokenForOrg(app.Id.Split("/")[0], orgNumber: null, authenticationLevel: 3);
var newInstance = await _localApp.Instantiate(app.Id, instance, content, xmlDataId, token);

return Redirect($"/{app.Id}/#/instance/{newInstance.Id}");
Expand All @@ -175,13 +176,30 @@ public async Task<ActionResult> LogInTestUser(string action, StartAppModel start
return Redirect($"/{app.Id}/");
}


[AllowAnonymous]
[HttpGet("/Home/Tokens")]
public async Task<IActionResult> Tokens()
{
var model = new TokensViewModel
{
AuthenticationLevels = GetAuthenticationLevels(2),
TestUsers = await GetUsersSelectList(),
DefaultOrg = _localPlatformSettings.LocalAppMode == "http" ? (await GetAppsList()).First().Value?.Split("/").FirstOrDefault() : null,
};

return View(model);
}


/// <summary>
///
/// Returns a user token with the given userId as claim
/// </summary>
/// <param name="userId"></param>
/// <param name="userId">UserId of the token holder</param>
/// <param name="authenticationLevel">Authentication level of the token</param>
/// <returns></returns>
[HttpGet("{userId}")]
public async Task<ActionResult> GetTestUserToken(int userId)
[HttpGet("/Home/GetTestUserToken/{userId?}")]
public async Task<ActionResult> GetTestUserToken(int userId, [FromQuery] int authenticationLevel = 2)
{
UserProfile profile = await _userProfileService.GetUser(userId);

Expand All @@ -191,25 +209,36 @@ public async Task<ActionResult> GetTestUserToken(int userId)
}

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

/// <summary>
/// Returns a org token with the given org as claim
/// </summary>
/// <param name="id"></param>
/// <param name="org">The short code used to identify the service owner org</param>
/// <param name="orgNumber">Organization number to be included in token (if not an official service owner)</param>
/// <param name="authenticationLevel">Authentication level of the token</param>
/// <returns></returns>
[HttpGet("{id}")]
public async Task<ActionResult> GetTestOrgToken(string id, [FromQuery] string orgNumber = null)
[HttpGet("/Home/GetTestOrgToken/{org?}")]
public async Task<ActionResult> GetTestOrgToken(string org, [FromQuery] string orgNumber = null, [FromQuery] int? authenticationLevel = 3)
{
// Create a test token with long duration
string token = await _authenticationService.GenerateTokenForOrg(org, orgNumber, authenticationLevel);

return Ok(token);
}

[HttpPost("{org}")]
public async Task<ActionResult> GetTestOrgTokenPost(string org, [FromQuery] string orgNumber = null, [FromQuery] int? authenticationLevel = 3)
{
// Create a test token with long duration
string token = await _authenticationService.GenerateTokenForOrg(id, orgNumber);
string token = await _authenticationService.GenerateTokenForOrg(org, orgNumber, authenticationLevel);

return Ok(token);
}

private async Task<IEnumerable<SelectListItem>> GetTestUsersForList()
private async Task<IEnumerable<SelectListItem>> GetTestUsersAndPartiesSelectList()
{
var data = await _testDataService.GetTestData();
var userItems = new List<SelectListItem>();
Expand Down Expand Up @@ -252,6 +281,23 @@ private async Task<IEnumerable<SelectListItem>> GetTestUsersForList()
return userItems;
}

private async Task<List<SelectListItem>> GetUsersSelectList()
{
var data = await _testDataService.GetTestData();
var testUsers = new List<SelectListItem>();
foreach (UserProfile profile in data.Profile.User.Values)
{
var properProfile = await _userProfileService.GetUser(profile.UserId);
testUsers.Add(new()
{
Text = properProfile?.Party.Name,
Value = profile.UserId.ToString(),
});
}

return testUsers;
}

private async Task<int> GetAppAuthLevel(bool isHttp, IEnumerable<SelectListItem> testApps)
{
if (!isHttp)
Expand All @@ -276,7 +322,7 @@ private async Task<int> GetAppAuthLevel(bool isHttp, IEnumerable<SelectListItem>
}
}

private List<SelectListItem> GetAuthenticationLevels(int defaultAuthLevel)
private static List<SelectListItem> GetAuthenticationLevels(int defaultAuthLevel)
{
return new()
{
Expand Down
11 changes: 11 additions & 0 deletions src/Models/TokensModel.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
#nullable enable
using Microsoft.AspNetCore.Mvc.Rendering;

namespace LocalTest.Models;

public class TokensViewModel
{
public required IEnumerable<SelectListItem> TestUsers { get; init; } = default!;
public required List<SelectListItem> AuthenticationLevels { get; init; } = default!;
public required string DefaultOrg { get; init; }
}
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ public string GenerateToken(ClaimsPrincipal principal)
}

/// <inheritdoc />
public async Task<string> GenerateTokenForOrg(string org, string? orgNumber = null)
public async Task<string> GenerateTokenForOrg(string org, string? orgNumber, int? authenticationLevel)
{
if (orgNumber is null)
{
Expand All @@ -68,7 +68,7 @@ public async Task<string> GenerateTokenForOrg(string org, string? orgNumber = nu
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(AltinnCoreClaimTypes.AuthenticationLevel, authenticationLevel.ToString(), ClaimValueTypes.Integer32, issuer));
claims.Add(new Claim("urn:altinn:scope", "altinn:serviceowner/instances.read", ClaimValueTypes.String, issuer));
if (!string.IsNullOrEmpty(orgNumber))
{
Expand Down
3 changes: 2 additions & 1 deletion src/Services/Authentication/Interface/IAuthentication.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,9 @@ public interface IAuthentication
/// </summary>
/// <param name="org">Three letter application owner name (eg, TST )</param>
/// <param name="orgNumber">Optional Organization number for the application owner. Will be fetched if not provided</param>
/// <param name="authenticationLevel">The authentication level of the generated token</param>
/// <returns>JWT token</returns>
public Task<string> GenerateTokenForOrg(string org, string? orgNumber = null);
public Task<string> GenerateTokenForOrg(string org, string? orgNumber, int? authenticationLevel);

/// <summary>
/// Get JWT token for user profile
Expand Down
25 changes: 18 additions & 7 deletions src/Services/Storage/Implementation/ApplicationRepository.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,18 +9,22 @@
using Altinn.Platform.Storage.Repository;

using LocalTest.Configuration;

using LocalTest.Services.LocalApp.Interface;
using Microsoft.Extensions.Options;

namespace LocalTest.Services.Storage.Implementation
{
public class ApplicationRepository : IApplicationRepository
{
private readonly LocalPlatformSettings _localPlatformSettings;
private readonly ILocalApp _localApp;

public ApplicationRepository(IOptions<LocalPlatformSettings> localPlatformSettings)
public ApplicationRepository(
IOptions<LocalPlatformSettings> localPlatformSettings,
ILocalApp localApp)
{
_localPlatformSettings = localPlatformSettings.Value;
_localApp = localApp;
}

public Task<Application> Create(Application item)
Expand All @@ -35,13 +39,20 @@ public Task<bool> Delete(string appId, string org)

public async Task<Application> FindOne(string appId, string org)
{
var filename = GetApplicationsDirectory() + appId + ".json";
if (File.Exists(filename))
try
{
var application = JsonSerializer.Deserialize<Application>(await File.ReadAllTextAsync(filename));
if (application is not null)
// Get fresh first
return await _localApp.GetApplicationMetadata(appId);
}
catch (Exception){
var filename = GetApplicationsDirectory() + appId + ".json";
if (File.Exists(filename))

Check failure

Code scanning / CodeQL

Uncontrolled data used in path expression High

This path depends on a
user-provided value
.
This path depends on a
user-provided value
.
This path depends on a
user-provided value
.
This path depends on a
user-provided value
.
This path depends on a
user-provided value
.
{
return application;
var application = JsonSerializer.Deserialize<Application>(await File.ReadAllTextAsync(filename));

Check failure

Code scanning / CodeQL

Uncontrolled data used in path expression High

This path depends on a
user-provided value
.
This path depends on a
user-provided value
.
This path depends on a
user-provided value
.
This path depends on a
user-provided value
.
This path depends on a
user-provided value
.
if (application is not null)
{
return application;
}
}
}

Expand Down
2 changes: 1 addition & 1 deletion src/Services/Storage/Implementation/DataRepository.cs
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ public async Task<List<DataElement>> ReadAll(Guid instanceGuid)
dataElements.Add(instance);
}
}
return dataElements.OrderBy(x => x.Created).ToList();
return dataElements;
}

public async Task<Stream> ReadDataFromStorage(string org, string blobStoragePath)
Expand Down
55 changes: 55 additions & 0 deletions src/Views/Home/Tokens.cshtml
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
@model TokensViewModel
@{
ViewData["Title"] = "Tokens for localtest";
}
<div class="text-center">
<h1 class="display-4">Welcome to Altinn App Local Testing</h1>
<p>Create tokens for accessing the localtest apis.</p>

<div class="alert alert-warning">Note that LocalTest is not an exact replica of the production systems, and that there are differences</div>

<h2>Generate end users token (like from idporten)</h2>
@using (Html.BeginForm("GetTestUserToken", "Home", FormMethod.Get, new { Class = "form" }))
{
<div class="form-group">
<label>Select user</label>
@Html.DropDownList("userId", Model.TestUsers, new { Class = "form-control" })
</div>
<div class="form-group">
<label>Select your authentication level</label>
@Html.DropDownList("authenticationLevel", Model.AuthenticationLevels, new { Class = "form-control" })
</div>

<div class="form-group">
<button type="submit" class="btn btn-primary">Get User token</button>
</div>
}
<h2>Service owner tokens</h2>

@using (Html.BeginForm("GetTestOrgToken", "Home", FormMethod.Get, new { Class = "form-signin" }))
{
<div class="form-group">
<label>Select service owner org</label>
@Html.TextBox("org", Model.DefaultOrg, new { Class = "form-control" })
</div>
<div class="form-group">
<label>optional org number for the token</label>
@Html.TextBox("orgNumber", "", new { Class = "form-control", Placeholder = "For official orgs this is fetch from altinncdn.no" })
</div>
<div class="form-group">
<label>Select your authentication level</label>
@Html.DropDownList("authenticationLevel", Model.AuthenticationLevels, new { Class = "form-control" })
</div>

<div class="form-group">
<button type="submit" class="btn btn-primary">Get User token</button>
</div>
}
</div>

@section Styles
{
<style>
</style>
}
5 changes: 5 additions & 0 deletions src/Views/Shared/_Layout.cshtml
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
<title>@ViewData["Title"] - Altinn Studio</title>
<link href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-T3c6CoIi6uLrA9TneNEoa7RxnatzjcDSCmG1MXxSR1GAsXEV/Dwwykc2MPK8M2HN" crossorigin="anonymous">
<link rel="stylesheet" href="~/localtestresources/css/site.css" />
@RenderSection("Styles", required: false)
</head>
<body>
<header>
Expand All @@ -29,6 +30,10 @@
<a class="nav-link text-secondary" asp-area="" asp-controller="TenorUsers"
asp-action="Index">User administration</a>
</li>
<li class="nav-item">
<a class="nav-link text-dark" asp-area="" asp-controller="Home"
asp-action="Tokens">Tokens</a>
</li>
<li class="nav-item">
<a class="nav-link text-dark" asp-area="" asp-controller="StorageExplorer" asp-action="Index">Storage Explorer</a>
</li>
Expand Down

0 comments on commit 3a23f1b

Please sign in to comment.