Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
{{#>SecurityTasksHtmlLayout}}
<table width="100%" border="0" style="display: block; padding: 30px;" align="center" cellpadding="0" cellspacing="0">
<tr>
<td
style="font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; font-style: normal; font-weight: 400; font-size: 16px; line-height: 24px;">
Keep you and your organization safe by changing passwords that are weak, reused, or exposed in a data breach.
</td>
</tr>
<tr>
<td
style="padding-top: 24px; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; font-style: normal; font-weight: 400; font-size: 16px; line-height: 24px;">
Launch the Bitwarden extension to review your at-risk passwords.
</td>
</tr>
</table>
<table width="100%" border="0" cellpadding="0" cellspacing="0" style="padding-bottom: 24px; padding-left: 24px; padding-right: 24px; text-align: center;" align="center">
<tr>
<td>
<a href="{{ReviewPasswordsUrl}}" clicktracking=off target="_blank"
style="display: inline-block; font-weight: bold; color: #ffffff; text-decoration: none; text-align: center; cursor: pointer; border-radius: 999px; background-color: #175DDC; border-color: #175DDC; border-style: solid; border-width: 10px 20px; margin: 0; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 16px; line-height: 25px; -webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none;">
Review at-risk passwords
</a>
</td>
</tr>
</table>
<table width="100%" border="0" cellpadding="0" cellspacing="0" style="padding-bottom: 24px; padding-left: 24px; padding-right: 24px; text-align: center;" align="center">
<tr>
<td display="font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; font-style: normal; font-weight: 400; font-size: 12px; line-height: 16px;">
{{formatAdminOwnerEmails AdminOwnerEmails}}
</td>
</tr>
</table>
{{/SecurityTasksHtmlLayout}}
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
{{#>SecurityTasksHtmlLayout}}
Keep you and your organization safe by changing passwords that are weak, reused, or exposed in a data breach.

Launch the Bitwarden extension to review your at-risk passwords.

Review at-risk passwords ({{{ReviewPasswordsUrl}}})

{{#if AdminOwnerEmails.[0]}}
{{#if AdminOwnerEmails.[1]}}
This request was initiated by
{{#each AdminOwnerEmails}}
{{#if @last}}and {{/if}}{{this}}{{#unless @last}}, {{/unless}}
{{/each}}.
{{else}}
This request was initiated by {{AdminOwnerEmails.[0]}}.
{{/if}}
{{/if}}
{{/SecurityTasksHtmlLayout}}
6 changes: 4 additions & 2 deletions src/Core/Platform/Mail/HandlebarsMailService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1617,8 +1617,10 @@ public async Task SendDeviceApprovalRequestedNotificationEmailAsync(IEnumerable<
await _mailDeliveryService.SendEmailAsync(message);
}

public async Task SendBulkSecurityTaskNotificationsAsync(Organization org, IEnumerable<UserSecurityTasksCount> securityTaskNotifications, IEnumerable<string> adminOwnerEmails)
public async Task SendBulkSecurityTaskNotificationsAsync(Organization org, IEnumerable<UserSecurityTasksCount> securityTaskNotifications, IEnumerable<string> adminOwnerEmails, bool useV2Template = false)
{
var templateName = useV2Template ? "SecurityTasksNotificationV2" : "SecurityTasksNotification";

MailQueueMessage CreateMessage(UserSecurityTasksCount notification)
{
var sanitizedOrgName = CoreHelpers.SanitizeForEmail(org.DisplayName(), false);
Expand All @@ -1631,7 +1633,7 @@ MailQueueMessage CreateMessage(UserSecurityTasksCount notification)
WebVaultUrl = _globalSettings.BaseServiceUri.VaultWithHash,
};
message.Category = "SecurityTasksNotification";
return new MailQueueMessage(message, "SecurityTasksNotification", model);
return new MailQueueMessage(message, templateName, model);
}
var messageModels = securityTaskNotifications.Select(CreateMessage);
await EnqueueMailAsync(messageModels.ToList());
Expand Down
2 changes: 1 addition & 1 deletion src/Core/Platform/Mail/IMailService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -144,5 +144,5 @@ Task SendFamiliesForEnterpriseRemoveSponsorshipsEmailAsync(string email, string
#nullable enable
Task SendClaimedDomainUserEmailAsync(ClaimedUserDomainClaimedEmails emailList);
Task SendDeviceApprovalRequestedNotificationEmailAsync(IEnumerable<string> adminEmails, Guid organizationId, string email, string? userName);
Task SendBulkSecurityTaskNotificationsAsync(Organization org, IEnumerable<UserSecurityTasksCount> securityTaskNotifications, IEnumerable<string> adminOwnerEmails);
Task SendBulkSecurityTaskNotificationsAsync(Organization org, IEnumerable<UserSecurityTasksCount> securityTaskNotifications, IEnumerable<string> adminOwnerEmails, bool useV2Template = false);
}
2 changes: 1 addition & 1 deletion src/Core/Platform/Mail/NoopMailService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -354,7 +354,7 @@ public Task SendDeviceApprovalRequestedNotificationEmailAsync(IEnumerable<string
return Task.FromResult(0);
}

public Task SendBulkSecurityTaskNotificationsAsync(Organization org, IEnumerable<UserSecurityTasksCount> securityTaskNotifications, IEnumerable<string> adminOwnerEmails)
public Task SendBulkSecurityTaskNotificationsAsync(Organization org, IEnumerable<UserSecurityTasksCount> securityTaskNotifications, IEnumerable<string> adminOwnerEmails, bool useV2Template = false)
{
return Task.FromResult(0);
}
Expand Down
10 changes: 8 additions & 2 deletions src/Core/Vault/Commands/CreateManyTaskNotificationsCommand.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
// FIXME: Update this file to be null safe and then delete the line below
#nullable disable

using Bit.Core;
using Bit.Core.Enums;
using Bit.Core.NotificationCenter.Commands.Interfaces;
using Bit.Core.NotificationCenter.Entities;
Expand All @@ -21,21 +22,24 @@
private readonly ICreateNotificationCommand _createNotificationCommand;
private readonly IPushNotificationService _pushNotificationService;
private readonly IOrganizationUserRepository _organizationUserRepository;
private readonly IFeatureService _featureService;

Check warning on line 25 in src/Core/Vault/Commands/CreateManyTaskNotificationsCommand.cs

View workflow job for this annotation

GitHub Actions / Run tests

'IFeatureService' is obsolete: 'Use Bitwarden.Server.Sdk.Features.IFeatureService instead'

public CreateManyTaskNotificationsCommand(
IGetSecurityTasksNotificationDetailsQuery getSecurityTasksNotificationDetailsQuery,
IOrganizationRepository organizationRepository,
IMailService mailService,
ICreateNotificationCommand createNotificationCommand,
IPushNotificationService pushNotificationService,
IOrganizationUserRepository organizationUserRepository)
IOrganizationUserRepository organizationUserRepository,
IFeatureService featureService)

Check warning on line 34 in src/Core/Vault/Commands/CreateManyTaskNotificationsCommand.cs

View workflow job for this annotation

GitHub Actions / Run tests

'IFeatureService' is obsolete: 'Use Bitwarden.Server.Sdk.Features.IFeatureService instead'
{
_getSecurityTasksNotificationDetailsQuery = getSecurityTasksNotificationDetailsQuery;
_organizationRepository = organizationRepository;
_mailService = mailService;
_createNotificationCommand = createNotificationCommand;
_pushNotificationService = pushNotificationService;
_organizationUserRepository = organizationUserRepository;
_featureService = featureService;
}

public async Task CreateAsync(Guid orgId, IEnumerable<SecurityTask> securityTasks)
Expand All @@ -62,7 +66,9 @@
// Ensure proper deserialization of emails
var orgAdminAndOwnerEmails = orgAdminEmails.Concat(orgOwnerEmails).Distinct().ToList();

await _mailService.SendBulkSecurityTaskNotificationsAsync(organization, userTaskCount, orgAdminAndOwnerEmails);
var useV2Template = _featureService.IsEnabled(FeatureFlagKeys.VFO1Foundation);

await _mailService.SendBulkSecurityTaskNotificationsAsync(organization, userTaskCount, orgAdminAndOwnerEmails, useV2Template);

// Break securityTaskCiphers into separate lists by user Id
var securityTaskCiphersByUser = securityTaskCiphers.GroupBy(x => x.UserId)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
using Bit.Core.AdminConsole.Entities;
using Bit.Core.Repositories;
using Bit.Core.Services;
using Bit.Core.Vault.Entities;
using Bit.Core.Vault.Models.Data;
using Bit.Core.Vault.Queries;
using Bit.Test.Common.AutoFixture;
using Bit.Test.Common.AutoFixture.Attributes;
using NSubstitute;
using Xunit;

namespace Bit.Core.Test.Vault.Commands;

[SutProviderCustomize]
public class CreateManyTaskNotificationsCommandTest
{
[Theory]
[BitAutoData]
public async Task CreateAsync_VFO1FoundationDisabled_UsesV1Template(
SutProvider<CreateManyTaskNotificationsCommand> sutProvider,
Organization organization,
ICollection<SecurityTask> securityTasks,
UserSecurityTaskCipher userSecurityTaskCipher)
{
userSecurityTaskCipher.UserId = Guid.NewGuid();
Setup(sutProvider, organization, userSecurityTaskCipher, vfo1FoundationEnabled: false);

await sutProvider.Sut.CreateAsync(organization.Id, securityTasks);

await sutProvider.GetDependency<IMailService>().Received(1)
.SendBulkSecurityTaskNotificationsAsync(
organization,
Arg.Any<IEnumerable<UserSecurityTasksCount>>(),
Arg.Any<IEnumerable<string>>(),
false);
}

[Theory]
[BitAutoData]
public async Task CreateAsync_VFO1FoundationEnabled_UsesV2Template(
SutProvider<CreateManyTaskNotificationsCommand> sutProvider,
Organization organization,
ICollection<SecurityTask> securityTasks,
UserSecurityTaskCipher userSecurityTaskCipher)
{
userSecurityTaskCipher.UserId = Guid.NewGuid();
Setup(sutProvider, organization, userSecurityTaskCipher, vfo1FoundationEnabled: true);

await sutProvider.Sut.CreateAsync(organization.Id, securityTasks);

await sutProvider.GetDependency<IMailService>().Received(1)
.SendBulkSecurityTaskNotificationsAsync(
organization,
Arg.Any<IEnumerable<UserSecurityTasksCount>>(),
Arg.Any<IEnumerable<string>>(),
true);
}

private static void Setup(
SutProvider<CreateManyTaskNotificationsCommand> sutProvider,
Organization organization,
UserSecurityTaskCipher userSecurityTaskCipher,
bool vfo1FoundationEnabled)
{
sutProvider.GetDependency<IFeatureService>()
.IsEnabled(FeatureFlagKeys.VFO1Foundation)
.Returns(vfo1FoundationEnabled);
sutProvider.GetDependency<IGetSecurityTasksNotificationDetailsQuery>()
.GetNotificationDetailsByManyIds(organization.Id, Arg.Any<IEnumerable<SecurityTask>>())
.Returns(new List<UserSecurityTaskCipher> { userSecurityTaskCipher });
sutProvider.GetDependency<IOrganizationRepository>()
.GetByIdAsync(organization.Id)
.Returns(organization);
}
}
Loading