diff --git a/Libraries/CO.CDP.Authentication/Extensions.cs b/Libraries/CO.CDP.Authentication/Extensions.cs index f1b0bfc0c..4bc4fc72b 100644 --- a/Libraries/CO.CDP.Authentication/Extensions.cs +++ b/Libraries/CO.CDP.Authentication/Extensions.cs @@ -9,6 +9,7 @@ using Microsoft.Extensions.DependencyInjection.Extensions; using Microsoft.Extensions.Hosting; using Microsoft.IdentityModel.Tokens; +using static CO.CDP.Authentication.Constants; namespace CO.CDP.Authentication; @@ -86,6 +87,11 @@ public static AuthorizationBuilder AddEntityVerificationAuthorization(this IServ { return services .AddAuthorizationBuilder() + .AddPolicy("OneLoginPolicy", policy => + { + policy.RequireAuthenticatedUser(); + policy.RequireClaim(ClaimType.Channel, Channel.OneLogin); + }) .SetFallbackPolicy( new AuthorizationPolicyBuilder() .AddAuthenticationSchemes(JwtBearerDefaults.AuthenticationScheme) diff --git a/Services/CO.CDP.EntityVerification.Tests/Api/GetIdentifiersTests.cs b/Services/CO.CDP.EntityVerification.Tests/Api/GetIdentifiersTests.cs new file mode 100644 index 000000000..fb34ce2df --- /dev/null +++ b/Services/CO.CDP.EntityVerification.Tests/Api/GetIdentifiersTests.cs @@ -0,0 +1,35 @@ +using CO.CDP.EntityVerification.Model; +using CO.CDP.EntityVerification.UseCase; +using FluentAssertions; +using Microsoft.Extensions.DependencyInjection; +using Moq; +using System.Net; +using static CO.CDP.Authentication.Constants; +using static System.Net.HttpStatusCode; + +namespace CO.CDP.EntityVerification.Tests.Api; + +public class GetIdentifiersTests +{ + private readonly Mock>> _lookupIdentifierUseCase = new(); + + [Theory] + [InlineData(OK, Channel.OneLogin)] + [InlineData(Forbidden, Channel.ServiceKey)] + [InlineData(Forbidden, Channel.OrganisationKey)] + [InlineData(Forbidden, "unknown_channel")] + public async Task GetIdentifiers_Authorization_ReturnsExpectedStatusCode( + HttpStatusCode expectedStatusCode, string channel) + { + var identifier = "test_identifier"; + _lookupIdentifierUseCase.Setup(useCase => useCase.Execute(It.IsAny())) + .ReturnsAsync([new Identifier { Scheme = "SIC", Id = "01230", LegalName = "Acme Ltd" }]); + + var factory = new TestAuthorizationWebApplicationFactory( + channel, serviceCollection: s => s.AddScoped(_ => _lookupIdentifierUseCase.Object)); + + var response = await factory.CreateClient().GetAsync($"/identifiers/{identifier}"); + + response.StatusCode.Should().Be(expectedStatusCode); + } +} \ No newline at end of file diff --git a/Services/CO.CDP.EntityVerification.Tests/Api/TestAuthorizationWebApplicationFactory.cs b/Services/CO.CDP.EntityVerification.Tests/Api/TestAuthorizationWebApplicationFactory.cs new file mode 100644 index 000000000..873c9c738 --- /dev/null +++ b/Services/CO.CDP.EntityVerification.Tests/Api/TestAuthorizationWebApplicationFactory.cs @@ -0,0 +1,49 @@ +using Microsoft.AspNetCore.Authentication; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Authorization.Policy; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc.Testing; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; +using System.Security.Claims; + +namespace CO.CDP.EntityVerification.Tests.Api; + +public class TestAuthorizationWebApplicationFactory( + string channel, + Action? serviceCollection = null) + : WebApplicationFactory where TProgram : class +{ + protected override IHost CreateHost(IHostBuilder builder) + { + if (serviceCollection != null) builder.ConfigureServices(serviceCollection); + + builder.ConfigureServices(services => + { + services.AddTransient(sp => new AuthorizationPolicyEvaluator( + ActivatorUtilities.CreateInstance(sp), channel)); + }); + + return base.CreateHost(builder); + } +} + +public class AuthorizationPolicyEvaluator(PolicyEvaluator innerEvaluator, string? channel) : IPolicyEvaluator +{ + const string JwtBearerScheme = "Bearer"; + + public async Task AuthenticateAsync(AuthorizationPolicy policy, HttpContext context) + { + var claimsIdentity = new ClaimsIdentity(JwtBearerScheme); + if (!string.IsNullOrWhiteSpace(channel)) claimsIdentity.AddClaims([new Claim("channel", channel)]); + + return await Task.FromResult(AuthenticateResult.Success(new AuthenticationTicket(new ClaimsPrincipal(claimsIdentity), + new AuthenticationProperties(), JwtBearerScheme))); + } + + public Task AuthorizeAsync( + AuthorizationPolicy policy, AuthenticateResult authenticationResult, HttpContext context, object? resource) + { + return innerEvaluator.AuthorizeAsync(policy, authenticationResult, context, resource); + } +} \ No newline at end of file diff --git a/Services/CO.CDP.EntityVerification/Api/PponEndpointExtensions.cs b/Services/CO.CDP.EntityVerification/Api/PponEndpointExtensions.cs index f5b0de893..dc52fa8c8 100644 --- a/Services/CO.CDP.EntityVerification/Api/PponEndpointExtensions.cs +++ b/Services/CO.CDP.EntityVerification/Api/PponEndpointExtensions.cs @@ -4,6 +4,7 @@ using CO.CDP.Swashbuckle.Filter; using CO.CDP.Swashbuckle.Security; using DotSwashbuckle.AspNetCore.SwaggerGen; +using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using Microsoft.OpenApi.Models; @@ -14,7 +15,8 @@ public static class PponEndpointExtensions public static void UsePponEndpoints(this WebApplication app) { app.MapGet("/identifiers/{identifier}", - async (string identifier, IUseCase> useCase) => + [Authorize(Policy = "OneLoginPolicy")] + async (string identifier, IUseCase> useCase) => await useCase.Execute(new LookupIdentifierQuery(identifier)) .AndThen(identifier => identifier.Any() ? Results.Ok(identifier) : Results.NotFound())) .Produces>(StatusCodes.Status200OK, "application/json") diff --git a/Services/CO.CDP.Forms.WebApi.Tests/Api/DeleteFormAnswerSetEndpointTest.cs b/Services/CO.CDP.Forms.WebApi.Tests/Api/DeleteFormAnswerSetEndpointTest.cs index 12d7c48ef..7b9dd0f67 100644 --- a/Services/CO.CDP.Forms.WebApi.Tests/Api/DeleteFormAnswerSetEndpointTest.cs +++ b/Services/CO.CDP.Forms.WebApi.Tests/Api/DeleteFormAnswerSetEndpointTest.cs @@ -5,6 +5,7 @@ using Microsoft.Extensions.Hosting; using Moq; using System.Net; +using static CO.CDP.Authentication.Constants; using static System.Net.HttpStatusCode; namespace CO.CDP.Forms.WebApi.Tests.Api; @@ -36,4 +37,28 @@ public async Task DeleteSupplierInformation_TestCases(bool useCaseResult, HttpSt response.StatusCode.Should().Be(expectedStatusCode); } + + [Theory] + [InlineData(NoContent, Channel.OneLogin, OrganisationPersonScope.Admin)] + [InlineData(NoContent, Channel.OneLogin, OrganisationPersonScope.Editor)] + [InlineData(Forbidden, Channel.OneLogin, OrganisationPersonScope.Viewer)] + [InlineData(Forbidden, Channel.ServiceKey)] + [InlineData(Forbidden, Channel.OrganisationKey)] + [InlineData(Forbidden, "unknown_channel")] + public async Task GetFormSectionQuestions_Authorization_ReturnsExpectedStatusCode( + HttpStatusCode expectedStatusCode, string channel, string? scope = null) + { + var answerSetId = Guid.NewGuid(); + var organisationId = Guid.NewGuid(); + var command = (organisationId, answerSetId); + + _deleteAnswerSetUseCase.Setup(uc => uc.Execute(command)).ReturnsAsync(true); + + var factory = new TestAuthorizationWebApplicationFactory( + channel, organisationId, scope, serviceCollection: s => s.AddScoped(_ => _deleteAnswerSetUseCase.Object)); + + var response = await factory.CreateClient().DeleteAsync($"/forms/answer-sets/{answerSetId}?organisation-id={organisationId}"); + + response.StatusCode.Should().Be(expectedStatusCode); + } } \ No newline at end of file diff --git a/Services/CO.CDP.Forms.WebApi.Tests/Api/GetFormSectionQuestionsTest.cs b/Services/CO.CDP.Forms.WebApi.Tests/Api/GetFormSectionQuestionsTest.cs index a4396b4a4..70251abcc 100644 --- a/Services/CO.CDP.Forms.WebApi.Tests/Api/GetFormSectionQuestionsTest.cs +++ b/Services/CO.CDP.Forms.WebApi.Tests/Api/GetFormSectionQuestionsTest.cs @@ -5,6 +5,8 @@ using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; using Moq; +using System.Net; +using static CO.CDP.Authentication.Constants; using static System.Net.HttpStatusCode; namespace CO.CDP.Forms.WebApi.Tests.Api; @@ -19,7 +21,7 @@ public GetFormSectionQuestionsTest() TestWebApplicationFactory factory = new(builder => { builder.ConfigureServices(services => - services.AddScoped>(_ => _getFormSectionQuestionsUseCase.Object) + services.AddScoped(_ => _getFormSectionQuestionsUseCase.Object) ); }); _httpClient = factory.CreateClient(); @@ -59,6 +61,32 @@ public async Task ItFindsTheFormSectionWithQuestions() await response.Should().HaveContent(sectionQuestionsResponse); } + [Theory] + [InlineData(OK, Channel.OneLogin, OrganisationPersonScope.Admin)] + [InlineData(OK, Channel.OneLogin, OrganisationPersonScope.Editor)] + [InlineData(OK, Channel.OneLogin, OrganisationPersonScope.Viewer)] + [InlineData(Forbidden, Channel.ServiceKey)] + [InlineData(Forbidden, Channel.OrganisationKey)] + [InlineData(Forbidden, "unknown_channel")] + public async Task GetFormSectionQuestions_Authorization_ReturnsExpectedStatusCode( + HttpStatusCode expectedStatusCode, string channel, string? scope = null) + { + var formId = Guid.NewGuid(); + var sectionId = Guid.NewGuid(); + var organisationId = Guid.NewGuid(); + var command = (formId, sectionId, organisationId); + + _getFormSectionQuestionsUseCase.Setup(uc => uc.Execute(command)) + .ReturnsAsync(new SectionQuestionsResponse()); + + var factory = new TestAuthorizationWebApplicationFactory( + channel, organisationId, scope, serviceCollection: s => s.AddScoped(_ => _getFormSectionQuestionsUseCase.Object)); + + var response = await factory.CreateClient().GetAsync($"/forms/{formId}/sections/{sectionId}/questions?organisation-id={organisationId}"); + + response.StatusCode.Should().Be(expectedStatusCode); + } + private static SectionQuestionsResponse GivenSectionQuestionsResponse() { var question1 = Guid.NewGuid(); diff --git a/Services/CO.CDP.Forms.WebApi.Tests/Api/GetFormSectionstEndpointTest.cs b/Services/CO.CDP.Forms.WebApi.Tests/Api/GetFormSectionstEndpointTest.cs index 7c07e9617..68c1edbf3 100644 --- a/Services/CO.CDP.Forms.WebApi.Tests/Api/GetFormSectionstEndpointTest.cs +++ b/Services/CO.CDP.Forms.WebApi.Tests/Api/GetFormSectionstEndpointTest.cs @@ -5,7 +5,9 @@ using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; using Moq; +using System.Net; using System.Net.Http.Json; +using static CO.CDP.Authentication.Constants; using static System.Net.HttpStatusCode; namespace CO.CDP.Forms.WebApi.Tests.Api; @@ -58,4 +60,27 @@ public async Task GetFormSections_WhenFormIdExists_ShouldReturnOk() response.Should().BeEquivalentTo(formSections); } + + [Theory] + [InlineData(OK, Channel.OneLogin)] + [InlineData(Forbidden, Channel.ServiceKey)] + [InlineData(Forbidden, Channel.OrganisationKey)] + [InlineData(Forbidden, "unknown_channel")] + public async Task GetFormSections_Authorization_ReturnsExpectedStatusCode( + HttpStatusCode expectedStatusCode, string channel) + { + var formId = Guid.NewGuid(); + var organisationId = Guid.NewGuid(); + var command = (formId, organisationId); + + _useCase.Setup(uc => uc.Execute(command)) + .ReturnsAsync(new FormSectionResponse { FormSections = [] }); + + var factory = new TestAuthorizationWebApplicationFactory( + channel, serviceCollection: s => s.AddScoped(_ => _useCase.Object)); + + var response = await factory.CreateClient().GetAsync($"/forms/{formId}/sections?organisation-id={organisationId}"); + + response.StatusCode.Should().Be(expectedStatusCode); + } } \ No newline at end of file diff --git a/Services/CO.CDP.Forms.WebApi.Tests/Api/PutFormSectionAnswersTest.cs b/Services/CO.CDP.Forms.WebApi.Tests/Api/PutFormSectionAnswersTest.cs new file mode 100644 index 000000000..61e8bc9da --- /dev/null +++ b/Services/CO.CDP.Forms.WebApi.Tests/Api/PutFormSectionAnswersTest.cs @@ -0,0 +1,45 @@ +using CO.CDP.Forms.WebApi.Model; +using CO.CDP.Forms.WebApi.UseCase; +using CO.CDP.TestKit.Mvc; +using FluentAssertions; +using Microsoft.Extensions.DependencyInjection; +using Moq; +using System.Net; +using System.Net.Http.Json; +using static CO.CDP.Authentication.Constants; +using static System.Net.HttpStatusCode; + +namespace CO.CDP.Forms.WebApi.Tests.Api; + +public class PutFormSectionAnswersTest +{ + private readonly Mock> _updateFormSectionAnswersUseCase = new(); + + [Theory] + [InlineData(NoContent, Channel.OneLogin, OrganisationPersonScope.Admin)] + [InlineData(NoContent, Channel.OneLogin, OrganisationPersonScope.Editor)] + [InlineData(Forbidden, Channel.OneLogin, OrganisationPersonScope.Viewer)] + [InlineData(Forbidden, Channel.ServiceKey)] + [InlineData(Forbidden, Channel.OrganisationKey)] + [InlineData(Forbidden, "unknown_channel")] + public async Task GetFormSectionQuestions_Authorization_ReturnsExpectedStatusCode( + HttpStatusCode expectedStatusCode, string channel, string? scope = null) + { + var formId = Guid.NewGuid(); + var sectionId = Guid.NewGuid(); + var answerSetId = Guid.NewGuid(); + var organisationId = Guid.NewGuid(); + var updateFormSectionAnswers = new UpdateFormSectionAnswers(); + var command = (formId, sectionId, answerSetId, organisationId, updateFormSectionAnswers); + + _updateFormSectionAnswersUseCase.Setup(uc => uc.Execute(command)).ReturnsAsync(true); + + var factory = new TestAuthorizationWebApplicationFactory( + channel, organisationId, scope, serviceCollection: s => s.AddScoped(_ => _updateFormSectionAnswersUseCase.Object)); + + var response = await factory.CreateClient().PutAsJsonAsync( + $"/forms/{formId}/sections/{sectionId}/answers/{answerSetId}?organisation-id={organisationId}", updateFormSectionAnswers); + + response.StatusCode.Should().Be(expectedStatusCode); + } +} diff --git a/Services/CO.CDP.Forms.WebApi/Api/Forms.cs b/Services/CO.CDP.Forms.WebApi/Api/Forms.cs index c686a5a4f..9b57339d2 100644 --- a/Services/CO.CDP.Forms.WebApi/Api/Forms.cs +++ b/Services/CO.CDP.Forms.WebApi/Api/Forms.cs @@ -1,3 +1,4 @@ +using CO.CDP.Authentication.Authorization; using CO.CDP.Forms.WebApi.Model; using CO.CDP.Forms.WebApi.UseCase; using CO.CDP.Functional; @@ -9,6 +10,7 @@ using Microsoft.AspNetCore.Mvc; using Microsoft.OpenApi.Models; using System.Reflection; +using static CO.CDP.Authentication.Constants; namespace CO.CDP.Forms.WebApi.Api; @@ -17,7 +19,8 @@ public static class EndpointExtensions public static void UseFormsEndpoints(this WebApplication app) { app.MapGet("/forms/{formId}/sections", - async (Guid formId, [FromQuery(Name = "organisation-id")] Guid organisationId, IUseCase<(Guid, Guid), FormSectionResponse?> useCase) => + [OrganisationAuthorize([AuthenticationChannel.OneLogin])] + async (Guid formId, [FromQuery(Name = "organisation-id")] Guid organisationId, IUseCase<(Guid, Guid), FormSectionResponse?> useCase) => await useCase.Execute((formId, organisationId)) .AndThen(res => res != null ? Results.Ok(res) : Results.NotFound())) .Produces(StatusCodes.Status200OK, "application/json") @@ -37,7 +40,11 @@ await useCase.Execute((formId, organisationId)) }); app.MapGet("/forms/{formId}/sections/{sectionId}/questions", - async (Guid formId, Guid sectionId, [FromQuery(Name = "organisation-id")] Guid organisationId, IUseCase<(Guid, Guid, Guid), SectionQuestionsResponse?> useCase) => + [OrganisationAuthorize( + [AuthenticationChannel.OneLogin], + [OrganisationPersonScope.Admin, OrganisationPersonScope.Editor, OrganisationPersonScope.Viewer], + OrganisationIdLocation.QueryString)] + async (Guid formId, Guid sectionId, [FromQuery(Name = "organisation-id")] Guid organisationId, IUseCase<(Guid, Guid, Guid), SectionQuestionsResponse?> useCase) => await useCase.Execute((formId, sectionId, organisationId)) .AndThen(sectionQuestions => sectionQuestions != null ? Results.Ok(sectionQuestions) : Results.NotFound())) .Produces(StatusCodes.Status200OK, "application/json") @@ -56,8 +63,12 @@ await useCase.Execute((formId, sectionId, organisationId)) return operation; }); - app.MapPut("/forms/{formId}/sections/{sectionId}/answers/{answerSetId}", async ( - Guid formId, Guid sectionId, Guid answerSetId, + app.MapPut("/forms/{formId}/sections/{sectionId}/answers/{answerSetId}", + [OrganisationAuthorize( + [AuthenticationChannel.OneLogin], + [OrganisationPersonScope.Admin, OrganisationPersonScope.Editor], + OrganisationIdLocation.QueryString)] + async (Guid formId, Guid sectionId, Guid answerSetId, [FromQuery(Name = "organisation-id")] Guid organisationId, [FromBody] UpdateFormSectionAnswers updateFormSectionAnswers, IUseCase<(Guid formId, Guid sectionId, Guid answerSetId, Guid organisationId, UpdateFormSectionAnswers updateFormSectionAnswers), bool> updateFormSectionAnswersUseCase) => @@ -82,7 +93,11 @@ await useCase.Execute((formId, sectionId, organisationId)) }); app.MapDelete("/forms/answer-sets/{answerSetId}", - async (Guid answerSetId, [FromQuery(Name = "organisation-id")] Guid organisationId, IUseCase<(Guid, Guid), bool> useCase) => + [OrganisationAuthorize( + [AuthenticationChannel.OneLogin], + [OrganisationPersonScope.Admin, OrganisationPersonScope.Editor], + OrganisationIdLocation.QueryString)] + async (Guid answerSetId, [FromQuery(Name = "organisation-id")] Guid organisationId, IUseCase<(Guid, Guid), bool> useCase) => await useCase.Execute((organisationId, answerSetId)) .AndThen(success => success ? Results.NoContent() : Results.NotFound())) .Produces(StatusCodes.Status204NoContent) diff --git a/Services/CO.CDP.Person.WebApi.Tests/Api/GetPersonTest.cs b/Services/CO.CDP.Person.WebApi.Tests/Api/GetPersonTest.cs new file mode 100644 index 000000000..dc2b4f319 --- /dev/null +++ b/Services/CO.CDP.Person.WebApi.Tests/Api/GetPersonTest.cs @@ -0,0 +1,62 @@ +using CO.CDP.OrganisationInformation.Persistence; +using CO.CDP.Person.WebApi.Model; +using CO.CDP.Person.WebApi.UseCase; +using CO.CDP.TestKit.Mvc; +using FluentAssertions; +using Microsoft.Extensions.DependencyInjection; +using Moq; +using System.Net; +using System.Net.Http.Json; +using static CO.CDP.Authentication.Constants; +using static System.Net.HttpStatusCode; + +namespace CO.CDP.Person.WebApi.Tests.Api; + +public class GetPersonTest +{ + private readonly Mock> _getPersonUseCase = new(); + private readonly Mock> _claimPersonInviteUseCase = new(); + + [Theory] + [InlineData(OK, Channel.OneLogin)] + [InlineData(Forbidden, Channel.ServiceKey)] + [InlineData(Forbidden, Channel.OrganisationKey)] + [InlineData(Forbidden, "unknown_channel")] + public async Task GetSharedData_Authorization_ReturnsExpectedStatusCode( + HttpStatusCode expectedStatusCode, string channel) + { + var personId = Guid.NewGuid(); + _getPersonUseCase.Setup(useCase => useCase.Execute(personId)) + .ReturnsAsync(new Model.Person { Id = personId, FirstName = "fn", LastName = "ln", Email = "fn.ln@test" }); + + var factory = new TestAuthorizationWebApplicationFactory( + channel, serviceCollection: s => s.AddScoped(_ => _getPersonUseCase.Object)); + + var response = await factory.CreateClient().GetAsync($"/persons/{personId}"); + + response.StatusCode.Should().Be(expectedStatusCode); + } + + [Theory] + [InlineData(NoContent, Channel.OneLogin)] + [InlineData(Forbidden, Channel.ServiceKey)] + [InlineData(Forbidden, Channel.OrganisationKey)] + [InlineData(Forbidden, "unknown_channel")] + public async Task ClaimPersonInvite_Authorization_ReturnsExpectedStatusCode( + HttpStatusCode expectedStatusCode, string channel) + { + var personId = Guid.NewGuid(); + var claimPersonInvite = new ClaimPersonInvite { PersonInviteId = Guid.NewGuid() }; + var command = (personId, claimPersonInvite); + + _claimPersonInviteUseCase.Setup(useCase => useCase.Execute(command)) + .ReturnsAsync(Mock.Of()); + + var factory = new TestAuthorizationWebApplicationFactory( + channel, serviceCollection: s => s.AddScoped(_ => _claimPersonInviteUseCase.Object)); + + var response = await factory.CreateClient().PostAsJsonAsync($"/persons/{personId}/claim-person-invite", claimPersonInvite); + + response.StatusCode.Should().Be(expectedStatusCode); + } +} \ No newline at end of file diff --git a/Services/CO.CDP.Person.WebApi/Api/Person.cs b/Services/CO.CDP.Person.WebApi/Api/Person.cs index eb020c63d..30e6dd150 100644 --- a/Services/CO.CDP.Person.WebApi/Api/Person.cs +++ b/Services/CO.CDP.Person.WebApi/Api/Person.cs @@ -1,3 +1,4 @@ +using CO.CDP.Authentication.Authorization; using CO.CDP.Functional; using CO.CDP.OrganisationInformation.Persistence; using CO.CDP.Person.WebApi.Model; @@ -49,7 +50,9 @@ await useCase.Execute(command) return operation; }); - app.MapGet("/persons/{personId}", async (Guid personId, IUseCase useCase) => + app.MapGet("/persons/{personId}", + [OrganisationAuthorize([AuthenticationChannel.OneLogin])] + async (Guid personId, IUseCase useCase) => await useCase.Execute(personId) .AndThen(person => person != null ? Results.Ok(person) : Results.NotFound())) .Produces(200, "application/json") @@ -65,11 +68,11 @@ await useCase.Execute(personId) }); app.MapPost("/persons/{personId}/claim-person-invite", - async (Guid personId, ClaimPersonInvite command, IUseCase<(Guid, ClaimPersonInvite), PersonInvite> useCase) => + [OrganisationAuthorize([AuthenticationChannel.OneLogin])] + async (Guid personId, ClaimPersonInvite command, IUseCase<(Guid, ClaimPersonInvite), PersonInvite> useCase) => await useCase.Execute((personId, command)) .AndThen(_ => Results.NoContent()) ) - .Produces(StatusCodes.Status200OK) .Produces(StatusCodes.Status204NoContent) .ProducesProblem(StatusCodes.Status400BadRequest) .Produces(StatusCodes.Status401Unauthorized) @@ -81,7 +84,6 @@ await useCase.Execute((personId, command)) operation.OperationId = "ClaimPersonInvite"; operation.Description = "Claims a person invite."; operation.Summary = "Claims a person invite."; - operation.Responses["200"].Description = "Person invite claimed successfully."; operation.Responses["204"].Description = "Person invite claimed successfully."; operation.Responses["400"].Description = "Bad request."; operation.Responses["401"].Description = "Valid authentication credentials are missing in the request."; @@ -114,6 +116,7 @@ await useCase.Execute((personId, command)) operation.Responses["200"].Description = "Person updated."; return operation; }); + app.MapDelete("/persons/{personId}", (Guid personId) => { _persons.Remove(personId); diff --git a/Services/CO.CDP.Person.WebApi/UseCase/GetPersonUseCase.cs b/Services/CO.CDP.Person.WebApi/UseCase/GetPersonUseCase.cs index adb1686c3..934dcde36 100644 --- a/Services/CO.CDP.Person.WebApi/UseCase/GetPersonUseCase.cs +++ b/Services/CO.CDP.Person.WebApi/UseCase/GetPersonUseCase.cs @@ -6,9 +6,9 @@ namespace CO.CDP.Person.WebApi.UseCase; public class GetPersonUseCase(IPersonRepository personRepository, IMapper mapper) : IUseCase { - public async Task Execute(Guid tenantId) + public async Task Execute(Guid personId) { - return await personRepository.Find(tenantId) + return await personRepository.Find(personId) .AndThen(mapper.Map); } } \ No newline at end of file diff --git a/Services/CO.CDP.Tenant.WebApi.Tests/Api/TenantLookupTest.cs b/Services/CO.CDP.Tenant.WebApi.Tests/Api/TenantLookupTest.cs index b70cd39b0..5a08477c1 100644 --- a/Services/CO.CDP.Tenant.WebApi.Tests/Api/TenantLookupTest.cs +++ b/Services/CO.CDP.Tenant.WebApi.Tests/Api/TenantLookupTest.cs @@ -5,7 +5,9 @@ using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; using Moq; +using System.Net; using System.Net.Http.Json; +using static CO.CDP.Authentication.Constants; using static System.Net.HttpStatusCode; namespace CO.CDP.Tenant.WebApi.Tests.Api; @@ -29,9 +31,8 @@ public TenantLookupTest() [Fact] public async Task IfNoTenantIsFound_ReturnsNotFound() { - _getTenantUseCase.Setup(useCase => useCase.Execute()) - .Returns(Task.FromResult(null)); + .ReturnsAsync((TenantLookup?)null); var response = await _httpClient.GetAsync($"/tenant/lookup"); @@ -44,14 +45,33 @@ public async Task IfTenantIsFound_ReturnsTenant() var lookup = GivenTenantLookup(); _getTenantUseCase.Setup(useCase => useCase.Execute()) - .Returns(Task.FromResult(lookup)); + .ReturnsAsync(lookup); - var response = await _httpClient.GetAsync($"/tenant/lookup"); + var response = await _httpClient.GetAsync("/tenant/lookup"); response.Should().HaveStatusCode(OK); (await response.Content.ReadFromJsonAsync()).Should().BeEquivalentTo(lookup); } + [Theory] + [InlineData(OK, Channel.OneLogin)] + [InlineData(Forbidden, Channel.ServiceKey)] + [InlineData(Forbidden, Channel.OrganisationKey)] + [InlineData(Forbidden, "unknown_channel")] + public async Task GetSharedData_Authorization_ReturnsExpectedStatusCode( + HttpStatusCode expectedStatusCode, string channel) + { + _getTenantUseCase.Setup(useCase => useCase.Execute()) + .ReturnsAsync(GivenTenantLookup()); + + var factory = new TestAuthorizationWebApplicationFactory( + channel, serviceCollection: s => s.AddScoped(_ => _getTenantUseCase.Object)); + + var response = await factory.CreateClient().GetAsync("/tenant/lookup"); + + response.StatusCode.Should().Be(expectedStatusCode); + } + private static TenantLookup GivenTenantLookup() { return new TenantLookup diff --git a/Services/CO.CDP.Tenant.WebApi/Api/Tenant.cs b/Services/CO.CDP.Tenant.WebApi/Api/Tenant.cs index 49c1b4f39..dc7c9682b 100644 --- a/Services/CO.CDP.Tenant.WebApi/Api/Tenant.cs +++ b/Services/CO.CDP.Tenant.WebApi/Api/Tenant.cs @@ -1,3 +1,4 @@ +using CO.CDP.Authentication.Authorization; using CO.CDP.Functional; using CO.CDP.OrganisationInformation; using CO.CDP.Swashbuckle.Filter; @@ -39,6 +40,7 @@ await useCase.Execute(command) operation.Responses["500"].Description = "Internal server error."; return operation; }); + app.MapGet("/tenants/{tenantId}", async (Guid tenantId, IUseCase useCase) => await useCase.Execute(tenantId) .AndThen(tenant => tenant != null ? Results.Ok(tenant) : Results.NotFound())) @@ -64,7 +66,8 @@ public static void UseTenantLookupEndpoints(this WebApplication app) var openApiTags = new List { new() { Name = "Tenant Lookup" } }; app.MapGet("/tenant/lookup", - async (IUseCase useCase) => + [OrganisationAuthorize([AuthenticationChannel.OneLogin])] + async (IUseCase useCase) => await useCase.Execute() .AndThen(tenant => tenant != null ? Results.Ok(tenant) : Results.NotFound())) .Produces(StatusCodes.Status200OK, "application/json")