1
0
mirror of https://github.com/bitwarden/server.git synced 2025-04-05 13:08:17 -05:00

[SG-841] Refactor GetOrganizationApiKeyCommand (#2436)

* Renamed and split up class to only query for an organization key

* Added a command class to create an organization api key

* Updated service registration and controller to include new changes

* Updated test cases to reflect refactor

* fixed lint issues

* Fixed PR comment
This commit is contained in:
Gbubemi Smith 2022-11-28 17:39:09 -07:00 committed by GitHub
parent 5bcacf785f
commit f74730dd2f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 97 additions and 55 deletions

View File

@ -33,8 +33,9 @@ public class OrganizationsController : Controller
private readonly ICurrentContext _currentContext; private readonly ICurrentContext _currentContext;
private readonly ISsoConfigRepository _ssoConfigRepository; private readonly ISsoConfigRepository _ssoConfigRepository;
private readonly ISsoConfigService _ssoConfigService; private readonly ISsoConfigService _ssoConfigService;
private readonly IGetOrganizationApiKeyCommand _getOrganizationApiKeyCommand; private readonly IGetOrganizationApiKeyQuery _getOrganizationApiKeyQuery;
private readonly IRotateOrganizationApiKeyCommand _rotateOrganizationApiKeyCommand; private readonly IRotateOrganizationApiKeyCommand _rotateOrganizationApiKeyCommand;
private readonly ICreateOrganizationApiKeyCommand _createOrganizationApiKeyCommand;
private readonly IOrganizationApiKeyRepository _organizationApiKeyRepository; private readonly IOrganizationApiKeyRepository _organizationApiKeyRepository;
private readonly GlobalSettings _globalSettings; private readonly GlobalSettings _globalSettings;
@ -48,8 +49,9 @@ public class OrganizationsController : Controller
ICurrentContext currentContext, ICurrentContext currentContext,
ISsoConfigRepository ssoConfigRepository, ISsoConfigRepository ssoConfigRepository,
ISsoConfigService ssoConfigService, ISsoConfigService ssoConfigService,
IGetOrganizationApiKeyCommand getOrganizationApiKeyCommand, IGetOrganizationApiKeyQuery getOrganizationApiKeyQuery,
IRotateOrganizationApiKeyCommand rotateOrganizationApiKeyCommand, IRotateOrganizationApiKeyCommand rotateOrganizationApiKeyCommand,
ICreateOrganizationApiKeyCommand createOrganizationApiKeyCommand,
IOrganizationApiKeyRepository organizationApiKeyRepository, IOrganizationApiKeyRepository organizationApiKeyRepository,
GlobalSettings globalSettings) GlobalSettings globalSettings)
{ {
@ -62,8 +64,9 @@ public class OrganizationsController : Controller
_currentContext = currentContext; _currentContext = currentContext;
_ssoConfigRepository = ssoConfigRepository; _ssoConfigRepository = ssoConfigRepository;
_ssoConfigService = ssoConfigService; _ssoConfigService = ssoConfigService;
_getOrganizationApiKeyCommand = getOrganizationApiKeyCommand; _getOrganizationApiKeyQuery = getOrganizationApiKeyQuery;
_rotateOrganizationApiKeyCommand = rotateOrganizationApiKeyCommand; _rotateOrganizationApiKeyCommand = rotateOrganizationApiKeyCommand;
_createOrganizationApiKeyCommand = createOrganizationApiKeyCommand;
_organizationApiKeyRepository = organizationApiKeyRepository; _organizationApiKeyRepository = organizationApiKeyRepository;
_globalSettings = globalSettings; _globalSettings = globalSettings;
} }
@ -514,8 +517,9 @@ public class OrganizationsController : Controller
} }
} }
var organizationApiKey = await _getOrganizationApiKeyCommand var organizationApiKey = await _getOrganizationApiKeyQuery
.GetOrganizationApiKeyAsync(organization.Id, model.Type); .GetOrganizationApiKeyAsync(organization.Id, model.Type) ??
await _createOrganizationApiKeyCommand.CreateAsync(organization.Id, model.Type);
var user = await _userService.GetUserByPrincipalAsync(User); var user = await _userService.GetUserByPrincipalAsync(User);
if (user == null) if (user == null)
@ -565,8 +569,9 @@ public class OrganizationsController : Controller
throw new NotFoundException(); throw new NotFoundException();
} }
var organizationApiKey = await _getOrganizationApiKeyCommand var organizationApiKey = await _getOrganizationApiKeyQuery
.GetOrganizationApiKeyAsync(organization.Id, model.Type); .GetOrganizationApiKeyAsync(organization.Id, model.Type) ??
await _createOrganizationApiKeyCommand.CreateAsync(organization.Id, model.Type);
var user = await _userService.GetUserByPrincipalAsync(User); var user = await _userService.GetUserByPrincipalAsync(User);
if (user == null) if (user == null)

