1
0
mirror of https://github.com/bitwarden/server.git synced 2024-11-21 12:05:42 +01:00

[PM-13722] Refactor ValidateOrganizationsDomainAsync (#4905)

Refactored ValidateOrganizationsDomainAsync to use VerifyOrganizationDomainAsync
This commit is contained in:
Jared McCannon 2024-10-18 07:45:34 -05:00 committed by GitHub
parent 1d3188d3f5
commit 4fec7cadb7
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
8 changed files with 109 additions and 75 deletions

View File

@ -101,7 +101,7 @@ public class OrganizationDomainController : Controller
throw new NotFoundException(); throw new NotFoundException();
} }
organizationDomain = await _verifyOrganizationDomainCommand.VerifyOrganizationDomainAsync(organizationDomain); organizationDomain = await _verifyOrganizationDomainCommand.UserVerifyOrganizationDomainAsync(organizationDomain);
return new OrganizationDomainResponseModel(organizationDomain); return new OrganizationDomainResponseModel(organizationDomain);
} }

View File

@ -6,7 +6,6 @@ using Bit.Core.Repositories;
using Bit.Core.Services; using Bit.Core.Services;
using Bit.Core.Settings; using Bit.Core.Settings;
using Bit.Core.Utilities; using Bit.Core.Utilities;
using Microsoft.Extensions.Logging;
namespace Bit.Core.AdminConsole.OrganizationFeatures.OrganizationDomains; namespace Bit.Core.AdminConsole.OrganizationFeatures.OrganizationDomains;
@ -14,21 +13,15 @@ public class CreateOrganizationDomainCommand : ICreateOrganizationDomainCommand
{ {
private readonly IOrganizationDomainRepository _organizationDomainRepository; private readonly IOrganizationDomainRepository _organizationDomainRepository;
private readonly IEventService _eventService; private readonly IEventService _eventService;
private readonly IDnsResolverService _dnsResolverService;
private readonly ILogger<VerifyOrganizationDomainCommand> _logger;
private readonly IGlobalSettings _globalSettings; private readonly IGlobalSettings _globalSettings;
public CreateOrganizationDomainCommand( public CreateOrganizationDomainCommand(
IOrganizationDomainRepository organizationDomainRepository, IOrganizationDomainRepository organizationDomainRepository,
IEventService eventService, IEventService eventService,
IDnsResolverService dnsResolverService,
ILogger<VerifyOrganizationDomainCommand> logger,
IGlobalSettings globalSettings) IGlobalSettings globalSettings)
{ {
_organizationDomainRepository = organizationDomainRepository; _organizationDomainRepository = organizationDomainRepository;
_eventService = eventService; _eventService = eventService;
_dnsResolverService = dnsResolverService;
_logger = logger;
_globalSettings = globalSettings; _globalSettings = globalSettings;
} }

View File

@ -4,5 +4,6 @@ namespace Bit.Core.AdminConsole.OrganizationFeatures.OrganizationDomains.Interfa
public interface IVerifyOrganizationDomainCommand public interface IVerifyOrganizationDomainCommand
{ {
Task<OrganizationDomain> VerifyOrganizationDomainAsync(OrganizationDomain organizationDomain); Task<OrganizationDomain> UserVerifyOrganizationDomainAsync(OrganizationDomain organizationDomain);
Task<OrganizationDomain> SystemVerifyOrganizationDomainAsync(OrganizationDomain organizationDomain);
} }

View File

@ -4,6 +4,7 @@ using Bit.Core.Enums;
using Bit.Core.Exceptions; using Bit.Core.Exceptions;
using Bit.Core.Repositories; using Bit.Core.Repositories;
using Bit.Core.Services; using Bit.Core.Services;
using Bit.Core.Settings;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
namespace Bit.Core.AdminConsole.OrganizationFeatures.OrganizationDomains; namespace Bit.Core.AdminConsole.OrganizationFeatures.OrganizationDomains;
@ -13,34 +14,85 @@ public class VerifyOrganizationDomainCommand : IVerifyOrganizationDomainCommand
private readonly IOrganizationDomainRepository _organizationDomainRepository; private readonly IOrganizationDomainRepository _organizationDomainRepository;
private readonly IDnsResolverService _dnsResolverService; private readonly IDnsResolverService _dnsResolverService;
private readonly IEventService _eventService; private readonly IEventService _eventService;
private readonly IGlobalSettings _globalSettings;
private readonly ILogger<VerifyOrganizationDomainCommand> _logger; private readonly ILogger<VerifyOrganizationDomainCommand> _logger;
public VerifyOrganizationDomainCommand( public VerifyOrganizationDomainCommand(
IOrganizationDomainRepository organizationDomainRepository, IOrganizationDomainRepository organizationDomainRepository,
IDnsResolverService dnsResolverService, IDnsResolverService dnsResolverService,
IEventService eventService, IEventService eventService,
IGlobalSettings globalSettings,
ILogger<VerifyOrganizationDomainCommand> logger) ILogger<VerifyOrganizationDomainCommand> logger)
{ {
_organizationDomainRepository = organizationDomainRepository; _organizationDomainRepository = organizationDomainRepository;
_dnsResolverService = dnsResolverService; _dnsResolverService = dnsResolverService;
_eventService = eventService; _eventService = eventService;
_globalSettings = globalSettings;
_logger = logger; _logger = logger;
} }
public async Task<OrganizationDomain> VerifyOrganizationDomainAsync(OrganizationDomain domain)
public async Task<OrganizationDomain> UserVerifyOrganizationDomainAsync(OrganizationDomain organizationDomain)
{ {
var domainVerificationResult = await VerifyOrganizationDomainAsync(organizationDomain);
await _eventService.LogOrganizationDomainEventAsync(domainVerificationResult,
domainVerificationResult.VerifiedDate != null
? EventType.OrganizationDomain_Verified
: EventType.OrganizationDomain_NotVerified);
await _organizationDomainRepository.ReplaceAsync(domainVerificationResult);
return domainVerificationResult;
}
public async Task<OrganizationDomain> SystemVerifyOrganizationDomainAsync(OrganizationDomain organizationDomain)
{
organizationDomain.SetJobRunCount();
var domainVerificationResult = await VerifyOrganizationDomainAsync(organizationDomain);
if (domainVerificationResult.VerifiedDate is not null)
{
_logger.LogInformation(Constants.BypassFiltersEventId, "Successfully validated domain");
await _eventService.LogOrganizationDomainEventAsync(domainVerificationResult,
EventType.OrganizationDomain_Verified,
EventSystemUser.DomainVerification);
}
else
{
domainVerificationResult.SetNextRunDate(_globalSettings.DomainVerification.VerificationInterval);
await _eventService.LogOrganizationDomainEventAsync(domainVerificationResult,
EventType.OrganizationDomain_NotVerified,
EventSystemUser.DomainVerification);
_logger.LogInformation(Constants.BypassFiltersEventId,
"Verification for organization {OrgId} with domain {Domain} failed",
domainVerificationResult.OrganizationId, domainVerificationResult.DomainName);
}
await _organizationDomainRepository.ReplaceAsync(domainVerificationResult);
return domainVerificationResult;
}
private async Task<OrganizationDomain> VerifyOrganizationDomainAsync(OrganizationDomain domain)
{
domain.SetLastCheckedDate();
if (domain.VerifiedDate is not null) if (domain.VerifiedDate is not null)
{ {
domain.SetLastCheckedDate();
await _organizationDomainRepository.ReplaceAsync(domain); await _organizationDomainRepository.ReplaceAsync(domain);
throw new ConflictException("Domain has already been verified."); throw new ConflictException("Domain has already been verified.");
} }
var claimedDomain = var claimedDomain =
await _organizationDomainRepository.GetClaimedDomainsByDomainNameAsync(domain.DomainName); await _organizationDomainRepository.GetClaimedDomainsByDomainNameAsync(domain.DomainName);
if (claimedDomain.Any())
if (claimedDomain.Count > 0)
{ {
domain.SetLastCheckedDate();
await _organizationDomainRepository.ReplaceAsync(domain); await _organizationDomainRepository.ReplaceAsync(domain);
throw new ConflictException("The domain is not available to be claimed."); throw new ConflictException("The domain is not available to be claimed.");
} }
@ -58,11 +110,6 @@ public class VerifyOrganizationDomainCommand : IVerifyOrganizationDomainCommand
domain.DomainName, e.Message); domain.DomainName, e.Message);
} }
domain.SetLastCheckedDate();
await _organizationDomainRepository.ReplaceAsync(domain);
await _eventService.LogOrganizationDomainEventAsync(domain,
domain.VerifiedDate != null ? EventType.OrganizationDomain_Verified : EventType.OrganizationDomain_NotVerified);
return domain; return domain;
} }
} }

