From 1dc9aad1f115cf921d919f1c5009947215d61a3b Mon Sep 17 00:00:00 2001 From: Colton Hurst Date: Wed, 15 Mar 2023 11:51:01 -0400 Subject: [PATCH] SM-655: Add Authorizations and Tests for the SM Porting Controller (#2802) * SM-655: Add Authorize attribute for secrets on the SM Porting Controller * SM-655: Add access secrets manager check to SM Import and Export * SM-655: Add tests for export and import endpoints --- .../SecretsManagerPortingController.cs | 6 +- .../SecretsManagerPortingControllerTest.cs | 79 +++++++++++++++++++ 2 files changed, 83 insertions(+), 2 deletions(-) create mode 100644 test/Api.IntegrationTest/SecretsManager/Controllers/SecretsManagerPortingControllerTest.cs diff --git a/src/Api/SecretsManager/Controllers/SecretsManagerPortingController.cs b/src/Api/SecretsManager/Controllers/SecretsManagerPortingController.cs index e077d67cb..86e3ed182 100644 --- a/src/Api/SecretsManager/Controllers/SecretsManagerPortingController.cs +++ b/src/Api/SecretsManager/Controllers/SecretsManagerPortingController.cs @@ -6,11 +6,13 @@ using Bit.Core.Exceptions; using Bit.Core.SecretsManager.Commands.Porting.Interfaces; using Bit.Core.SecretsManager.Repositories; using Bit.Core.Services; +using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; namespace Bit.Api.SecretsManager.Controllers; [SecretsManager] +[Authorize("secrets")] public class SecretsManagerPortingController : Controller { private readonly ISecretRepository _secretRepository; @@ -31,7 +33,7 @@ public class SecretsManagerPortingController : Controller [HttpGet("sm/{organizationId}/export")] public async Task Export([FromRoute] Guid organizationId, [FromRoute] string format = "json") { - if (!await _currentContext.OrganizationAdmin(organizationId)) + if (!await _currentContext.OrganizationAdmin(organizationId) || !_currentContext.AccessSecretsManager(organizationId)) { throw new NotFoundException(); } @@ -51,7 +53,7 @@ public class SecretsManagerPortingController : Controller [HttpPost("sm/{organizationId}/import")] public async Task Import([FromRoute] Guid organizationId, [FromBody] SMImportRequestModel importRequest) { - if (!await _currentContext.OrganizationAdmin(organizationId)) + if (!await _currentContext.OrganizationAdmin(organizationId) || !_currentContext.AccessSecretsManager(organizationId)) { throw new NotFoundException(); } diff --git a/test/Api.IntegrationTest/SecretsManager/Controllers/SecretsManagerPortingControllerTest.cs b/test/Api.IntegrationTest/SecretsManager/Controllers/SecretsManagerPortingControllerTest.cs new file mode 100644 index 000000000..49d132125 --- /dev/null +++ b/test/Api.IntegrationTest/SecretsManager/Controllers/SecretsManagerPortingControllerTest.cs @@ -0,0 +1,79 @@ +using System.Net; +using System.Net.Http.Headers; +using Bit.Api.IntegrationTest.Factories; +using Bit.Api.SecretsManager.Models.Request; +using Bit.Core.SecretsManager.Repositories; +using Xunit; + +namespace Bit.Api.IntegrationTest.SecretsManager.Controllers; + +public class SecretsManagerPortingControllerTest : IClassFixture, IAsyncLifetime +{ + private readonly string _mockEncryptedString = + "2.3Uk+WNBIoU5xzmVFNcoWzz==|1MsPIYuRfdOHfu/0uY6H2Q==|/98sp4wb6pHP1VTZ9JcNCYgQjEUMFPlqJgCwRk1YXKg="; + + private readonly HttpClient _client; + private readonly ApiApplicationFactory _factory; + private readonly IProjectRepository _projectRepository; + private readonly IAccessPolicyRepository _accessPolicyRepository; + + private string _email = null!; + private SecretsManagerOrganizationHelper _organizationHelper = null!; + + public SecretsManagerPortingControllerTest(ApiApplicationFactory factory) + { + _factory = factory; + _client = _factory.CreateClient(); + _projectRepository = _factory.GetService(); + _accessPolicyRepository = _factory.GetService(); + } + + public async Task InitializeAsync() + { + _email = $"integration-test{Guid.NewGuid()}@bitwarden.com"; + await _factory.LoginWithNewAccount(_email); + _organizationHelper = new SecretsManagerOrganizationHelper(_factory, _email); + } + + public Task DisposeAsync() + { + _client.Dispose(); + return Task.CompletedTask; + } + + private async Task LoginAsync(string email) + { + var tokens = await _factory.LoginAsync(email); + _client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", tokens.Token); + } + + [Theory] + [InlineData(false, false)] + [InlineData(true, false)] + [InlineData(false, true)] + public async Task Import_SmNotEnabled_NotFound(bool useSecrets, bool accessSecrets) + { + var (org, _) = await _organizationHelper.Initialize(useSecrets, accessSecrets); + await LoginAsync(_email); + + var projectsList = new List(); + var secretsList = new List(); + var request = new SMImportRequestModel { Projects = projectsList, Secrets = secretsList }; + + var response = await _client.PostAsJsonAsync($"sm/{org.Id}/import", request); + Assert.Equal(HttpStatusCode.NotFound, response.StatusCode); + } + + [Theory] + [InlineData(false, false)] + [InlineData(true, false)] + [InlineData(false, true)] + public async Task Export_SmNotEnabled_NotFound(bool useSecrets, bool accessSecrets) + { + var (org, _) = await _organizationHelper.Initialize(useSecrets, accessSecrets); + await LoginAsync(_email); + + var response = await _client.GetAsync($"sm/{org.Id}/export"); + Assert.Equal(HttpStatusCode.NotFound, response.StatusCode); + } +}