View File

@ -0,0 +1,32 @@
using Bit.Core.Entities;
using Bit.Core.Enums;
using Bit.Core.OrganizationFeatures.OrganizationApiKeys.Interfaces;
using Bit.Core.Repositories;
using Bit.Core.Utilities;
namespace Bit.Core.OrganizationFeatures.OrganizationApiKeys;
public class CreateOrganizationApiKeyCommand : ICreateOrganizationApiKeyCommand
{
private readonly IOrganizationApiKeyRepository _organizationApiKeyRepository;
public CreateOrganizationApiKeyCommand(IOrganizationApiKeyRepository organizationApiKeyRepository)
{
_organizationApiKeyRepository = organizationApiKeyRepository;
}
public async Task<OrganizationApiKey> CreateAsync(Guid organizationId,
OrganizationApiKeyType organizationApiKeyType)
{
var apiKey = new OrganizationApiKey
{
OrganizationId = organizationId,
Type = organizationApiKeyType,
ApiKey = CoreHelpers.SecureRandomString(30),
RevisionDate = DateTime.UtcNow,
};
await _organizationApiKeyRepository.CreateAsync(apiKey);
return apiKey;
}
}

View File

@ -2,15 +2,14 @@
using Bit.Core.Enums; using Bit.Core.Enums;
using Bit.Core.OrganizationFeatures.OrganizationApiKeys.Interfaces; using Bit.Core.OrganizationFeatures.OrganizationApiKeys.Interfaces;
using Bit.Core.Repositories; using Bit.Core.Repositories;
using Bit.Core.Utilities;
namespace Bit.Core.OrganizationFeatures.OrganizationApiKeys; namespace Bit.Core.OrganizationFeatures.OrganizationApiKeys;
public class GetOrganizationApiKeyCommand : IGetOrganizationApiKeyCommand public class GetOrganizationApiKeyQuery : IGetOrganizationApiKeyQuery
{ {
private readonly IOrganizationApiKeyRepository _organizationApiKeyRepository; private readonly IOrganizationApiKeyRepository _organizationApiKeyRepository;
public GetOrganizationApiKeyCommand(IOrganizationApiKeyRepository organizationApiKeyRepository) public GetOrganizationApiKeyQuery(IOrganizationApiKeyRepository organizationApiKeyRepository)
{ {
_organizationApiKeyRepository = organizationApiKeyRepository; _organizationApiKeyRepository = organizationApiKeyRepository;
} }
@ -25,20 +24,6 @@ public class GetOrganizationApiKeyCommand : IGetOrganizationApiKeyCommand
var apiKeys = await _organizationApiKeyRepository var apiKeys = await _organizationApiKeyRepository
.GetManyByOrganizationIdTypeAsync(organizationId, organizationApiKeyType); .GetManyByOrganizationIdTypeAsync(organizationId, organizationApiKeyType);
if (apiKeys == null || !apiKeys.Any())
{
var apiKey = new OrganizationApiKey
{
OrganizationId = organizationId,
Type = organizationApiKeyType,
ApiKey = CoreHelpers.SecureRandomString(30),
RevisionDate = DateTime.UtcNow,
};
await _organizationApiKeyRepository.CreateAsync(apiKey);
return apiKey;
}
// NOTE: Currently we only allow one type of api key per organization // NOTE: Currently we only allow one type of api key per organization
return apiKeys.Single(); return apiKeys.Single();
} }

View File

@ -0,0 +1,9 @@
using Bit.Core.Entities;
using Bit.Core.Enums;
namespace Bit.Core.OrganizationFeatures.OrganizationApiKeys.Interfaces;
public interface ICreateOrganizationApiKeyCommand
{
Task<OrganizationApiKey> CreateAsync(Guid organizationId, OrganizationApiKeyType organizationApiKeyType);
}

View File

@ -3,7 +3,7 @@ using Bit.Core.Enums;
namespace Bit.Core.OrganizationFeatures.OrganizationApiKeys.Interfaces; namespace Bit.Core.OrganizationFeatures.OrganizationApiKeys.Interfaces;
public interface IGetOrganizationApiKeyCommand public interface IGetOrganizationApiKeyQuery
{ {
Task<OrganizationApiKey> GetOrganizationApiKeyAsync(Guid organizationId, OrganizationApiKeyType organizationApiKeyType); Task<OrganizationApiKey> GetOrganizationApiKeyAsync(Guid organizationId, OrganizationApiKeyType organizationApiKeyType);
} }

