Skip to content

Commit

Permalink
[SG-682] Add Event Log Entries to Organization Domain (#2492)
Browse files Browse the repository at this point in the history
* Added domain name property to Event related objects

* Added organization domain claiming event types

* Created migration script and updated related event scripts to include domanName

* Added EF Migrations

* Renamed postres script file extension

* Added DomainName property to response model

* Added abstraction to interface

* Added system name to enum

* dotnet formattinfg fix

* Added events to organization domain actions

* Added LastCheckedDate property to domain

* Migrations and stored procedure updates with new column

* Added new stored procedure to get domain by org id and domain name

* Log organization domain event abstract method

* Ef migrattion to add new LastCheckedDate column

* Added duplicate domain exception

* Modified create command to include domain verification and last checked date and renamed methods used

* removed variable

* changed service lifetime

* Renamed trigger

* Initialed property in constructor

* Ensured domain name is stored as lower case

* Fixed suggestions from review

* Fixed suggestions from review
  • Loading branch information
gbubemismith authored Dec 14, 2022
1 parent 179d5a1 commit 3b3b5d4
Show file tree
Hide file tree
Showing 50 changed files with 7,468 additions and 42 deletions.
7 changes: 4 additions & 3 deletions src/Api/Controllers/OrganizationDomainController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -79,21 +79,22 @@ public async Task<OrganizationDomainResponseModel> Post(string orgId, [FromBody]
{
OrganizationId = orgIdGuid,
Txt = model.Txt,
DomainName = model.DomainName
DomainName = model.DomainName.ToLower()
};

var domain = await _createOrganizationDomainCommand.CreateAsync(organizationDomain);
return new OrganizationDomainResponseModel(domain);
}

[HttpPost("{id}/verify")]
public async Task<bool> Verify(string orgId, string id)
public async Task<OrganizationDomainResponseModel> Verify(string orgId, string id)
{
var orgIdGuid = new Guid(orgId);
var idGuid = new Guid(id);
await ValidateOrganizationAccessAsync(orgIdGuid);

return await _verifyOrganizationDomainCommand.VerifyOrganizationDomain(idGuid);
var domain = await _verifyOrganizationDomainCommand.VerifyOrganizationDomain(idGuid);
return new OrganizationDomainResponseModel(domain);
}

[HttpDelete("{id}")]
Expand Down
2 changes: 1 addition & 1 deletion src/Api/Controllers/OrganizationsController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -726,7 +726,7 @@ public async Task<OrganizationSsoResponseModel> PostSso(Guid id, [FromBody] Orga
[HttpPost("sso/details")]
public async Task<OrganizationDomainSsoDetailsResponseModel> GetSso([FromBody] OrganisationSsoDomainDetailsRequestModel model)
{
var ssoResult = await _organizationDomainRepository.GetOrganizationDomainSsoDetails(model.Email);
var ssoResult = await _organizationDomainRepository.GetOrganizationDomainSsoDetailsAsync(model.Email);
if (ssoResult is null)
{
throw new NotFoundException();
Expand Down
6 changes: 3 additions & 3 deletions src/Api/Jobs/JobsHostedService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -47,8 +47,8 @@ public override async Task StartAsync(CancellationToken cancellationToken)
.WithIntervalInHours(24)
.RepeatForever())
.Build();
var validateOrganizationTrigger = TriggerBuilder.Create()
.WithIdentity("ValidateOrganizationTrigger")
var validateOrganizationDomainTrigger = TriggerBuilder.Create()
.WithIdentity("ValidateOrganizationDomainTrigger")
.StartNow()
.WithCronSchedule("0 0 * * * ?")
.Build();
Expand All @@ -61,7 +61,7 @@ public override async Task StartAsync(CancellationToken cancellationToken)
new Tuple<Type, ITrigger>(typeof(EmergencyAccessTimeoutJob), emergencyAccessTimeoutTrigger),
new Tuple<Type, ITrigger>(typeof(ValidateUsersJob), everyTopOfTheSixthHourTrigger),
new Tuple<Type, ITrigger>(typeof(ValidateOrganizationsJob), everyTwelfthHourAndThirtyMinutesTrigger),
new Tuple<Type, ITrigger>(typeof(ValidateOrganizationDomainJob), validateOrganizationTrigger),
new Tuple<Type, ITrigger>(typeof(ValidateOrganizationDomainJob), validateOrganizationDomainTrigger),
};

if (_globalSettings.SelfHosted && _globalSettings.EnableCloudCommunication)
Expand Down
14 changes: 9 additions & 5 deletions src/Api/Jobs/ValidateOrganizationDomainJob.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,20 +7,24 @@ namespace Bit.Api.Jobs;

public class ValidateOrganizationDomainJob : BaseJob
{
private readonly IVerificationDomainService _verificationDomainService;

private readonly IServiceProvider _serviceProvider;
public ValidateOrganizationDomainJob(
IVerificationDomainService verificationDomainService,
IServiceProvider serviceProvider,
ILogger<ValidateOrganizationDomainJob> logger)
: base(logger)
{
_verificationDomainService = verificationDomainService;
_serviceProvider = serviceProvider;
}

protected override async Task ExecuteJobAsync(IJobExecutionContext context)
{
_logger.LogInformation(Constants.BypassFiltersEventId, "Execute job task: ValidateOrganizationDomainJob: Start");
await _verificationDomainService.ValidateOrganizationsDomainAsync();
using (var serviceScope = _serviceProvider.CreateScope())
{
var verificationDomainService =
serviceScope.ServiceProvider.GetRequiredService<IOrganizationDomainVerificationService>();
await verificationDomainService.ValidateOrganizationsDomainAsync();
}
_logger.LogInformation(Constants.BypassFiltersEventId, "Execute job task: ValidateOrganizationDomainJob: End");
}
}
2 changes: 2 additions & 0 deletions src/Api/Models/Response/EventResponseModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ public EventResponseModel(IEvent ev)
IpAddress = ev.IpAddress;
InstallationId = ev.InstallationId;
SystemUser = ev.SystemUser;
DomainName = ev.DomainName;
}

public EventType Type { get; set; }
Expand All @@ -50,4 +51,5 @@ public EventResponseModel(IEvent ev)
public DeviceType? DeviceType { get; set; }
public string IpAddress { get; set; }
public EventSystemUser? SystemUser { get; set; }
public string DomainName { get; set; }
}
3 changes: 3 additions & 0 deletions src/Core/Entities/Event.cs
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ public Event(IEvent e)
IpAddress = e.IpAddress;
ActingUserId = e.ActingUserId;
SystemUser = e.SystemUser;

}

