diff --git a/src/Core/AdminConsole/OrganizationFeatures/OrganizationDomains/VerifyOrganizationDomainCommand.cs b/src/Core/AdminConsole/OrganizationFeatures/OrganizationDomains/VerifyOrganizationDomainCommand.cs index dd6add669..2ec2e05e1 100644 --- a/src/Core/AdminConsole/OrganizationFeatures/OrganizationDomains/VerifyOrganizationDomainCommand.cs +++ b/src/Core/AdminConsole/OrganizationFeatures/OrganizationDomains/VerifyOrganizationDomainCommand.cs @@ -22,6 +22,9 @@ public class VerifyOrganizationDomainCommand( IPolicyService policyService, IFeatureService featureService, ICurrentContext currentContext, + IMailService mailService, + IOrganizationUserRepository organizationUserRepository, + IOrganizationRepository organizationRepository, ILogger logger) : IVerifyOrganizationDomainCommand { @@ -50,7 +53,7 @@ public class VerifyOrganizationDomainCommand( return domainVerificationResult; } - public async Task SystemVerifyOrganizationDomainAsync(OrganizationDomain organizationDomain) + public async Task SystemVerifyOrganizationDomainAsync(OrganizationDomain organizationDomain) // need request object to hold who is doing the action { var actingUser = new SystemUser(EventSystemUser.DomainVerification); @@ -109,7 +112,7 @@ public class VerifyOrganizationDomainCommand( { domain.SetVerifiedDate(); - await EnableSingleOrganizationPolicyAsync(domain.OrganizationId, actingUser); + await DomainVerificationSideEffectsAsync(domain.OrganizationId, actingUser); } } catch (Exception e) @@ -121,7 +124,7 @@ public class VerifyOrganizationDomainCommand( return domain; } - private async Task EnableSingleOrganizationPolicyAsync(Guid organizationId, IActingUser actingUser) + private async Task DomainVerificationSideEffectsAsync(Guid organizationId, IActingUser actingUser) { if (featureService.IsEnabled(FeatureFlagKeys.AccountDeprovisioning)) { @@ -131,4 +134,24 @@ public class VerifyOrganizationDomainCommand( eventSystemUser: actingUser is SystemUser systemUser ? systemUser.SystemUserType : null); } } + + private async Task EnableSingleOrganizationPolicyAsync(Guid organizationId, IActingUser actingUser) => + await policyService.SaveAsync( + new Policy { OrganizationId = organizationId, Type = PolicyType.SingleOrg, Enabled = true }, + savingUserId: actingUser is StandardUser standardUser ? standardUser.UserId : null, + eventSystemUser: actingUser is SystemUser systemUser ? systemUser.SystemUserType : null); + + private async Task SendVerifiedDomainUserEmailAsync(Guid organizationId) + { + var orgUsers = await organizationUserRepository.GetManyByOrganizationWithClaimedDomainsAsync(organizationId); + var orgUserUsers = await organizationUserRepository.GetManyDetailsByOrganizationAsync(organizationId); + + var userEmails = orgUsers.Where(x => x.Status != OrganizationUserStatusType.Revoked) + .Select(y => orgUserUsers.FirstOrDefault(x => x.Id == y.Id)?.Email) + .Where(x => x is not null); + + var organization = await organizationRepository.GetByIdAsync(organizationId); + + await Task.WhenAll(userEmails.Select(email => mailService.SendVerifiedDomainUserEmailAsync(email, organization))); + } } diff --git a/src/Core/MailTemplates/Handlebars/AdminConsole/DomainClaimedByOrganization.html.hbs b/src/Core/MailTemplates/Handlebars/AdminConsole/DomainClaimedByOrganization.html.hbs new file mode 100644 index 000000000..e69de29bb diff --git a/src/Core/MailTemplates/Handlebars/AdminConsole/DomainClaimedByOrganization.text.hbs b/src/Core/MailTemplates/Handlebars/AdminConsole/DomainClaimedByOrganization.text.hbs new file mode 100644 index 000000000..e69de29bb diff --git a/src/Core/Models/Mail/VerifiedDomainUserNotificationViewModel.cs b/src/Core/Models/Mail/VerifiedDomainUserNotificationViewModel.cs new file mode 100644 index 000000000..41b17cbd5 --- /dev/null +++ b/src/Core/Models/Mail/VerifiedDomainUserNotificationViewModel.cs @@ -0,0 +1,6 @@ +namespace Bit.Core.Models.Mail; + +public class VerifiedDomainUserNotificationViewModel : BaseMailModel +{ + public string OrganizationName { get; init; } +} diff --git a/src/Core/Services/IMailService.cs b/src/Core/Services/IMailService.cs index bc8d1440f..e1cb4c229 100644 --- a/src/Core/Services/IMailService.cs +++ b/src/Core/Services/IMailService.cs @@ -93,5 +93,6 @@ public interface IMailService Task SendRequestSMAccessToAdminEmailAsync(IEnumerable adminEmails, string organizationName, string userRequestingAccess, string emailContent); Task SendFamiliesForEnterpriseRemoveSponsorshipsEmailAsync(string email, string offerAcceptanceDate, string organizationId, string organizationName); + Task SendVerifiedDomainUserEmailAsync(string email, Organization organization); } diff --git a/src/Core/Services/Implementations/HandlebarsMailService.cs b/src/Core/Services/Implementations/HandlebarsMailService.cs index acc729e53..681321fc3 100644 --- a/src/Core/Services/Implementations/HandlebarsMailService.cs +++ b/src/Core/Services/Implementations/HandlebarsMailService.cs @@ -460,6 +460,19 @@ public class HandlebarsMailService : IMailService await _mailDeliveryService.SendEmailAsync(message); } + public async Task SendVerifiedDomainUserEmailAsync(string email, Organization organization) + { + var message = CreateDefaultMessage($"Your Bitwarden account is claimed by {organization.DisplayName()}", email); + + var model = new VerifiedDomainUserNotificationViewModel + { + OrganizationName = CoreHelpers.SanitizeForEmail(organization.DisplayName(), false) + }; + await AddMessageContentAsync(message, "AdminConsole.VerifiedDomainUserNotification", model); + message.Category = "VerifiedDomainUserNotification"; + await _mailDeliveryService.SendEmailAsync(message); + } + public async Task SendNewDeviceLoggedInEmail(string email, string deviceType, DateTime timestamp, string ip) { var message = CreateDefaultMessage($"New Device Logged In From {deviceType}", email); diff --git a/src/Core/Services/NoopImplementations/NoopMailService.cs b/src/Core/Services/NoopImplementations/NoopMailService.cs index 399874eee..b00ade3e4 100644 --- a/src/Core/Services/NoopImplementations/NoopMailService.cs +++ b/src/Core/Services/NoopImplementations/NoopMailService.cs @@ -309,5 +309,6 @@ public class NoopMailService : IMailService { return Task.FromResult(0); } + public Task SendVerifiedDomainUserEmailAsync(string email, Organization organization) => Task.CompletedTask; }