View File

@ -24,7 +24,7 @@ public static class OrganizationServiceCollectionExtensions
services.AddTokenizers(); services.AddTokenizers();
services.AddOrganizationConnectionCommands(); services.AddOrganizationConnectionCommands();
services.AddOrganizationSponsorshipCommands(globalSettings); services.AddOrganizationSponsorshipCommands(globalSettings);
services.AddOrganizationApiKeyCommands(); services.AddOrganizationApiKeyCommandsQueries();
} }
private static void AddOrganizationConnectionCommands(this IServiceCollection services) private static void AddOrganizationConnectionCommands(this IServiceCollection services)
@ -59,10 +59,11 @@ public static class OrganizationServiceCollectionExtensions
} }
} }
private static void AddOrganizationApiKeyCommands(this IServiceCollection services) private static void AddOrganizationApiKeyCommandsQueries(this IServiceCollection services)
{ {
services.AddScoped<IGetOrganizationApiKeyCommand, GetOrganizationApiKeyCommand>(); services.AddScoped<IGetOrganizationApiKeyQuery, GetOrganizationApiKeyQuery>();
services.AddScoped<IRotateOrganizationApiKeyCommand, RotateOrganizationApiKeyCommand>(); services.AddScoped<IRotateOrganizationApiKeyCommand, RotateOrganizationApiKeyCommand>();
services.AddScoped<ICreateOrganizationApiKeyCommand, CreateOrganizationApiKeyCommand>();
} }
private static void AddTokenizers(this IServiceCollection services) private static void AddTokenizers(this IServiceCollection services)

View File

@ -26,9 +26,10 @@ public class OrganizationsControllerTests : IDisposable
private readonly ISsoConfigRepository _ssoConfigRepository; private readonly ISsoConfigRepository _ssoConfigRepository;
private readonly ISsoConfigService _ssoConfigService; private readonly ISsoConfigService _ssoConfigService;
private readonly IUserService _userService; private readonly IUserService _userService;
private readonly IGetOrganizationApiKeyCommand _getOrganizationApiKeyCommand; private readonly IGetOrganizationApiKeyQuery _getOrganizationApiKeyQuery;
private readonly IRotateOrganizationApiKeyCommand _rotateOrganizationApiKeyCommand; private readonly IRotateOrganizationApiKeyCommand _rotateOrganizationApiKeyCommand;
private readonly IOrganizationApiKeyRepository _organizationApiKeyRepository; private readonly IOrganizationApiKeyRepository _organizationApiKeyRepository;
private readonly ICreateOrganizationApiKeyCommand _createOrganizationApiKeyCommand;
private readonly OrganizationsController _sut; private readonly OrganizationsController _sut;
@ -43,15 +44,16 @@ public class OrganizationsControllerTests : IDisposable
_policyRepository = Substitute.For<IPolicyRepository>(); _policyRepository = Substitute.For<IPolicyRepository>();
_ssoConfigRepository = Substitute.For<ISsoConfigRepository>(); _ssoConfigRepository = Substitute.For<ISsoConfigRepository>();
_ssoConfigService = Substitute.For<ISsoConfigService>(); _ssoConfigService = Substitute.For<ISsoConfigService>();
_getOrganizationApiKeyCommand = Substitute.For<IGetOrganizationApiKeyCommand>(); _getOrganizationApiKeyQuery = Substitute.For<IGetOrganizationApiKeyQuery>();
_rotateOrganizationApiKeyCommand = Substitute.For<IRotateOrganizationApiKeyCommand>(); _rotateOrganizationApiKeyCommand = Substitute.For<IRotateOrganizationApiKeyCommand>();
_organizationApiKeyRepository = Substitute.For<IOrganizationApiKeyRepository>(); _organizationApiKeyRepository = Substitute.For<IOrganizationApiKeyRepository>();
_userService = Substitute.For<IUserService>(); _userService = Substitute.For<IUserService>();
_createOrganizationApiKeyCommand = Substitute.For<ICreateOrganizationApiKeyCommand>();
_sut = new OrganizationsController(_organizationRepository, _organizationUserRepository, _sut = new OrganizationsController(_organizationRepository, _organizationUserRepository,
_policyRepository, _organizationService, _userService, _paymentService, _currentContext, _policyRepository, _organizationService, _userService, _paymentService, _currentContext,
_ssoConfigRepository, _ssoConfigService, _getOrganizationApiKeyCommand, _rotateOrganizationApiKeyCommand, _ssoConfigRepository, _ssoConfigService, _getOrganizationApiKeyQuery, _rotateOrganizationApiKeyCommand,
_organizationApiKeyRepository, _globalSettings); _createOrganizationApiKeyCommand, _organizationApiKeyRepository, _globalSettings);
} }
public void Dispose() public void Dispose()