public Guid Id { get; set; }
Expand All @@ -49,6 +50,8 @@ public Event(IEvent e)
public string IpAddress { get; set; }
public Guid? ActingUserId { get; set; }
public EventSystemUser? SystemUser { get; set; }
public string DomainName { get; set; }


public void SetNewId()
{
Expand Down
6 changes: 6 additions & 0 deletions src/Core/Entities/OrganizationDomain.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ public class OrganizationDomain : ITableObject<Guid>
public DateTime CreationDate { get; set; } = DateTime.UtcNow;
public DateTime? VerifiedDate { get; private set; }
public DateTime NextRunDate { get; private set; }
public DateTime? LastCheckedDate { get; private set; }
public int JobRunCount { get; private set; }
public void SetNewId() => Id = CoreHelpers.GenerateComb();

Expand All @@ -39,4 +40,9 @@ public void SetVerifiedDate()
{
VerifiedDate = DateTime.UtcNow;
}

public void SetLastCheckedDate()
{
LastCheckedDate = DateTime.UtcNow;
}
}
3 changes: 2 additions & 1 deletion src/Core/Enums/EventSystemUser.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,6 @@

public enum EventSystemUser : byte
{
SCIM = 1
SCIM = 1,
SSO = 2
}
5 changes: 5 additions & 0 deletions src/Core/Enums/EventType.cs
Original file line number Diff line number Diff line change
Expand Up @@ -75,4 +75,9 @@ public enum EventType : int
ProviderOrganization_Added = 1901,
ProviderOrganization_Removed = 1902,
ProviderOrganization_VaultAccessed = 1903,

OrganizationDomain_Added = 1904,
OrganizationDomain_Removed = 1905,
OrganizationDomain_Verified = 1906,
OrganizationDomain_NotVerified = 1907
}
10 changes: 10 additions & 0 deletions src/Core/Exceptions/DuplicateDomainException.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
namespace Bit.Core.Exceptions;

