From 2cf7f18858fc8c3e2bbccafdd64f65e86ba4558a Mon Sep 17 00:00:00 2001 From: Matt Portune Date: Mon, 18 May 2020 16:06:34 -0400 Subject: [PATCH] License expiration email for self-hosted org/premium accounts --- .../Handlebars/LicenseExpired.html.hbs | 18 ++++++++++++++++++ .../Handlebars/LicenseExpired.text.hbs | 9 +++++++++ .../Models/Mail/LicenseExpiredViewModel.cs | 7 +++++++ src/Core/Services/IMailService.cs | 1 + .../Implementations/HandlebarsMailService.cs | 12 ++++++++++++ .../Implementations/LicensingService.cs | 7 +++++++ .../NoopImplementations/NoopMailService.cs | 5 +++++ .../Services/LicensingServiceTests.cs | 3 +++ 8 files changed, 62 insertions(+) create mode 100644 src/Core/MailTemplates/Handlebars/LicenseExpired.html.hbs create mode 100644 src/Core/MailTemplates/Handlebars/LicenseExpired.text.hbs create mode 100644 src/Core/Models/Mail/LicenseExpiredViewModel.cs diff --git a/src/Core/MailTemplates/Handlebars/LicenseExpired.html.hbs b/src/Core/MailTemplates/Handlebars/LicenseExpired.html.hbs new file mode 100644 index 000000000..421b1ef49 --- /dev/null +++ b/src/Core/MailTemplates/Handlebars/LicenseExpired.html.hbs @@ -0,0 +1,18 @@ +{{#>FullHtmlLayout}} + + + + + + + +
+ {{#if IsOrganization}} + This email is to notify you that your Bitwarden organization license has expired and must be updated for continued use. See the following article for details about replacing your license file: + {{else}} + This email is to notify you that your Bitwarden premium license has expired and must be updated for continued use. See the following article for details about replacing your license file: + {{/if}} +
+ {{{link 'https://bitwarden.com/help/article/licensing-on-premise/'}}} +
+{{/FullHtmlLayout}} diff --git a/src/Core/MailTemplates/Handlebars/LicenseExpired.text.hbs b/src/Core/MailTemplates/Handlebars/LicenseExpired.text.hbs new file mode 100644 index 000000000..f010f457e --- /dev/null +++ b/src/Core/MailTemplates/Handlebars/LicenseExpired.text.hbs @@ -0,0 +1,9 @@ +{{#>BasicTextLayout}} +{{#if IsOrganization}} +This email is to notify you that your Bitwarden organization license has expired and must be updated for continued use. See the following article for details about replacing your license file: +{{else}} +This email is to notify you that your Bitwarden premium license has expired and must be updated for continued use. See the following article for details about replacing your license file: +{{/if}} + +https://bitwarden.com/help/article/licensing-on-premise/ +{{/BasicTextLayout}} \ No newline at end of file diff --git a/src/Core/Models/Mail/LicenseExpiredViewModel.cs b/src/Core/Models/Mail/LicenseExpiredViewModel.cs new file mode 100644 index 000000000..1c9be0a10 --- /dev/null +++ b/src/Core/Models/Mail/LicenseExpiredViewModel.cs @@ -0,0 +1,7 @@ +namespace Bit.Core.Models.Mail +{ + public class LicenseExpiredViewModel : BaseMailModel + { + public bool IsOrganization { get; set; } + } +} diff --git a/src/Core/Services/IMailService.cs b/src/Core/Services/IMailService.cs index 72c4e5b01..647f45823 100644 --- a/src/Core/Services/IMailService.cs +++ b/src/Core/Services/IMailService.cs @@ -25,6 +25,7 @@ namespace Bit.Core.Services bool mentionInvoices); Task SendPaymentFailedAsync(string email, decimal amount, bool mentionInvoices); Task SendAddedCreditAsync(string email, decimal amount); + Task SendLicenseExpiredAsync(IEnumerable emails, bool isOrganization); Task SendNewDeviceLoggedInEmail(string email, string deviceType, DateTime timestamp, string ip); Task SendRecoverTwoFactorEmail(string email, DateTime timestamp, string ip); } diff --git a/src/Core/Services/Implementations/HandlebarsMailService.cs b/src/Core/Services/Implementations/HandlebarsMailService.cs index 373995af8..99f764fe1 100644 --- a/src/Core/Services/Implementations/HandlebarsMailService.cs +++ b/src/Core/Services/Implementations/HandlebarsMailService.cs @@ -278,6 +278,18 @@ namespace Bit.Core.Services message.Category = "AddedCredit"; await _mailDeliveryService.SendEmailAsync(message); } + + public async Task SendLicenseExpiredAsync(IEnumerable emails, bool isOrganization) + { + var message = CreateDefaultMessage("License Expired", emails); + var model = new LicenseExpiredViewModel + { + IsOrganization = isOrganization + }; + await AddMessageContentAsync(message, "LicenseExpired", model); + message.Category = "LicenseExpired"; + await _mailDeliveryService.SendEmailAsync(message); + } public async Task SendNewDeviceLoggedInEmail(string email, string deviceType, DateTime timestamp, string ip) { diff --git a/src/Core/Services/Implementations/LicensingService.cs b/src/Core/Services/Implementations/LicensingService.cs index 5b41c6fcc..9918efa26 100644 --- a/src/Core/Services/Implementations/LicensingService.cs +++ b/src/Core/Services/Implementations/LicensingService.cs @@ -24,6 +24,7 @@ namespace Bit.Core.Services private readonly IUserRepository _userRepository; private readonly IOrganizationRepository _organizationRepository; private readonly IOrganizationUserRepository _organizationUserRepository; + private readonly IMailService _mailService; private readonly ILogger _logger; private IDictionary _userCheckCache = new Dictionary(); @@ -32,6 +33,7 @@ namespace Bit.Core.Services IUserRepository userRepository, IOrganizationRepository organizationRepository, IOrganizationUserRepository organizationUserRepository, + IMailService mailService, IWebHostEnvironment environment, ILogger logger, GlobalSettings globalSettings) @@ -39,6 +41,7 @@ namespace Bit.Core.Services _userRepository = userRepository; _organizationRepository = organizationRepository; _organizationUserRepository = organizationUserRepository; + _mailService = mailService; _logger = logger; _globalSettings = globalSettings; @@ -124,6 +127,8 @@ namespace Bit.Core.Services org.ExpirationDate = license?.Expires ?? DateTime.UtcNow; org.RevisionDate = DateTime.UtcNow; await _organizationRepository.ReplaceAsync(org); + + await _mailService.SendLicenseExpiredAsync(new List {org.BillingEmail}, true); } public async Task ValidateUsersAsync() @@ -213,6 +218,8 @@ namespace Bit.Core.Services user.PremiumExpirationDate = license?.Expires ?? DateTime.UtcNow; user.RevisionDate = DateTime.UtcNow; await _userRepository.ReplaceAsync(user); + + await _mailService.SendLicenseExpiredAsync(new List {user.Email}, false); } public bool VerifyLicense(ILicense license) diff --git a/src/Core/Services/NoopImplementations/NoopMailService.cs b/src/Core/Services/NoopImplementations/NoopMailService.cs index bdbb742d7..5a169b31e 100644 --- a/src/Core/Services/NoopImplementations/NoopMailService.cs +++ b/src/Core/Services/NoopImplementations/NoopMailService.cs @@ -88,6 +88,11 @@ namespace Bit.Core.Services return Task.FromResult(0); } + public Task SendLicenseExpiredAsync(IEnumerable emails, bool isOrganization) + { + return Task.FromResult(0); + } + public Task SendNewDeviceLoggedInEmail(string email, string deviceType, DateTime timestamp, string ip) { return Task.FromResult(0); diff --git a/test/Core.Test/Services/LicensingServiceTests.cs b/test/Core.Test/Services/LicensingServiceTests.cs index 025ab1f56..b234a0054 100644 --- a/test/Core.Test/Services/LicensingServiceTests.cs +++ b/test/Core.Test/Services/LicensingServiceTests.cs @@ -16,6 +16,7 @@ namespace Bit.Core.Test.Services private readonly IUserRepository _userRepository; private readonly IOrganizationRepository _organizationRepository; private readonly IOrganizationUserRepository _organizationUserRepository; + private readonly IMailService _mailService; private readonly IWebHostEnvironment _hostingEnvironment; private readonly ILogger _logger; @@ -24,6 +25,7 @@ namespace Bit.Core.Test.Services _userRepository = Substitute.For(); _organizationRepository = Substitute.For(); _organizationUserRepository = Substitute.For(); + _mailService = Substitute.For(); _hostingEnvironment = Substitute.For(); _logger = Substitute.For>(); _globalSettings = new GlobalSettings(); @@ -32,6 +34,7 @@ namespace Bit.Core.Test.Services _userRepository, _organizationRepository, _organizationUserRepository, + _mailService, _hostingEnvironment, _logger, _globalSettings