mirror of
https://github.com/bitwarden/server.git
synced 2026-02-14 21:03:51 +08:00
[PM-27882] Add SendOrganizationConfirmationCommand (#6743)
This commit is contained in:
@@ -1,5 +1,7 @@
|
||||
using Bit.Core.AdminConsole.Models.Data.OrganizationUsers;
|
||||
using Bit.Core.AdminConsole.Entities;
|
||||
using Bit.Core.AdminConsole.Models.Data.OrganizationUsers;
|
||||
using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.Interfaces;
|
||||
using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.OrganizationConfirmation;
|
||||
using Bit.Core.AdminConsole.OrganizationFeatures.Policies;
|
||||
using Bit.Core.AdminConsole.OrganizationFeatures.Policies.PolicyRequirements;
|
||||
using Bit.Core.Entities;
|
||||
@@ -25,6 +27,8 @@ public class AutomaticallyConfirmOrganizationUserCommand(IOrganizationUserReposi
|
||||
IPushNotificationService pushNotificationService,
|
||||
IPolicyRequirementQuery policyRequirementQuery,
|
||||
ICollectionRepository collectionRepository,
|
||||
IFeatureService featureService,
|
||||
ISendOrganizationConfirmationCommand sendOrganizationConfirmationCommand,
|
||||
TimeProvider timeProvider,
|
||||
ILogger<AutomaticallyConfirmOrganizationUserCommand> logger) : IAutomaticallyConfirmOrganizationUserCommand
|
||||
{
|
||||
@@ -143,9 +147,7 @@ public class AutomaticallyConfirmOrganizationUserCommand(IOrganizationUserReposi
|
||||
{
|
||||
var user = await userRepository.GetByIdAsync(request.OrganizationUser!.UserId!.Value);
|
||||
|
||||
await mailService.SendOrganizationConfirmedEmailAsync(request.Organization!.Name,
|
||||
user!.Email,
|
||||
request.OrganizationUser.AccessSecretsManager);
|
||||
await SendOrganizationConfirmedEmailAsync(request.Organization!, user!.Email, request.OrganizationUser.AccessSecretsManager);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
@@ -183,4 +185,23 @@ public class AutomaticallyConfirmOrganizationUserCommand(IOrganizationUserReposi
|
||||
Organization = await organizationRepository.GetByIdAsync(request.OrganizationId)
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sends the organization confirmed email using either the new mailer pattern or the legacy mail service,
|
||||
/// depending on the feature flag.
|
||||
/// </summary>
|
||||
/// <param name="organization">The organization the user was confirmed to.</param>
|
||||
/// <param name="userEmail">The email address of the confirmed user.</param>
|
||||
/// <param name="accessSecretsManager">Whether the user has access to Secrets Manager.</param>
|
||||
internal async Task SendOrganizationConfirmedEmailAsync(Organization organization, string userEmail, bool accessSecretsManager)
|
||||
{
|
||||
if (featureService.IsEnabled(FeatureFlagKeys.OrganizationConfirmationEmail))
|
||||
{
|
||||
await sendOrganizationConfirmationCommand.SendConfirmationAsync(organization, userEmail, accessSecretsManager);
|
||||
}
|
||||
else
|
||||
{
|
||||
await mailService.SendOrganizationConfirmedEmailAsync(organization.Name, userEmail, accessSecretsManager);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
// FIXME: Update this file to be null safe and then delete the line below
|
||||
#nullable disable
|
||||
|
||||
using Bit.Core.AdminConsole.Entities;
|
||||
using Bit.Core.AdminConsole.Enums;
|
||||
using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.Interfaces;
|
||||
using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.OrganizationConfirmation;
|
||||
using Bit.Core.AdminConsole.OrganizationFeatures.Policies;
|
||||
using Bit.Core.AdminConsole.OrganizationFeatures.Policies.Enforcement.AutoConfirm;
|
||||
using Bit.Core.AdminConsole.OrganizationFeatures.Policies.PolicyRequirements;
|
||||
@@ -35,7 +37,7 @@ public class ConfirmOrganizationUserCommand : IConfirmOrganizationUserCommand
|
||||
private readonly IFeatureService _featureService;
|
||||
private readonly ICollectionRepository _collectionRepository;
|
||||
private readonly IAutomaticUserConfirmationPolicyEnforcementValidator _automaticUserConfirmationPolicyEnforcementValidator;
|
||||
|
||||
private readonly ISendOrganizationConfirmationCommand _sendOrganizationConfirmationCommand;
|
||||
public ConfirmOrganizationUserCommand(
|
||||
IOrganizationRepository organizationRepository,
|
||||
IOrganizationUserRepository organizationUserRepository,
|
||||
@@ -50,7 +52,7 @@ public class ConfirmOrganizationUserCommand : IConfirmOrganizationUserCommand
|
||||
IPolicyRequirementQuery policyRequirementQuery,
|
||||
IFeatureService featureService,
|
||||
ICollectionRepository collectionRepository,
|
||||
IAutomaticUserConfirmationPolicyEnforcementValidator automaticUserConfirmationPolicyEnforcementValidator)
|
||||
IAutomaticUserConfirmationPolicyEnforcementValidator automaticUserConfirmationPolicyEnforcementValidator, ISendOrganizationConfirmationCommand sendOrganizationConfirmationCommand)
|
||||
{
|
||||
_organizationRepository = organizationRepository;
|
||||
_organizationUserRepository = organizationUserRepository;
|
||||
@@ -66,8 +68,8 @@ public class ConfirmOrganizationUserCommand : IConfirmOrganizationUserCommand
|
||||
_featureService = featureService;
|
||||
_collectionRepository = collectionRepository;
|
||||
_automaticUserConfirmationPolicyEnforcementValidator = automaticUserConfirmationPolicyEnforcementValidator;
|
||||
_sendOrganizationConfirmationCommand = sendOrganizationConfirmationCommand;
|
||||
}
|
||||
|
||||
public async Task<OrganizationUser> ConfirmUserAsync(Guid organizationId, Guid organizationUserId, string key,
|
||||
Guid confirmingUserId, string defaultUserCollectionName = null)
|
||||
{
|
||||
@@ -170,7 +172,7 @@ public class ConfirmOrganizationUserCommand : IConfirmOrganizationUserCommand
|
||||
orgUser.Email = null;
|
||||
|
||||
await _eventService.LogOrganizationUserEventAsync(orgUser, EventType.OrganizationUser_Confirmed);
|
||||
await _mailService.SendOrganizationConfirmedEmailAsync(organization.DisplayName(), user.Email, orgUser.AccessSecretsManager);
|
||||
await SendOrganizationConfirmedEmailAsync(organization, user.Email, orgUser.AccessSecretsManager);
|
||||
succeededUsers.Add(orgUser);
|
||||
result.Add(Tuple.Create(orgUser, ""));
|
||||
}
|
||||
@@ -339,4 +341,23 @@ public class ConfirmOrganizationUserCommand : IConfirmOrganizationUserCommand
|
||||
|
||||
await _collectionRepository.UpsertDefaultCollectionsAsync(organizationId, eligibleOrganizationUserIds, defaultUserCollectionName);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sends the organization confirmed email using either the new mailer pattern or the legacy mail service,
|
||||
/// depending on the feature flag.
|
||||
/// </summary>
|
||||
/// <param name="organization">The organization the user was confirmed to.</param>
|
||||
/// <param name="userEmail">The email address of the confirmed user.</param>
|
||||
/// <param name="accessSecretsManager">Whether the user has access to Secrets Manager.</param>
|
||||
internal async Task SendOrganizationConfirmedEmailAsync(Organization organization, string userEmail, bool accessSecretsManager)
|
||||
{
|
||||
if (_featureService.IsEnabled(FeatureFlagKeys.OrganizationConfirmationEmail))
|
||||
{
|
||||
await _sendOrganizationConfirmationCommand.SendConfirmationAsync(organization, userEmail, accessSecretsManager);
|
||||
}
|
||||
else
|
||||
{
|
||||
await _mailService.SendOrganizationConfirmedEmailAsync(organization.DisplayName(), userEmail, accessSecretsManager);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
using Bit.Core.AdminConsole.Entities;
|
||||
|
||||
namespace Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.OrganizationConfirmation;
|
||||
|
||||
public interface ISendOrganizationConfirmationCommand
|
||||
{
|
||||
/// <summary>
|
||||
/// Sends an organization confirmation email to the specified user.
|
||||
/// </summary>
|
||||
/// <param name="organization">The organization to send the confirmation email for.</param>
|
||||
/// <param name="userEmail">The email address of the user to send the confirmation to.</param>
|
||||
/// <param name="accessSecretsManager">Whether the user has access to Secrets Manager.</param>
|
||||
Task SendConfirmationAsync(Organization organization, string userEmail, bool accessSecretsManager);
|
||||
|
||||
/// <summary>
|
||||
/// Sends organization confirmation emails to multiple users.
|
||||
/// </summary>
|
||||
/// <param name="organization">The organization to send the confirmation emails for.</param>
|
||||
/// <param name="userEmails">The email addresses of the users to send confirmations to.</param>
|
||||
/// <param name="accessSecretsManager">Whether the users have access to Secrets Manager.</param>
|
||||
Task SendConfirmationsAsync(Organization organization, IEnumerable<string> userEmails, bool accessSecretsManager);
|
||||
}
|
||||
@@ -0,0 +1,110 @@
|
||||
using System.Net;
|
||||
using Bit.Core.AdminConsole.Entities;
|
||||
using Bit.Core.AdminConsole.Models.Mail.Mailer.OrganizationConfirmation;
|
||||
using Bit.Core.Billing.Enums;
|
||||
using Bit.Core.Platform.Mail.Mailer;
|
||||
using Bit.Core.Settings;
|
||||
|
||||
namespace Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.OrganizationConfirmation;
|
||||
|
||||
public class SendOrganizationConfirmationCommand(IMailer mailer, GlobalSettings globalSettings) : ISendOrganizationConfirmationCommand
|
||||
{
|
||||
private const string _titleFirst = "You're confirmed as a member of ";
|
||||
private const string _titleThird = "!";
|
||||
|
||||
private static string GetConfirmationSubject(string organizationName) =>
|
||||
$"You Have Been Confirmed To {organizationName}";
|
||||
private string GetWebVaultUrl(bool accessSecretsManager) => accessSecretsManager
|
||||
? globalSettings.BaseServiceUri.VaultWithHashAndSecretManagerProduct
|
||||
: globalSettings.BaseServiceUri.VaultWithHash;
|
||||
|
||||
public async Task SendConfirmationAsync(Organization organization, string userEmail, bool accessSecretsManager = false)
|
||||
{
|
||||
await SendConfirmationsAsync(organization, [userEmail], accessSecretsManager);
|
||||
}
|
||||
|
||||
public async Task SendConfirmationsAsync(Organization organization, IEnumerable<string> userEmails, bool accessSecretsManager = false)
|
||||
{
|
||||
var userEmailsList = userEmails.ToList();
|
||||
|
||||
if (userEmailsList.Count == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var organizationName = WebUtility.HtmlDecode(organization.Name);
|
||||
|
||||
if (IsEnterpriseOrTeamsPlan(organization.PlanType))
|
||||
{
|
||||
await SendEnterpriseTeamsEmailsAsync(userEmailsList, organizationName, accessSecretsManager);
|
||||
return;
|
||||
}
|
||||
|
||||
await SendFamilyFreeConfirmEmailsAsync(userEmailsList, organizationName, accessSecretsManager);
|
||||
}
|
||||
|
||||
private async Task SendEnterpriseTeamsEmailsAsync(List<string> userEmailsList, string organizationName, bool accessSecretsManager)
|
||||
{
|
||||
var mail = new OrganizationConfirmationEnterpriseTeams
|
||||
{
|
||||
ToEmails = userEmailsList,
|
||||
Subject = GetConfirmationSubject(organizationName),
|
||||
View = new OrganizationConfirmationEnterpriseTeamsView
|
||||
{
|
||||
OrganizationName = organizationName,
|
||||
TitleFirst = _titleFirst,
|
||||
TitleSecondBold = organizationName,
|
||||
TitleThird = _titleThird,
|
||||
WebVaultUrl = GetWebVaultUrl(accessSecretsManager)
|
||||
}
|
||||
};
|
||||
|
||||
await mailer.SendEmail(mail);
|
||||
}
|
||||
|
||||
private async Task SendFamilyFreeConfirmEmailsAsync(List<string> userEmailsList, string organizationName, bool accessSecretsManager)
|
||||
{
|
||||
var mail = new OrganizationConfirmationFamilyFree
|
||||
{
|
||||
ToEmails = userEmailsList,
|
||||
Subject = GetConfirmationSubject(organizationName),
|
||||
View = new OrganizationConfirmationFamilyFreeView
|
||||
{
|
||||
OrganizationName = organizationName,
|
||||
TitleFirst = _titleFirst,
|
||||
TitleSecondBold = organizationName,
|
||||
TitleThird = _titleThird,
|
||||
WebVaultUrl = GetWebVaultUrl(accessSecretsManager)
|
||||
}
|
||||
};
|
||||
|
||||
await mailer.SendEmail(mail);
|
||||
}
|
||||
|
||||
|
||||
private static bool IsEnterpriseOrTeamsPlan(PlanType planType)
|
||||
{
|
||||
return planType switch
|
||||
{
|
||||
PlanType.TeamsMonthly2019 or
|
||||
PlanType.TeamsAnnually2019 or
|
||||
PlanType.TeamsMonthly2020 or
|
||||
PlanType.TeamsAnnually2020 or
|
||||
PlanType.TeamsMonthly2023 or
|
||||
PlanType.TeamsAnnually2023 or
|
||||
PlanType.TeamsStarter2023 or
|
||||
PlanType.TeamsMonthly or
|
||||
PlanType.TeamsAnnually or
|
||||
PlanType.TeamsStarter or
|
||||
PlanType.EnterpriseMonthly2019 or
|
||||
PlanType.EnterpriseAnnually2019 or
|
||||
PlanType.EnterpriseMonthly2020 or
|
||||
PlanType.EnterpriseAnnually2020 or
|
||||
PlanType.EnterpriseMonthly2023 or
|
||||
PlanType.EnterpriseAnnually2023 or
|
||||
PlanType.EnterpriseMonthly or
|
||||
PlanType.EnterpriseAnnually => true,
|
||||
_ => false
|
||||
};
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user