public class DuplicateDomainException : Exception
{
public DuplicateDomainException()
: base("A domain already exists for this organization.")
{

}
}
1 change: 1 addition & 0 deletions src/Core/Models/Data/EventMessage.cs
Original file line number Diff line number Diff line change
Expand Up @@ -32,4 +32,5 @@ public EventMessage(ICurrentContext currentContext)
public string IpAddress { get; set; }
public Guid? IdempotencyId { get; private set; } = Guid.NewGuid();
public EventSystemUser? SystemUser { get; set; }
public string DomainName { get; set; }
}
2 changes: 2 additions & 0 deletions src/Core/Models/Data/EventTableEntity.cs
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ private EventTableEntity(IEvent e)
IpAddress = e.IpAddress;
ActingUserId = e.ActingUserId;
SystemUser = e.SystemUser;
DomainName = e.DomainName;
}

public DateTime Date { get; set; }
Expand All @@ -46,6 +47,7 @@ private EventTableEntity(IEvent e)
public string IpAddress { get; set; }
public Guid? ActingUserId { get; set; }
public EventSystemUser? SystemUser { get; set; }
public string DomainName { get; set; }

public override IDictionary<string, EntityProperty> WriteEntity(OperationContext operationContext)
{
Expand Down
1 change: 1 addition & 0 deletions src/Core/Models/Data/IEvent.cs
Original file line number Diff line number Diff line change
Expand Up @@ -21,4 +21,5 @@ public interface IEvent
string IpAddress { get; set; }
DateTime Date { get; set; }
EventSystemUser? SystemUser { get; set; }
string DomainName { get; set; }
}
Original file line number Diff line number Diff line change
@@ -1,17 +1,30 @@
using Bit.Core.Entities;
using Bit.Core.Enums;
using Bit.Core.Exceptions;
using Bit.Core.OrganizationFeatures.OrganizationDomains.Interfaces;
using Bit.Core.Repositories;
using Bit.Core.Services;
using Microsoft.Extensions.Logging;

namespace Bit.Core.OrganizationFeatures.OrganizationDomains;

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

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

public async Task<OrganizationDomain> CreateAsync(OrganizationDomain organizationDomain)
Expand All @@ -24,8 +37,36 @@ public async Task<OrganizationDomain> CreateAsync(OrganizationDomain organizatio
throw new DomainClaimedException();
}

//check for duplicate domain entry for an organization
var duplicateOrgDomain =
await _organizationDomainRepository.GetDomainByOrgIdAndDomainNameAsync(organizationDomain.OrganizationId,
organizationDomain.DomainName);
if (duplicateOrgDomain is not null)
{
throw new DuplicateDomainException();
}

try
{
if (await _dnsResolverService.ResolveAsync(organizationDomain.DomainName, organizationDomain.Txt))
{
organizationDomain.SetVerifiedDate();
}
}
catch (Exception e)
{
_logger.LogError("Error verifying Organization domain.", e);
}

organizationDomain.SetNextRunDate();
organizationDomain.SetLastCheckedDate();

var orgDomain = await _organizationDomainRepository.CreateAsync(organizationDomain);

await _eventService.LogOrganizationDomainEventAsync(orgDomain, EventType.OrganizationDomain_Added);
await _eventService.LogOrganizationDomainEventAsync(orgDomain,
orgDomain.VerifiedDate != null ? EventType.OrganizationDomain_Verified : EventType.OrganizationDomain_NotVerified);

return await _organizationDomainRepository.CreateAsync(organizationDomain);
return orgDomain;
}
}
Original file line number Diff line number Diff line change
@@ -1,16 +1,21 @@
using Bit.Core.Exceptions;
using Bit.Core.Enums;
using Bit.Core.Exceptions;
using Bit.Core.OrganizationFeatures.OrganizationDomains.Interfaces;
using Bit.Core.Repositories;
using Bit.Core.Services;

namespace Bit.Core.OrganizationFeatures.OrganizationDomains;

public class DeleteOrganizationDomainCommand : IDeleteOrganizationDomainCommand
{
private readonly IOrganizationDomainRepository _organizationDomainRepository;
private readonly IEventService _eventService;

public DeleteOrganizationDomainCommand(IOrganizationDomainRepository organizationDomainRepository)
public DeleteOrganizationDomainCommand(IOrganizationDomainRepository organizationDomainRepository,
IEventService eventService)
{
_organizationDomainRepository = organizationDomainRepository;
_eventService = eventService;
}

public async Task DeleteAsync(Guid id)
Expand All @@ -22,5 +27,6 @@ public async Task DeleteAsync(Guid id)
}

await _organizationDomainRepository.DeleteAsync(domain);
await _eventService.LogOrganizationDomainEventAsync(domain, EventType.OrganizationDomain_Removed);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,5 +14,5 @@ public GetOrganizationDomainByOrganizationIdQuery(IOrganizationDomainRepository
}

