Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
84 changes: 33 additions & 51 deletions src/Altinn.App.Api/Controllers/StatelessDataController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,6 @@ public class StatelessDataController : ControllerBase
private readonly IPrefill _prefillService;
private readonly IAltinnPartyClient _altinnPartyClientClient;
private readonly IPDP _pdp;
private readonly IAuthenticationContext _authenticationContext;
private const long REQUEST_SIZE_LIMIT = 2000 * 1024 * 1024;

private const string PartyPrefix = "partyid";
Expand Down Expand Up @@ -63,7 +62,6 @@ IAuthenticationContext authenticationContext
_prefillService = prefillService;
_altinnPartyClientClient = altinnPartyClientClient;
_pdp = pdp;
_authenticationContext = authenticationContext;
}

/// <summary>
Expand All @@ -85,7 +83,7 @@ public async Task<ActionResult> Get(
[FromRoute] string org,
[FromRoute] string app,
[FromQuery] string dataType,
[FromHeader(Name = "party")] string partyFromHeader,
[FromHeader(Name = "party")] string? partyFromHeader,
[FromQuery] string? language = null
)
{
Expand All @@ -105,11 +103,20 @@ public async Task<ActionResult> Get(
);
}

if (partyFromHeader is null)
{
return BadRequest(
$"Invalid party header. Please provide a party header on the form partyid:123, org:[orgnr] or person:[ssn]"
);
}

InstanceOwner? owner = await GetInstanceOwner(partyFromHeader);

if (owner is null)
{
return BadRequest(
$"Invalid party header. Please provide a party header on the form partyid:123, org:[orgnr] or person:[ssn]"
$"Invalid party header. Could not lookup instance owner from the provided partyid: ${partyFromHeader}. "
+ $"Make sure partyid is represented with prefix \"partyId:\", \"person:\" or \"org:\" (eg: \"partyId:123\")"
);
}

Expand Down Expand Up @@ -307,58 +314,33 @@ public async Task<ActionResult> PostAnonymous([FromQuery] string dataType, [From
return Ok(appModel);
}

private async Task<InstanceOwner?> GetInstanceOwner(string? partyFromHeader)
private async Task<InstanceOwner?> GetInstanceOwner(string partyFromHeader)
{
// Use the party id of the logged in user, if no party id is given in the header
// Not sure if this is really used anywhere. It doesn't seem useful, as you'd
// always want to create an instance based on the selected party, not the person
// you happened to log in as.
if (partyFromHeader is null)
// Get the party as read in from the header. Authorization happens later.
var headerParts = partyFromHeader.Split(':');
if (partyFromHeader.Contains(',') || headerParts.Length != 2)
{
var currentAuth = _authenticationContext.Current;
Party? party = currentAuth switch
{
Authenticated.User auth => await auth.LookupSelectedParty(),
Authenticated.SelfIdentifiedUser auth => (await auth.LoadDetails()).Party,
Authenticated.Org auth => (await auth.LoadDetails()).Party,
Authenticated.ServiceOwner auth => (await auth.LoadDetails()).Party,
Authenticated.SystemUser auth => (await auth.LoadDetails()).Party,
_ => null,
};

if (party is null)
return null;

return InstantiationHelper.PartyToInstanceOwner(party);
return null;
}
else

var id = headerParts[1];
var idPrefix = headerParts[0].ToLowerInvariant();
var party = idPrefix switch
{
// Get the party as read in from the header. Authorization happens later.
var headerParts = partyFromHeader.Split(':');
if (partyFromHeader.Contains(',') || headerParts.Length != 2)
{
return null;
}

var id = headerParts[1];
var idPrefix = headerParts[0].ToLowerInvariant();
var party = idPrefix switch
{
PartyPrefix => await _altinnPartyClientClient.GetParty(int.TryParse(id, out var partyId) ? partyId : 0),

// Frontend seems to only use partyId, not orgnr or ssn.
PersonPrefix => await _altinnPartyClientClient.LookupParty(new PartyLookup { Ssn = id }),
OrgPrefix => await _altinnPartyClientClient.LookupParty(new PartyLookup { OrgNo = id }),
_ => null,
};

if (party is null || party.PartyId == 0)
{
return null;
}

return InstantiationHelper.PartyToInstanceOwner(party);
PartyPrefix => await _altinnPartyClientClient.GetParty(int.TryParse(id, out var partyId) ? partyId : 0),

// Frontend seems to only use partyId, not orgnr or ssn.
PersonPrefix => await _altinnPartyClientClient.LookupParty(new PartyLookup { Ssn = id }),
OrgPrefix => await _altinnPartyClientClient.LookupParty(new PartyLookup { OrgNo = id }),
_ => null,
};

if (party is null || party.PartyId == 0)
{
return null;
}

return InstantiationHelper.PartyToInstanceOwner(party);
}

private async Task<EnforcementResult> AuthorizeAction(string org, string app, int partyId, string action)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -290,6 +290,9 @@ public async Task Get_Returns_Forbidden_when_returned_descision_is_Deny()
var dataProcessorMock = new Mock<IDataProcessor>();
var prefillMock = new Mock<IPrefill>();
var registerMock = new Mock<IAltinnPartyClient>();
registerMock
.Setup(r => r.GetParty(501337))
.ReturnsAsync(new Platform.Register.Models.Party { PartyId = 501337 });
var pdpMock = new Mock<IPDP>();
var authContextMock = new Mock<IAuthenticationContext>();
var dataType = "some-value";
Expand All @@ -307,6 +310,7 @@ public async Task Get_Returns_Forbidden_when_returned_descision_is_Deny()
statelessDataController.ControllerContext = new ControllerContext();
statelessDataController.ControllerContext.HttpContext = new DefaultHttpContext();
statelessDataController.ControllerContext.HttpContext.User = TestAuthentication.GetUserPrincipal();

authContextMock.Setup(c => c.Current).Returns(TestAuthentication.GetUserAuthentication());
pdpMock
.Setup(p => p.GetDecisionForRequest(It.IsAny<XacmlJsonRequestRoot>()))
Expand All @@ -322,7 +326,7 @@ public async Task Get_Returns_Forbidden_when_returned_descision_is_Deny()

// Act
appResourcesMock.Setup(x => x.GetClassRefForLogicDataType(dataType)).Returns(typeof(DummyModel).FullName!);
var result = await statelessDataController.Get("ttd", "demo-app", dataType, null!);
var result = await statelessDataController.Get("ttd", "demo-app", dataType, "partyId:501337");

// Assert
result.Should().BeOfType<StatusCodeResult>().Which.StatusCode.Should().Be(403);
Expand All @@ -332,7 +336,6 @@ public async Task Get_Returns_Forbidden_when_returned_descision_is_Deny()
pdpMock.VerifyNoOtherCalls();
dataProcessorMock.VerifyNoOtherCalls();
prefillMock.VerifyNoOtherCalls();
registerMock.VerifyNoOtherCalls();
}

