Skip to content

Commit

Permalink
[PM-13722] Refactor ValidateOrganizationsDomainAsync (bitwarden#4905)
Browse files Browse the repository at this point in the history
Refactored ValidateOrganizationsDomainAsync to use VerifyOrganizationDomainAsync
  • Loading branch information
jrmccannon authored Oct 18, 2024
1 parent 1d3188d commit 4fec7ca
Show file tree
Hide file tree
Showing 8 changed files with 109 additions and 75 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,7 @@ public async Task<OrganizationDomainResponseModel> Verify(Guid orgId, Guid id)
throw new NotFoundException();
}

organizationDomain = await _verifyOrganizationDomainCommand.VerifyOrganizationDomainAsync(organizationDomain);
organizationDomain = await _verifyOrganizationDomainCommand.UserVerifyOrganizationDomainAsync(organizationDomain);

return new OrganizationDomainResponseModel(organizationDomain);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,29 +6,22 @@
using Bit.Core.Services;
using Bit.Core.Settings;
using Bit.Core.Utilities;
using Microsoft.Extensions.Logging;

namespace Bit.Core.AdminConsole.OrganizationFeatures.OrganizationDomains;

public class CreateOrganizationDomainCommand : ICreateOrganizationDomainCommand
{
private readonly IOrganizationDomainRepository _organizationDomainRepository;
private readonly IEventService _eventService;
private readonly IDnsResolverService _dnsResolverService;
private readonly ILogger<VerifyOrganizationDomainCommand> _logger;
private readonly IGlobalSettings _globalSettings;

public CreateOrganizationDomainCommand(
IOrganizationDomainRepository organizationDomainRepository,
IEventService eventService,
IDnsResolverService dnsResolverService,
ILogger<VerifyOrganizationDomainCommand> logger,
IGlobalSettings globalSettings)
{
_organizationDomainRepository = organizationDomainRepository;
_eventService = eventService;
_dnsResolverService = dnsResolverService;
_logger = logger;
_globalSettings = globalSettings;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,6 @@ namespace Bit.Core.AdminConsole.OrganizationFeatures.OrganizationDomains.Interfa

public interface IVerifyOrganizationDomainCommand
{
Task<OrganizationDomain> VerifyOrganizationDomainAsync(OrganizationDomain organizationDomain);
Task<OrganizationDomain> UserVerifyOrganizationDomainAsync(OrganizationDomain organizationDomain);
Task<OrganizationDomain> SystemVerifyOrganizationDomainAsync(OrganizationDomain organizationDomain);
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
using Bit.Core.Exceptions;
using Bit.Core.Repositories;
using Bit.Core.Services;
using Bit.Core.Settings;
using Microsoft.Extensions.Logging;

namespace Bit.Core.AdminConsole.OrganizationFeatures.OrganizationDomains;
Expand All @@ -13,34 +14,85 @@ public class VerifyOrganizationDomainCommand : IVerifyOrganizationDomainCommand
private readonly IOrganizationDomainRepository _organizationDomainRepository;
private readonly IDnsResolverService _dnsResolverService;
private readonly IEventService _eventService;
private readonly IGlobalSettings _globalSettings;
private readonly ILogger<VerifyOrganizationDomainCommand> _logger;

public VerifyOrganizationDomainCommand(
IOrganizationDomainRepository organizationDomainRepository,
IDnsResolverService dnsResolverService,
IEventService eventService,
IGlobalSettings globalSettings,
ILogger<VerifyOrganizationDomainCommand> logger)
{
_organizationDomainRepository = organizationDomainRepository;
_dnsResolverService = dnsResolverService;
_eventService = eventService;
_globalSettings = globalSettings;
_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)
{
domain.SetLastCheckedDate();
await _organizationDomainRepository.ReplaceAsync(domain);
throw new ConflictException("Domain has already been verified.");
}

var claimedDomain =
await _organizationDomainRepository.GetClaimedDomainsByDomainNameAsync(domain.DomainName);
if (claimedDomain.Any())

if (claimedDomain.Count > 0)
{
domain.SetLastCheckedDate();
await _organizationDomainRepository.ReplaceAsync(domain);
throw new ConflictException("The domain is not available to be claimed.");
}
Expand All @@ -58,11 +110,6 @@ public async Task<OrganizationDomain> VerifyOrganizationDomainAsync(Organization
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;
}
}
Original file line number Diff line number Diff line change
@@ -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.Services;
using Bit.Core.Settings;
Expand All @@ -10,78 +11,55 @@ public class OrganizationDomainService : IOrganizationDomainService
{
private readonly IOrganizationDomainRepository _domainRepository;
private readonly IOrganizationUserRepository _organizationUserRepository;
private readonly IDnsResolverService _dnsResolverService;
private readonly IEventService _eventService;
private readonly IMailService _mailService;
private readonly IVerifyOrganizationDomainCommand _verifyOrganizationDomainCommand;
private readonly TimeProvider _timeProvider;
private readonly ILogger<OrganizationDomainService> _logger;
private readonly IGlobalSettings _globalSettings;

public OrganizationDomainService(
IOrganizationDomainRepository domainRepository,
IOrganizationUserRepository organizationUserRepository,
IDnsResolverService dnsResolverService,
IEventService eventService,
IMailService mailService,
IVerifyOrganizationDomainCommand verifyOrganizationDomainCommand,
TimeProvider timeProvider,
ILogger<OrganizationDomainService> logger,
IGlobalSettings globalSettings)
{
_domainRepository = domainRepository;
_organizationUserRepository = organizationUserRepository;
_dnsResolverService = dnsResolverService;
_eventService = eventService;
_mailService = mailService;
_verifyOrganizationDomainCommand = verifyOrganizationDomainCommand;
_timeProvider = timeProvider;
_logger = logger;
_globalSettings = globalSettings;
}

public async Task ValidateOrganizationsDomainAsync()
{
//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);

_logger.LogInformation(Constants.BypassFiltersEventId, "Validating {verifiableDomainsCount} domains.", verifiableDomains.Count);

foreach (var domain in verifiableDomains)
{
_logger.LogInformation(Constants.BypassFiltersEventId,
"Attempting verification for organization {OrgId} with domain {Domain}",
domain.OrganizationId,
domain.DomainName);

try
{
_logger.LogInformation(Constants.BypassFiltersEventId, "Attempting verification for organization {OrgId} with domain {Domain}", domain.OrganizationId, domain.DomainName);

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);
}
_ = await _verifyOrganizationDomainCommand.SystemVerifyOrganizationDomainAsync(domain);
}
catch (Exception ex)
{
// Update entry on OrganizationDomain table
domain.SetLastCheckedDate();
domain.SetJobRunCount();
domain.SetNextRunDate(_globalSettings.DomainVerification.VerificationInterval);
await _domainRepository.ReplaceAsync(domain);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -229,13 +229,13 @@ public async Task Verify_WhenRequestIsValid(OrganizationDomain organizationDomai
sutProvider.GetDependency<IOrganizationDomainRepository>()
.GetDomainByIdOrganizationIdAsync(organizationDomain.Id, organizationDomain.OrganizationId)
.Returns(organizationDomain);
sutProvider.GetDependency<IVerifyOrganizationDomainCommand>().VerifyOrganizationDomainAsync(organizationDomain)
sutProvider.GetDependency<IVerifyOrganizationDomainCommand>().UserVerifyOrganizationDomainAsync(organizationDomain)
.Returns(new OrganizationDomain());

var result = await sutProvider.Sut.Verify(organizationDomain.OrganizationId, organizationDomain.Id);

await sutProvider.GetDependency<IVerifyOrganizationDomainCommand>().Received(1)
.VerifyOrganizationDomainAsync(organizationDomain);
.UserVerifyOrganizationDomainAsync(organizationDomain);
Assert.IsType<OrganizationDomainResponseModel>(result);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ namespace Bit.Core.Test.AdminConsole.OrganizationFeatures.OrganizationDomains;
public class VerifyOrganizationDomainCommandTests
{
[Theory, BitAutoData]
public async Task VerifyOrganizationDomain_ShouldThrowConflict_WhenDomainHasBeenClaimed(Guid id,
public async Task UserVerifyOrganizationDomain_ShouldThrowConflict_WhenDomainHasBeenClaimed(Guid id,
SutProvider<VerifyOrganizationDomainCommand> sutProvider)
{
var expected = new OrganizationDomain
Expand All @@ -30,14 +30,14 @@ public async Task VerifyOrganizationDomain_ShouldThrowConflict_WhenDomainHasBeen
.GetByIdAsync(id)
.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);
Assert.Contains("Domain has already been verified.", exception.Message);
}

[Theory, BitAutoData]
public async Task VerifyOrganizationDomain_ShouldThrowConflict_WhenDomainHasBeenClaimedByAnotherOrganization(Guid id,
public async Task UserVerifyOrganizationDomain_ShouldThrowConflict_WhenDomainHasBeenClaimedByAnotherOrganization(Guid id,
SutProvider<VerifyOrganizationDomainCommand> sutProvider)
{
var expected = new OrganizationDomain
Expand All @@ -54,14 +54,14 @@ public async Task VerifyOrganizationDomain_ShouldThrowConflict_WhenDomainHasBeen
.GetClaimedDomainsByDomainNameAsync(expected.DomainName)
.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);
Assert.Contains("The domain is not available to be claimed.", exception.Message);
}

[Theory, BitAutoData]
public async Task VerifyOrganizationDomain_ShouldVerifyDomainUpdateAndLogEvent_WhenTxtRecordExists(Guid id,
public async Task UserVerifyOrganizationDomain_ShouldVerifyDomainUpdateAndLogEvent_WhenTxtRecordExists(Guid id,
SutProvider<VerifyOrganizationDomainCommand> sutProvider)
{
var expected = new OrganizationDomain
Expand All @@ -81,7 +81,7 @@ public async Task VerifyOrganizationDomain_ShouldVerifyDomainUpdateAndLogEvent_W
.ResolveAsync(expected.DomainName, Arg.Any<string>())
.Returns(true);

var result = await sutProvider.Sut.VerifyOrganizationDomainAsync(expected);
var result = await sutProvider.Sut.UserVerifyOrganizationDomainAsync(expected);

Assert.NotNull(result.VerifiedDate);
await sutProvider.GetDependency<IOrganizationDomainRepository>().Received(1)
Expand All @@ -91,7 +91,7 @@ await sutProvider.GetDependency<IEventService>().Received(1)
}

[Theory, BitAutoData]
public async Task VerifyOrganizationDomain_ShouldNotSetVerifiedDate_WhenTxtRecordDoesNotExist(Guid id,
public async Task UserVerifyOrganizationDomain_ShouldNotSetVerifiedDate_WhenTxtRecordDoesNotExist(Guid id,
SutProvider<VerifyOrganizationDomainCommand> sutProvider)
{
var expected = new OrganizationDomain
Expand All @@ -111,10 +111,30 @@ public async Task VerifyOrganizationDomain_ShouldNotSetVerifiedDate_WhenTxtRecor
.ResolveAsync(expected.DomainName, Arg.Any<string>())
.Returns(false);

var result = await sutProvider.Sut.VerifyOrganizationDomainAsync(expected);
var result = await sutProvider.Sut.UserVerifyOrganizationDomainAsync(expected);

Assert.Null(result.VerifiedDate);
await sutProvider.GetDependency<IEventService>().Received(1)
.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);
}
}
Original file line number Diff line number Diff line change
@@ -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.Enums;
using Bit.Core.Repositories;
using Bit.Core.Services;
using Bit.Test.Common.AutoFixture;
using Bit.Test.Common.AutoFixture.Attributes;
using NSubstitute;
Expand Down Expand Up @@ -36,18 +35,14 @@ public async Task ValidateOrganizationsDomainAsync_CallsDnsResolverServiceAndRep
Txt = "btw+6789"
}
};

sutProvider.GetDependency<IOrganizationDomainRepository>().GetManyByNextRunDateAsync(default)
.ReturnsForAnyArgs(domains);

await sutProvider.Sut.ValidateOrganizationsDomainAsync();

await sutProvider.GetDependency<IDnsResolverService>().ReceivedWithAnyArgs(2)
.ResolveAsync(default, default);
await sutProvider.GetDependency<IOrganizationDomainRepository>().ReceivedWithAnyArgs(2)
.ReplaceAsync(default);
await sutProvider.GetDependency<IEventService>().ReceivedWithAnyArgs(2)
.LogOrganizationDomainEventAsync(default, EventType.OrganizationDomain_NotVerified,
EventSystemUser.DomainVerification);
await sutProvider.GetDependency<IVerifyOrganizationDomainCommand>().ReceivedWithAnyArgs(2)
.SystemVerifyOrganizationDomainAsync(default);
}

[Theory, BitAutoData]
Expand Down

0 comments on commit 4fec7ca

Please sign in to comment.