public async Task<ICollection<OrganizationDomain>> GetDomainsByOrganizationId(Guid orgId)
=> await _organizationDomainRepository.GetDomainsByOrganizationId(orgId);
=> await _organizationDomainRepository.GetDomainsByOrganizationIdAsync(orgId);
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
namespace Bit.Core.OrganizationFeatures.OrganizationDomains.Interfaces;
using Bit.Core.Entities;

namespace Bit.Core.OrganizationFeatures.OrganizationDomains.Interfaces;

public interface IVerifyOrganizationDomainCommand
{
Task<bool> VerifyOrganizationDomain(Guid id);
Task<OrganizationDomain> VerifyOrganizationDomain(Guid id);
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
using Bit.Core.Exceptions;
using Bit.Core.Entities;
using Bit.Core.Enums;
using Bit.Core.Exceptions;
using Bit.Core.OrganizationFeatures.OrganizationDomains.Interfaces;
using Bit.Core.Repositories;
using Bit.Core.Services;
Expand All @@ -10,19 +12,22 @@ public class VerifyOrganizationDomainCommand : IVerifyOrganizationDomainCommand
{
private readonly IOrganizationDomainRepository _organizationDomainRepository;
private readonly IDnsResolverService _dnsResolverService;
private readonly IEventService _eventService;
private readonly ILogger<VerifyOrganizationDomainCommand> _logger;

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

public async Task<bool> VerifyOrganizationDomain(Guid id)
public async Task<OrganizationDomain> VerifyOrganizationDomain(Guid id)
{
var domain = await _organizationDomainRepository.GetByIdAsync(id);
if (domain is null)
Expand All @@ -39,17 +44,17 @@ public async Task<bool> VerifyOrganizationDomain(Guid id)
if (await _dnsResolverService.ResolveAsync(domain.DomainName, domain.Txt))
{
domain.SetVerifiedDate();
domain.SetLastCheckedDate();
await _organizationDomainRepository.ReplaceAsync(domain);

return true;
await _eventService.LogOrganizationDomainEventAsync(domain, EventType.OrganizationDomain_Verified);
}
}
catch (Exception e)
{
_logger.LogError("Error verifying Organization domain.", e);
return false;
}

return false;
await _eventService.LogOrganizationDomainEventAsync(domain, EventType.OrganizationDomain_NotVerified);
return domain;
}
}
5 changes: 3 additions & 2 deletions src/Core/Repositories/IOrganizationDomainRepository.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@ namespace Bit.Core.Repositories;
public interface IOrganizationDomainRepository : IRepository<OrganizationDomain, Guid>
{
Task<ICollection<OrganizationDomain>> GetClaimedDomainsByDomainNameAsync(string domainName);
Task<ICollection<OrganizationDomain>> GetDomainsByOrganizationId(Guid orgId);
Task<ICollection<OrganizationDomain>> GetDomainsByOrganizationIdAsync(Guid orgId);
Task<ICollection<OrganizationDomain>> GetManyByNextRunDateAsync(DateTime date);
Task<OrganizationDomainSsoDetailsData> GetOrganizationDomainSsoDetails(string email);
Task<OrganizationDomainSsoDetailsData> GetOrganizationDomainSsoDetailsAsync(string email);
Task<OrganizationDomain> GetDomainByOrgIdAndDomainNameAsync(Guid orgId, string domainName);
}
2 changes: 2 additions & 0 deletions src/Core/Services/IEventService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -21,4 +21,6 @@ public interface IEventService
Task LogProviderUserEventAsync(ProviderUser providerUser, EventType type, DateTime? date = null);
Task LogProviderUsersEventAsync(IEnumerable<(ProviderUser, EventType, DateTime?)> events);
Task LogProviderOrganizationEventAsync(ProviderOrganization providerOrganization, EventType type, DateTime? date = null);
Task LogOrganizationDomainEventAsync(OrganizationDomain organizationDomain, EventType type, DateTime? date = null);
Task LogOrganizationDomainEventAsync(OrganizationDomain organizationDomain, EventType type, EventSystemUser systemUser, DateTime? date = null);
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
namespace Bit.Core.Services;

public interface IVerificationDomainService
public interface IOrganizationDomainVerificationService
{
Task ValidateOrganizationsDomainAsync();
}
Loading

0 comments on commit 3b3b5d4

Please sign in to comment.