[Fact]
Expand All @@ -344,6 +347,9 @@ public async Task Get_Returns_OK_with_appModel()
var dataProcessorMock = new Mock<IDataProcessor>();
var prefillMock = new Mock<IPrefill>();
var registerMock = new Mock<IAltinnPartyClient>();
registerMock
.Setup(r => r.GetParty(501337))
.ReturnsAsync(new Platform.Register.Models.Party { PartyId = 501337 });
var pdpMock = new Mock<IPDP>();
var authContextMock = new Mock<IAuthenticationContext>();
var dataType = "some-value";
Expand Down Expand Up @@ -379,7 +385,7 @@ public async Task Get_Returns_OK_with_appModel()

// Act
appResourcesMock.Setup(x => x.GetClassRefForLogicDataType(dataType)).Returns(classRef);
var result = await statelessDataController.Get("ttd", "demo-app", dataType, null!);
var result = await statelessDataController.Get("ttd", "demo-app", dataType, "partyId:501337");

// Assert
result.Should().BeOfType<OkObjectResult>().Which.StatusCode.Should().Be(200);
Expand All @@ -400,6 +406,82 @@ public async Task Get_Returns_OK_with_appModel()
pdpMock.VerifyNoOtherCalls();
dataProcessorMock.VerifyNoOtherCalls();
prefillMock.VerifyNoOtherCalls();
registerMock.VerifyNoOtherCalls();
}

[Fact]
public async Task Get_Returns_OK_When_Party_Is_Valid()
{
// Arrange
var dataType = "some-value";
var classRef = typeof(DummyModel).FullName!;

// Create mocks
var appModelMock = new Mock<IAppModel>();
var appResourcesMock = new Mock<IAppResources>();
var dataProcessorMock = new Mock<IDataProcessor>();
var prefillMock = new Mock<IPrefill>();
var registerMock = new Mock<IAltinnPartyClient>();
var pdpMock = new Mock<IPDP>();
var authContextMock = new Mock<IAuthenticationContext>();

// Set up the app resources so we have a valid classRef
appResourcesMock.Setup(x => x.GetClassRefForLogicDataType(dataType)).Returns(classRef);

// Set up the AltinnPartyClient to return a valid party
registerMock.Setup(r => r.GetParty(123)).ReturnsAsync(new Platform.Register.Models.Party { PartyId = 123 });

// Set up PD decision to Permit
pdpMock
.Setup(p => p.GetDecisionForRequest(It.IsAny<XacmlJsonRequestRoot>()))
.ReturnsAsync(
new XacmlJsonResponse
{
Response = new List<XacmlJsonResult>
{
new XacmlJsonResult { Decision = XacmlContextDecision.Permit.ToString() },
},
}
);

// Set up IAppModel so it can create a dummy model
appModelMock.Setup(a => a.Create(classRef)).Returns(new DummyModel());

// For demonstration, mock the user principal in the controller context
var principal = TestAuthentication.GetUserPrincipal();
authContextMock.Setup(c => c.Current).Returns(TestAuthentication.GetUserAuthentication());

// Instantiate the controller
ILogger<DataController> logger = new NullLogger<DataController>();
var controller = new StatelessDataController(
logger,
appModelMock.Object,
appResourcesMock.Object,
prefillMock.Object,
registerMock.Object,
pdpMock.Object,
new IDataProcessor[] { dataProcessorMock.Object },
authContextMock.Object
)
{
ControllerContext = new ControllerContext { HttpContext = new DefaultHttpContext { User = principal } },
};

// Act
// We pass in "partyid:123" as the party header to match the mock above
var result = await controller.Get("ttd", "demo-app", dataType, "partyid:123");

// Assert
result.Should().BeOfType<OkObjectResult>().Which.StatusCode.Should().Be(200);
result.Should().BeOfType<OkObjectResult>().Which.Value.Should().BeOfType<DummyModel>();

// Verify the calls
registerMock.Verify(r => r.GetParty(123), Times.Once);
appResourcesMock.Verify(x => x.GetClassRefForLogicDataType(dataType), Times.Once);
pdpMock.Verify(p => p.GetDecisionForRequest(It.IsAny<XacmlJsonRequestRoot>()), Times.Once);
appModelMock.Verify(a => a.Create(classRef), Times.Once);
dataProcessorMock.Verify(
p => p.ProcessDataRead(It.IsAny<Instance>(), null, It.IsAny<DummyModel>(), null),
Times.Once
);
}
}
Loading