View File

@ -1,4 +1,5 @@
using Bit.Core.Enums; using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationDomains.Interfaces;
using Bit.Core.Enums;
using Bit.Core.Repositories; using Bit.Core.Repositories;
using Bit.Core.Services; using Bit.Core.Services;
using Bit.Core.Settings; using Bit.Core.Settings;
@ -10,26 +11,29 @@ public class OrganizationDomainService : IOrganizationDomainService
{ {
private readonly IOrganizationDomainRepository _domainRepository; private readonly IOrganizationDomainRepository _domainRepository;
private readonly IOrganizationUserRepository _organizationUserRepository; private readonly IOrganizationUserRepository _organizationUserRepository;
private readonly IDnsResolverService _dnsResolverService;
private readonly IEventService _eventService; private readonly IEventService _eventService;
private readonly IMailService _mailService; private readonly IMailService _mailService;
private readonly IVerifyOrganizationDomainCommand _verifyOrganizationDomainCommand;
private readonly TimeProvider _timeProvider;
private readonly ILogger<OrganizationDomainService> _logger; private readonly ILogger<OrganizationDomainService> _logger;
private readonly IGlobalSettings _globalSettings; private readonly IGlobalSettings _globalSettings;
public OrganizationDomainService( public OrganizationDomainService(
IOrganizationDomainRepository domainRepository, IOrganizationDomainRepository domainRepository,
IOrganizationUserRepository organizationUserRepository, IOrganizationUserRepository organizationUserRepository,
IDnsResolverService dnsResolverService,
IEventService eventService, IEventService eventService,
IMailService mailService, IMailService mailService,
IVerifyOrganizationDomainCommand verifyOrganizationDomainCommand,
TimeProvider timeProvider,
ILogger<OrganizationDomainService> logger, ILogger<OrganizationDomainService> logger,
IGlobalSettings globalSettings) IGlobalSettings globalSettings)
{ {
_domainRepository = domainRepository; _domainRepository = domainRepository;
_organizationUserRepository = organizationUserRepository; _organizationUserRepository = organizationUserRepository;
_dnsResolverService = dnsResolverService;
_eventService = eventService; _eventService = eventService;
_mailService = mailService; _mailService = mailService;
_verifyOrganizationDomainCommand = verifyOrganizationDomainCommand;
_timeProvider = timeProvider;
_logger = logger; _logger = logger;
_globalSettings = globalSettings; _globalSettings = globalSettings;
} }
@ -37,7 +41,7 @@ public class OrganizationDomainService : IOrganizationDomainService
public async Task ValidateOrganizationsDomainAsync() public async Task ValidateOrganizationsDomainAsync()
{ {
//Date should be set 1 hour behind to ensure it selects all domains that should be verified //Date should be set 1 hour behind to ensure it selects all domains that should be verified
var runDate = DateTime.UtcNow.AddHours(-1); var runDate = _timeProvider.GetUtcNow().UtcDateTime.AddHours(-1);
var verifiableDomains = await _domainRepository.GetManyByNextRunDateAsync(runDate); var verifiableDomains = await _domainRepository.GetManyByNextRunDateAsync(runDate);
@ -45,43 +49,17 @@ public class OrganizationDomainService : IOrganizationDomainService
foreach (var domain in verifiableDomains) foreach (var domain in verifiableDomains)
{ {
_logger.LogInformation(Constants.BypassFiltersEventId,
"Attempting verification for organization {OrgId} with domain {Domain}",
domain.OrganizationId,
domain.DomainName);
try try
{ {
_logger.LogInformation(Constants.BypassFiltersEventId, "Attempting verification for organization {OrgId} with domain {Domain}", domain.OrganizationId, domain.DomainName); _ = await _verifyOrganizationDomainCommand.SystemVerifyOrganizationDomainAsync(domain);
var status = await _dnsResolverService.ResolveAsync(domain.DomainName, domain.Txt);
if (status)
{
_logger.LogInformation(Constants.BypassFiltersEventId, "Successfully validated domain");
// Update entry on OrganizationDomain table
domain.SetLastCheckedDate();
domain.SetVerifiedDate();
domain.SetJobRunCount();
await _domainRepository.ReplaceAsync(domain);
await _eventService.LogOrganizationDomainEventAsync(domain, EventType.OrganizationDomain_Verified,
EventSystemUser.DomainVerification);
}
else
{
// Update entry on OrganizationDomain table
domain.SetLastCheckedDate();
domain.SetJobRunCount();
domain.SetNextRunDate(_globalSettings.DomainVerification.VerificationInterval);
await _domainRepository.ReplaceAsync(domain);
await _eventService.LogOrganizationDomainEventAsync(domain, EventType.OrganizationDomain_NotVerified,
EventSystemUser.DomainVerification);
_logger.LogInformation(Constants.BypassFiltersEventId, "Verification for organization {OrgId} with domain {Domain} failed",
domain.OrganizationId, domain.DomainName);
}
} }
catch (Exception ex) catch (Exception ex)
{ {
// Update entry on OrganizationDomain table
domain.SetLastCheckedDate();
domain.SetJobRunCount();
domain.SetNextRunDate(_globalSettings.DomainVerification.VerificationInterval); domain.SetNextRunDate(_globalSettings.DomainVerification.VerificationInterval);
await _domainRepository.ReplaceAsync(domain); await _domainRepository.ReplaceAsync(domain);

View File

@ -229,13 +229,13 @@ public class OrganizationDomainControllerTests
sutProvider.GetDependency<IOrganizationDomainRepository>() sutProvider.GetDependency<IOrganizationDomainRepository>()
.GetDomainByIdOrganizationIdAsync(organizationDomain.Id, organizationDomain.OrganizationId) .GetDomainByIdOrganizationIdAsync(organizationDomain.Id, organizationDomain.OrganizationId)
.Returns(organizationDomain); .Returns(organizationDomain);
sutProvider.GetDependency<IVerifyOrganizationDomainCommand>().VerifyOrganizationDomainAsync(organizationDomain) sutProvider.GetDependency<IVerifyOrganizationDomainCommand>().UserVerifyOrganizationDomainAsync(organizationDomain)
.Returns(new OrganizationDomain()); .Returns(new OrganizationDomain());
var result = await sutProvider.Sut.Verify(organizationDomain.OrganizationId, organizationDomain.Id); var result = await sutProvider.Sut.Verify(organizationDomain.OrganizationId, organizationDomain.Id);
await sutProvider.GetDependency<IVerifyOrganizationDomainCommand>().Received(1) await sutProvider.GetDependency<IVerifyOrganizationDomainCommand>().Received(1)
.VerifyOrganizationDomainAsync(organizationDomain); .UserVerifyOrganizationDomainAsync(organizationDomain);
Assert.IsType<OrganizationDomainResponseModel>(result); Assert.IsType<OrganizationDomainResponseModel>(result);
} }

View File

@ -15,7 +15,7 @@ namespace Bit.Core.Test.AdminConsole.OrganizationFeatures.OrganizationDomains;
public class VerifyOrganizationDomainCommandTests public class VerifyOrganizationDomainCommandTests
{ {
[Theory, BitAutoData] [Theory, BitAutoData]
public async Task VerifyOrganizationDomain_ShouldThrowConflict_WhenDomainHasBeenClaimed(Guid id, public async Task UserVerifyOrganizationDomain_ShouldThrowConflict_WhenDomainHasBeenClaimed(Guid id,
SutProvider<VerifyOrganizationDomainCommand> sutProvider) SutProvider<VerifyOrganizationDomainCommand> sutProvider)
{ {
var expected = new OrganizationDomain var expected = new OrganizationDomain
@ -30,14 +30,14 @@ public class VerifyOrganizationDomainCommandTests
.GetByIdAsync(id) .GetByIdAsync(id)
.Returns(expected); .Returns(expected);
var requestAction = async () => await sutProvider.Sut.VerifyOrganizationDomainAsync(expected); var requestAction = async () => await sutProvider.Sut.UserVerifyOrganizationDomainAsync(expected);
var exception = await Assert.ThrowsAsync<ConflictException>(requestAction); var exception = await Assert.ThrowsAsync<ConflictException>(requestAction);
Assert.Contains("Domain has already been verified.", exception.Message); Assert.Contains("Domain has already been verified.", exception.Message);
} }
[Theory, BitAutoData] [Theory, BitAutoData]
public async Task VerifyOrganizationDomain_ShouldThrowConflict_WhenDomainHasBeenClaimedByAnotherOrganization(Guid id, public async Task UserVerifyOrganizationDomain_ShouldThrowConflict_WhenDomainHasBeenClaimedByAnotherOrganization(Guid id,
SutProvider<VerifyOrganizationDomainCommand> sutProvider) SutProvider<VerifyOrganizationDomainCommand> sutProvider)
{ {
var expected = new OrganizationDomain var expected = new OrganizationDomain
@ -54,14 +54,14 @@ public class VerifyOrganizationDomainCommandTests
.GetClaimedDomainsByDomainNameAsync(expected.DomainName) .GetClaimedDomainsByDomainNameAsync(expected.DomainName)
.Returns(new List<OrganizationDomain> { expected }); .Returns(new List<OrganizationDomain> { expected });
var requestAction = async () => await sutProvider.Sut.VerifyOrganizationDomainAsync(expected); var requestAction = async () => await sutProvider.Sut.UserVerifyOrganizationDomainAsync(expected);
var exception = await Assert.ThrowsAsync<ConflictException>(requestAction); var exception = await Assert.ThrowsAsync<ConflictException>(requestAction);
Assert.Contains("The domain is not available to be claimed.", exception.Message); Assert.Contains("The domain is not available to be claimed.", exception.Message);
} }
[Theory, BitAutoData] [Theory, BitAutoData]
public async Task VerifyOrganizationDomain_ShouldVerifyDomainUpdateAndLogEvent_WhenTxtRecordExists(Guid id, public async Task UserVerifyOrganizationDomain_ShouldVerifyDomainUpdateAndLogEvent_WhenTxtRecordExists(Guid id,
SutProvider<VerifyOrganizationDomainCommand> sutProvider) SutProvider<VerifyOrganizationDomainCommand> sutProvider)
{ {
var expected = new OrganizationDomain var expected = new OrganizationDomain
@ -81,7 +81,7 @@ public class VerifyOrganizationDomainCommandTests
.ResolveAsync(expected.DomainName, Arg.Any<string>()) .ResolveAsync(expected.DomainName, Arg.Any<string>())
.Returns(true); .Returns(true);
var result = await sutProvider.Sut.VerifyOrganizationDomainAsync(expected); var result = await sutProvider.Sut.UserVerifyOrganizationDomainAsync(expected);
Assert.NotNull(result.VerifiedDate); Assert.NotNull(result.VerifiedDate);
await sutProvider.GetDependency<IOrganizationDomainRepository>().Received(1) await sutProvider.GetDependency<IOrganizationDomainRepository>().Received(1)
@ -91,7 +91,7 @@ public class VerifyOrganizationDomainCommandTests
} }
[Theory, BitAutoData] [Theory, BitAutoData]
public async Task VerifyOrganizationDomain_ShouldNotSetVerifiedDate_WhenTxtRecordDoesNotExist(Guid id, public async Task UserVerifyOrganizationDomain_ShouldNotSetVerifiedDate_WhenTxtRecordDoesNotExist(Guid id,
SutProvider<VerifyOrganizationDomainCommand> sutProvider) SutProvider<VerifyOrganizationDomainCommand> sutProvider)
{ {
var expected = new OrganizationDomain var expected = new OrganizationDomain
@ -111,10 +111,30 @@ public class VerifyOrganizationDomainCommandTests
.ResolveAsync(expected.DomainName, Arg.Any<string>()) .ResolveAsync(expected.DomainName, Arg.Any<string>())
.Returns(false); .Returns(false);
var result = await sutProvider.Sut.VerifyOrganizationDomainAsync(expected); var result = await sutProvider.Sut.UserVerifyOrganizationDomainAsync(expected);
Assert.Null(result.VerifiedDate); Assert.Null(result.VerifiedDate);
await sutProvider.GetDependency<IEventService>().Received(1) await sutProvider.GetDependency<IEventService>().Received(1)
.LogOrganizationDomainEventAsync(Arg.Any<OrganizationDomain>(), EventType.OrganizationDomain_NotVerified); .LogOrganizationDomainEventAsync(Arg.Any<OrganizationDomain>(), EventType.OrganizationDomain_NotVerified);
} }
[Theory, BitAutoData]
public async Task SystemVerifyOrganizationDomain_CallsEventServiceWithUpdatedJobRunCount(SutProvider<VerifyOrganizationDomainCommand> sutProvider)
{
var domain = new OrganizationDomain()
{
Id = Guid.NewGuid(),
OrganizationId = Guid.NewGuid(),
CreationDate = DateTime.UtcNow,
DomainName = "test.com",
Txt = "btw+12345",
};
_ = await sutProvider.Sut.SystemVerifyOrganizationDomainAsync(domain);
await sutProvider.GetDependency<IEventService>().ReceivedWithAnyArgs(1)
.LogOrganizationDomainEventAsync(default, EventType.OrganizationDomain_NotVerified,
EventSystemUser.DomainVerification);
}
} }

View File

@ -1,8 +1,7 @@
using Bit.Core.AdminConsole.Services.Implementations; using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationDomains.Interfaces;
using Bit.Core.AdminConsole.Services.Implementations;
using Bit.Core.Entities; using Bit.Core.Entities;
using Bit.Core.Enums;
using Bit.Core.Repositories; using Bit.Core.Repositories;
using Bit.Core.Services;
using Bit.Test.Common.AutoFixture; using Bit.Test.Common.AutoFixture;
using Bit.Test.Common.AutoFixture.Attributes; using Bit.Test.Common.AutoFixture.Attributes;
using NSubstitute; using NSubstitute;
@ -36,18 +35,14 @@ public class OrganizationDomainServiceTests
Txt = "btw+6789" Txt = "btw+6789"
} }
}; };
sutProvider.GetDependency<IOrganizationDomainRepository>().GetManyByNextRunDateAsync(default) sutProvider.GetDependency<IOrganizationDomainRepository>().GetManyByNextRunDateAsync(default)
.ReturnsForAnyArgs(domains); .ReturnsForAnyArgs(domains);
await sutProvider.Sut.ValidateOrganizationsDomainAsync(); await sutProvider.Sut.ValidateOrganizationsDomainAsync();
await sutProvider.GetDependency<IDnsResolverService>().ReceivedWithAnyArgs(2) await sutProvider.GetDependency<IVerifyOrganizationDomainCommand>().ReceivedWithAnyArgs(2)
.ResolveAsync(default, default); .SystemVerifyOrganizationDomainAsync(default);
await sutProvider.GetDependency<IOrganizationDomainRepository>().ReceivedWithAnyArgs(2)
.ReplaceAsync(default);
await sutProvider.GetDependency<IEventService>().ReceivedWithAnyArgs(2)
.LogOrganizationDomainEventAsync(default, EventType.OrganizationDomain_NotVerified,
EventSystemUser.DomainVerification);
} }
[Theory, BitAutoData] [Theory, BitAutoData]