Skip to content

Commit

Permalink
DP-1035 Dynamic FTS return URLs for integration environment
Browse files Browse the repository at this point in the history
  • Loading branch information
dharmverma committed Dec 27, 2024
1 parent e752787 commit 3de37a9
Show file tree
Hide file tree
Showing 8 changed files with 97 additions and 14 deletions.
21 changes: 18 additions & 3 deletions Frontend/CO.CDP.OrganisationApp.Tests/FtsUrlServiceTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,24 +7,39 @@ namespace CO.CDP.OrganisationApp.Tests;

public class FtsUrlServiceTests
{
private readonly Mock<ISession> _sessionMock;
private readonly Mock<IConfiguration> _configurationMock;
private readonly Mock<ICookiePreferencesService> _cookiePreferencesService;
private readonly IFtsUrlService _service;

public FtsUrlServiceTests()
{
_sessionMock = new();
_configurationMock = new Mock<IConfiguration>();
_cookiePreferencesService = new Mock<ICookiePreferencesService>();
_configurationMock.Setup(c => c["FtsService"]).Returns("https://example.com/");
_service = new FtsUrlService(_configurationMock.Object, _cookiePreferencesService.Object);
_service = new FtsUrlService(_configurationMock.Object, _cookiePreferencesService.Object, _sessionMock.Object);
}

[Fact]
public void BuildUrl_WhenFtsServiceOriginSessionIsSet_BaseServiceUrlOriginShouldBeSessionValue()
{
_sessionMock.Setup(s => s.Get<string?>(Session.FtsServiceOrigin)).Returns("https://example1.com/");
_configurationMock.Setup(c => c["FtsService"]).Returns("https://example2.com/");
var service = new FtsUrlService(_configurationMock.Object, _cookiePreferencesService.Object, _sessionMock.Object);
CultureInfo.CurrentUICulture = new CultureInfo("en-GB");

var result = service.BuildUrl("test-endpoint");

result.Should().Be("https://example1.com/test-endpoint?language=en_GB&cookies_accepted=unknown");
}

[Fact]
public void Constructor_ShouldThrowException_WhenFtsServiceIsNotConfigured()
{
_configurationMock.Setup(c => c["FtsService"]).Returns((string?)null);

Action action = () => new FtsUrlService(_configurationMock.Object, _cookiePreferencesService.Object);
Action action = () => new FtsUrlService(_configurationMock.Object, _cookiePreferencesService.Object, _sessionMock.Object);

action.Should().Throw<InvalidOperationException>()
.WithMessage("FtsService is not configured.");
Expand All @@ -34,7 +49,7 @@ public void Constructor_ShouldThrowException_WhenFtsServiceIsNotConfigured()
public void BuildUrl_ShouldTrimTrailingSlashFromBaseServiceUrl()
{
_configurationMock.Setup(c => c["FtsService"]).Returns("https://example.com/");
var service = new FtsUrlService(_configurationMock.Object, _cookiePreferencesService.Object);
var service = new FtsUrlService(_configurationMock.Object, _cookiePreferencesService.Object, _sessionMock.Object);
var endpoint = "test-endpoint";
CultureInfo.CurrentUICulture = new CultureInfo("en-GB");

Expand Down
48 changes: 47 additions & 1 deletion Frontend/CO.CDP.OrganisationApp.Tests/Pages/OneLoginTest.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using CO.CDP.OrganisationApp.Authentication;
using CO.CDP.OrganisationApp.Constants;
using CO.CDP.OrganisationApp.Models;
using CO.CDP.OrganisationApp.Pages;
using CO.CDP.OrganisationApp.WebApiClients;
Expand All @@ -9,7 +10,9 @@
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Logging;
using Microsoft.FeatureManagement;
using Moq;
using System.Security.Claims;

Expand All @@ -24,6 +27,8 @@ public class OneLoginTest
private readonly Mock<IOneLoginAuthority> oneLoginAuthorityMock = new();
private readonly Mock<IAuthenticationService> authService = new();
private readonly Mock<IAuthorityClient> authorityClientMock = new();
private readonly Mock<IFeatureManager> featureManagerMock = new();
private readonly Mock<IConfiguration> configMock = new();
private const string urn = "urn:fdc:gov.uk:2022:7wTqYGMFQxgukTSpSI2GodMwe9";

[Fact]
Expand All @@ -48,6 +53,45 @@ public async Task OnGetSignIn_WhenRedirectUrlProvides_ShouldReturnAuthChallangeW
.Which.Properties!.RedirectUri.Should().Be("/one-login/user-info?redirectUri=%2Forg%2F1");
}

[Fact]
public async Task OnGetSignIn_WhenOriginIsNotNull_ShouldSetSessionWithFtsServiceOriginKey()
{
configMock.Setup(c => c["FtsServiceAllowedOrigins"]).Returns("http://example1.com,http://example2.com");
featureManagerMock.Setup(f => f.IsEnabledAsync(FeatureFlags.AllowDynamicFtsOrigins)).ReturnsAsync(true);
var origin = "http://example1.com";
var model = GivenOneLoginModel("sign-in");

var result = await model.OnGetAsync("/org/1", origin: origin);

sessionMock.Verify(s => s.Set(Session.FtsServiceOrigin, origin), Times.Once);
}

[Fact]
public async Task OnGetSignIn_WhenOriginIsNotListedInConfiguration_ShouldNotSetSessionWithFtsServiceOriginKey()
{
configMock.Setup(c => c["FtsServiceAllowedOrigins"]).Returns("http://example1.com,http://example2.com");
featureManagerMock.Setup(f => f.IsEnabledAsync(FeatureFlags.AllowDynamicFtsOrigins)).ReturnsAsync(true);
var origin = "http://example3.com";
var model = GivenOneLoginModel("sign-in");

var result = await model.OnGetAsync("/org/1", origin: origin);

sessionMock.Verify(s => s.Set(Session.FtsServiceOrigin, origin), Times.Never);
}

[Fact]
public async Task OnGetSignIn_WhenAllowDynamicFtsOriginsFeatureIsDisabled_ShouldNotSetSessionWithFtsServiceOriginKey()
{
configMock.Setup(c => c["FtsServiceAllowedOrigins"]).Returns("http://example1.com,http://example2.com");
featureManagerMock.Setup(f => f.IsEnabledAsync(FeatureFlags.AllowDynamicFtsOrigins)).ReturnsAsync(false);

var model = GivenOneLoginModel("sign-in");

var result = await model.OnGetAsync("/org/1", origin: "http://example1.com");

sessionMock.Verify(s => s.Set(Session.FtsServiceOrigin, "origin"), Times.Never);
}

[Fact]
public async Task OnGetUserInfo_OnSuccessfulAuthentication_ShouldRetrieveUserProfile()
{
Expand Down Expand Up @@ -320,7 +364,9 @@ private OneLoginModel GivenOneLoginModel(string pageAction)
logoutManagerMock.Object,
oneLoginAuthorityMock.Object,
authorityClientMock.Object,
new Mock<ILogger<OneLoginModel>>().Object)
new Mock<ILogger<OneLoginModel>>().Object,
featureManagerMock.Object,
configMock.Object)
{ PageAction = pageAction };
}
}
1 change: 1 addition & 0 deletions Frontend/CO.CDP.OrganisationApp/Constants/FeatureFlags.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,5 @@ namespace CO.CDP.OrganisationApp.Constants;
public static class FeatureFlags
{
public const string Consortium = "Consortium";
public const string AllowDynamicFtsOrigins = "AllowDynamicFtsOrigins";
}
8 changes: 6 additions & 2 deletions Frontend/CO.CDP.OrganisationApp/FtsUrlService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,14 @@ public class FtsUrlService : IFtsUrlService
{
private readonly string _ftsService;
private readonly ICookiePreferencesService _cookiePreferencesService;
private readonly ISession _session;

public FtsUrlService(IConfiguration configuration, ICookiePreferencesService cookiePreferencesService)
public FtsUrlService(IConfiguration configuration, ICookiePreferencesService cookiePreferencesService, ISession session)
{
var ftsService = configuration["FtsService"] ?? throw new InvalidOperationException("FtsService is not configured.");
_session = session;
var ftsService = _session.Get<string?>(Session.FtsServiceOrigin)
?? configuration["FtsService"]
?? throw new InvalidOperationException("FtsService is not configured.");
_ftsService = ftsService.TrimEnd('/');
_cookiePreferencesService = cookiePreferencesService;
}
Expand Down
25 changes: 19 additions & 6 deletions Frontend/CO.CDP.OrganisationApp/Pages/OneLogin.cshtml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
using Microsoft.AspNetCore.Authentication.OpenIdConnect;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.FeatureManagement;
using System.Net;

namespace CO.CDP.OrganisationApp.Pages;
Expand All @@ -22,16 +23,18 @@ public class OneLoginModel(
ILogoutManager logoutManager,
IOneLoginAuthority oneLoginAuthority,
IAuthorityClient authorityClient,
ILogger<OneLoginModel> logger) : PageModel
ILogger<OneLoginModel> logger,
IFeatureManager featureManager,
IConfiguration config) : PageModel
{
[BindProperty(SupportsGet = true)]
public required string PageAction { get; set; }

public async Task<IActionResult> OnGetAsync(string? redirectUri = null)
public async Task<IActionResult> OnGetAsync(string? redirectUri = null, string? origin = null)
{
return PageAction.ToLower() switch
{
"sign-in" => SignIn(redirectUri),
"sign-in" => await SignIn(redirectUri, origin),
"user-info" => await UserInfo(redirectUri),
"sign-out" => await SignOut(),
_ => Redirect("/"),
Expand Down Expand Up @@ -68,8 +71,18 @@ public async Task<IActionResult> OnPostAsync(string logout_token)
return Page();
}

private IActionResult SignIn(string? redirectUri = null)
private async Task<ChallengeResult> SignIn(string? redirectUri = null, string? origin = null)
{
if (!string.IsNullOrWhiteSpace(origin)
&& await featureManager.IsEnabledAsync(FeatureFlags.AllowDynamicFtsOrigins))
{
var allowedOrigins = config["FtsServiceAllowedOrigins"] ?? "";
if (allowedOrigins.Split(",", StringSplitOptions.RemoveEmptyEntries).Contains(origin))
{
session.Set(Session.FtsServiceOrigin, origin);
}
}

var uri = "/one-login/user-info";
if (Helper.ValidRelativeUri(redirectUri))
{
Expand All @@ -84,7 +97,7 @@ private async Task<IActionResult> UserInfo(string? redirectUri = null)
var userInfo = await httpContextAccessor.HttpContext!.AuthenticateAsync();
if (!userInfo.Succeeded)
{
return SignIn();
return await SignIn(redirectUri);
}

var urn = userInfo.Principal.FindFirst(JwtClaimTypes.Subject)?.Value;
Expand All @@ -93,7 +106,7 @@ private async Task<IActionResult> UserInfo(string? redirectUri = null)

if (urn == null)
{
return SignIn();
return await SignIn(redirectUri);
}
await logoutManager.RemoveAsLoggedOut(urn);

Expand Down
1 change: 1 addition & 0 deletions Frontend/CO.CDP.OrganisationApp/Session.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ public class Session(IHttpContextAccessor httpContextAccessor) : ISession
public const string RegistrationDetailsKey = "RegistrationDetails";
public const string ConnectedPersonKey = "ConnectedPerson";
public const string ConsortiumKey = "Consortium";
public const string FtsServiceOrigin = "FtsServiceOrigin";

public T? Get<T>(string key)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
"DiagnosticPage": {
"Enabled": false,
"Path": ""
}
},
"AllowDynamicFtsOrigins": true
}
}
4 changes: 3 additions & 1 deletion Frontend/CO.CDP.OrganisationApp/appsettings.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
"DataSharingService": "",
"EntityVerificationService": "",
"FtsService": "",
"FtsServiceAllowedOrigins": "",
"OneLogin": {
"Authority": "",
"ClientId": "",
Expand Down Expand Up @@ -56,7 +57,8 @@
},
"SharedSessions": false,
"Consortium": false,
"ContentSecurityPolicy": true
"ContentSecurityPolicy": true,
"AllowDynamicFtsOrigins": false
},
"CompaniesHouse": {
"Url": "",
Expand Down

0 comments on commit 3de37a9

Please sign in to comment.