View File

@ -0,0 +1,27 @@
using Bit.Core.Entities;
using Bit.Core.Enums;
using Bit.Core.OrganizationFeatures.OrganizationApiKeys;
using Bit.Core.Repositories;
using Bit.Test.Common.AutoFixture;
using Bit.Test.Common.AutoFixture.Attributes;
using NSubstitute;
using NSubstitute.ReceivedExtensions;
using Xunit;
namespace Bit.Core.Test.OrganizationFeatures.OrganizationApiKeys;
[SutProviderCustomize]
public class CreateOrganizationApiKeyCommandTest
{
[Theory]
[BitAutoData]
public async Task CreateAsync_CreatesOrganizationApiKey(SutProvider<CreateOrganizationApiKeyCommand> sutProvider,
Guid organizationId, OrganizationApiKeyType keyType)
{
await sutProvider.Sut.CreateAsync(organizationId, keyType);
await sutProvider.GetDependency<IOrganizationApiKeyRepository>().Received(1)
.CreateAsync(Arg.Is<OrganizationApiKey>(o => o.OrganizationId == organizationId
&& o.Type == keyType));
}
}

View File

@ -10,11 +10,11 @@ using Xunit;
namespace Bit.Core.Test.OrganizationFeatures.OrganizationApiKeys; namespace Bit.Core.Test.OrganizationFeatures.OrganizationApiKeys;
[SutProviderCustomize] [SutProviderCustomize]
public class GetOrganizationApiKeyCommandTests public class GetOrganizationApiKeyQueryTests
{ {
[Theory] [Theory]
[BitAutoData] [BitAutoData]
public async Task GetOrganizationApiKey_HasOne_Returns(SutProvider<GetOrganizationApiKeyCommand> sutProvider, public async Task GetOrganizationApiKey_HasOne_Returns(SutProvider<GetOrganizationApiKeyQuery> sutProvider,
Guid id, Guid organizationId, OrganizationApiKeyType keyType) Guid id, Guid organizationId, OrganizationApiKeyType keyType)
{ {
sutProvider.GetDependency<IOrganizationApiKeyRepository>() sutProvider.GetDependency<IOrganizationApiKeyRepository>()
@ -38,7 +38,7 @@ public class GetOrganizationApiKeyCommandTests
[Theory] [Theory]
[BitAutoData] [BitAutoData]
public async Task GetOrganizationApiKey_HasTwo_Throws(SutProvider<GetOrganizationApiKeyCommand> sutProvider, public async Task GetOrganizationApiKey_HasTwo_Throws(SutProvider<GetOrganizationApiKeyQuery> sutProvider,
Guid organizationId, OrganizationApiKeyType keyType) Guid organizationId, OrganizationApiKeyType keyType)
{ {
sutProvider.GetDependency<IOrganizationApiKeyRepository>() sutProvider.GetDependency<IOrganizationApiKeyRepository>()
@ -69,26 +69,7 @@ public class GetOrganizationApiKeyCommandTests
[Theory] [Theory]
[BitAutoData] [BitAutoData]
public async Task GetOrganizationApiKey_HasNone_CreatesAndReturns(SutProvider<GetOrganizationApiKeyCommand> sutProvider, public async Task GetOrganizationApiKey_BadType_Throws(SutProvider<GetOrganizationApiKeyQuery> sutProvider,
Guid organizationId, OrganizationApiKeyType keyType)
{
sutProvider.GetDependency<IOrganizationApiKeyRepository>()
.GetManyByOrganizationIdTypeAsync(organizationId, keyType)
.Returns(Enumerable.Empty<OrganizationApiKey>());
var apiKey = await sutProvider.Sut.GetOrganizationApiKeyAsync(organizationId, keyType);
Assert.NotNull(apiKey);
Assert.Equal(organizationId, apiKey.OrganizationId);
Assert.Equal(keyType, apiKey.Type);
await sutProvider.GetDependency<IOrganizationApiKeyRepository>()
.Received(1)
.CreateAsync(Arg.Any<OrganizationApiKey>());
}
[Theory]
[BitAutoData]
public async Task GetOrganizationApiKey_BadType_Throws(SutProvider<GetOrganizationApiKeyCommand> sutProvider,
Guid organizationId, OrganizationApiKeyType keyType) Guid organizationId, OrganizationApiKeyType keyType)
{ {
keyType = (OrganizationApiKeyType)byte.MaxValue; keyType = (OrganizationApiKeyType)byte.MaxValue;