diff --git a/src/Core/Core.csproj b/src/Core/Core.csproj index 41921a7436..439d6fcc38 100644 --- a/src/Core/Core.csproj +++ b/src/Core/Core.csproj @@ -1,4 +1,4 @@ - + net461 @@ -13,17 +13,29 @@ Bit.Core + + + + + + + + + + + + diff --git a/src/Core/MailTemplates/MasterPasswordHint.cshtml b/src/Core/MailTemplates/MasterPasswordHint.cshtml new file mode 100644 index 0000000000..e1e4060f45 --- /dev/null +++ b/src/Core/MailTemplates/MasterPasswordHint.cshtml @@ -0,0 +1,8 @@ +@model Bit.Core.Models.Mail.MasterPasswordHintViewModel +@{ + Layout = "_BasicMailLayout"; +} +

You (or someone) recently requested your master password hint.

+

Your hint is: "@Model.Hint"

+

Login: @Model.WebVaultUrl

+

If you did not request your master password hint you can safely ignore this email.

diff --git a/src/Core/MailTemplates/MasterPasswordHint.text.cshtml b/src/Core/MailTemplates/MasterPasswordHint.text.cshtml new file mode 100644 index 0000000000..2df848e378 --- /dev/null +++ b/src/Core/MailTemplates/MasterPasswordHint.text.cshtml @@ -0,0 +1,11 @@ +@model Bit.Core.Models.Mail.MasterPasswordHintViewModel +@{ + Layout = "_BasicMailLayout.text"; +} +You (or someone) recently requested your master password hint. + +Your hint is: "@Model.Hint" + +Login: @Model.WebVaultUrl + +If you did not request your master password hint you can safely ignore this email. diff --git a/src/Core/MailTemplates/NoMasterPasswordHint.cshtml b/src/Core/MailTemplates/NoMasterPasswordHint.cshtml new file mode 100644 index 0000000000..965d3ae7ec --- /dev/null +++ b/src/Core/MailTemplates/NoMasterPasswordHint.cshtml @@ -0,0 +1,9 @@ +@model Bit.Core.Models.Mail.BaseMailModel +@{ + Layout = "_BasicMailLayout"; +} +

+ You (or someone) recently requested your master password hint. + Unfortunately, your account does not have a master password hint. +

+

If you did not request your master password hint you can safely ignore this email.

diff --git a/src/Core/MailTemplates/NoMasterPasswordHint.text.cshtml b/src/Core/MailTemplates/NoMasterPasswordHint.text.cshtml new file mode 100644 index 0000000000..6ff6cdd03c --- /dev/null +++ b/src/Core/MailTemplates/NoMasterPasswordHint.text.cshtml @@ -0,0 +1,7 @@ +@model Bit.Core.Models.Mail.BaseMailModel +@{ + Layout = "_BasicMailLayout"; +} +You (or someone) recently requested your master password hint. Unfortunately, your account does not have a master password hint. + +If you did not request your master password hint you can safely ignore this email. diff --git a/src/Core/MailTemplates/Welcome.cshtml b/src/Core/MailTemplates/Welcome.cshtml new file mode 100644 index 0000000000..3938405461 --- /dev/null +++ b/src/Core/MailTemplates/Welcome.cshtml @@ -0,0 +1,10 @@ +@model Bit.Core.Models.Mail.BaseMailModel +@{ + Layout = "_BasicMailLayout"; +} + + + + + + \ No newline at end of file diff --git a/src/Core/MailTemplates/_BasicMailLayout.cshtml b/src/Core/MailTemplates/_BasicMailLayout.cshtml new file mode 100644 index 0000000000..98272d8d19 --- /dev/null +++ b/src/Core/MailTemplates/_BasicMailLayout.cshtml @@ -0,0 +1,12 @@ + + + + + + + + @RenderBody() + Regards,
+ The bitwarden team + + diff --git a/src/Core/MailTemplates/_BasicMailLayout.text.cshtml b/src/Core/MailTemplates/_BasicMailLayout.text.cshtml new file mode 100644 index 0000000000..cc72885a74 --- /dev/null +++ b/src/Core/MailTemplates/_BasicMailLayout.text.cshtml @@ -0,0 +1,3 @@ +@RenderBody() +Regards, +The bitwarden team \ No newline at end of file diff --git a/src/Core/Models/Mail/BaseMailModel.cs b/src/Core/Models/Mail/BaseMailModel.cs new file mode 100644 index 0000000000..914a48ca1d --- /dev/null +++ b/src/Core/Models/Mail/BaseMailModel.cs @@ -0,0 +1,14 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Bit.Core.Models.Mail +{ + public class BaseMailModel + { + public string SiteName { get; set; } + public string WebVaultUrl { get; set; } + } +} diff --git a/src/Core/Models/Mail/MailMessage.cs b/src/Core/Models/Mail/MailMessage.cs new file mode 100644 index 0000000000..82e4882543 --- /dev/null +++ b/src/Core/Models/Mail/MailMessage.cs @@ -0,0 +1,13 @@ +using System.Collections.Generic; + +namespace Bit.Core.Models.Mail +{ + public class MailMessage + { + public string Subject { get; set; } + public IEnumerable ToEmails { get; set; } + public string HtmlContent { get; set; } + public string TextContent { get; set; } + public IDictionary MetaData { get; set; } + } +} diff --git a/src/Core/Models/Mail/MasterPasswordHintViewModel.cs b/src/Core/Models/Mail/MasterPasswordHintViewModel.cs new file mode 100644 index 0000000000..d2cfff49ed --- /dev/null +++ b/src/Core/Models/Mail/MasterPasswordHintViewModel.cs @@ -0,0 +1,7 @@ +namespace Bit.Core.Models.Mail +{ + public class MasterPasswordHintViewModel : BaseMailModel + { + public string Hint { get; set; } + } +} diff --git a/src/Core/Program.cs b/src/Core/Program.cs new file mode 100644 index 0000000000..7bd9d1bc60 --- /dev/null +++ b/src/Core/Program.cs @@ -0,0 +1,9 @@ +namespace Bit.Core +{ + public class Program + { + public static void Main(string[] args) + { + } + } +} diff --git a/src/Core/Services/IMailDeliveryService.cs b/src/Core/Services/IMailDeliveryService.cs new file mode 100644 index 0000000000..bcac8189c0 --- /dev/null +++ b/src/Core/Services/IMailDeliveryService.cs @@ -0,0 +1,10 @@ +using System.Threading.Tasks; +using Bit.Core.Models.Mail; + +namespace Bit.Core.Services +{ + public interface IMailDeliveryService + { + Task SendEmailAsync(MailMessage message); + } +} diff --git a/src/Core/Services/Implementations/RazorMailService.cs b/src/Core/Services/Implementations/RazorMailService.cs new file mode 100644 index 0000000000..c42c910b6b --- /dev/null +++ b/src/Core/Services/Implementations/RazorMailService.cs @@ -0,0 +1,132 @@ +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using Bit.Core.Models.Table; +using RazorLight; +using Bit.Core.Models.Mail; +using RazorLight.Templating; +using System.IO; + +namespace Bit.Core.Services +{ + public class RazorMailService : IMailService + { + private readonly GlobalSettings _globalSettings; + private readonly IRazorLightEngine _engine; + private readonly IMailDeliveryService _mailDeliveryService; + + public RazorMailService( + GlobalSettings globalSettings, + IMailDeliveryService mailDeliveryService) + { + _globalSettings = globalSettings; + _mailDeliveryService = mailDeliveryService; + + var manager = new CustomEmbeddedResourceTemplateManager("Bit.Core.MailTemplates"); + var core = new EngineCore(manager, EngineConfiguration.Default); + var pageFactory = new DefaultPageFactory(core.KeyCompile); + var lookup = new DefaultPageLookup(pageFactory); + _engine = new RazorLightEngine(core, lookup); + } + + public Task SendChangeEmailAlreadyExistsEmailAsync(string fromEmail, string toEmail) + { + throw new NotImplementedException(); + } + + public Task SendChangeEmailEmailAsync(string newEmailAddress, string token) + { + throw new NotImplementedException(); + } + + public async Task SendMasterPasswordHintEmailAsync(string email, string hint) + { + var message = CreateDefaultMessage("Your Master Password Hint", email); + var model = new MasterPasswordHintViewModel + { + Hint = hint + }; + message.HtmlContent = _engine.Parse("MasterPasswordHint", model); + message.TextContent = _engine.Parse("MasterPasswordHint.text", model); + await _mailDeliveryService.SendEmailAsync(message); + } + + public async Task SendNoMasterPasswordHintEmailAsync(string email) + { + var message = CreateDefaultMessage("Your Master Password Hint", email); + var model = new BaseMailModel(); + message.HtmlContent = _engine.Parse("NoMasterPasswordHint", model); + message.TextContent = _engine.Parse("NoMasterPasswordHint.text", model); + await _mailDeliveryService.SendEmailAsync(message); + } + + public Task SendOrganizationAcceptedEmailAsync(string organizationName, string userEmail, IEnumerable adminEmails) + { + throw new NotImplementedException(); + } + + public Task SendOrganizationConfirmedEmailAsync(string organizationName, string email) + { + throw new NotImplementedException(); + } + + public Task SendOrganizationInviteEmailAsync(string organizationName, OrganizationUser orgUser, string token) + { + throw new NotImplementedException(); + } + + public Task SendWelcomeEmailAsync(User user) + { + throw new NotImplementedException(); + } + + private MailMessage CreateDefaultMessage(string subject, string toEmail) + { + return CreateDefaultMessage(subject, new List { toEmail }); + } + + private MailMessage CreateDefaultMessage(string subject, IEnumerable toEmails) + { + var message = new MailMessage + { + MetaData = new Dictionary(), + ToEmails = toEmails, + Subject = subject + }; + + return message; + } + + public class CustomEmbeddedResourceTemplateManager : ITemplateManager + { + public CustomEmbeddedResourceTemplateManager(string rootNamespace) + { + if(rootNamespace == null) + { + throw new ArgumentNullException(nameof(rootNamespace)); + } + + Namespace = rootNamespace; + } + + public string Namespace { get; } + + public ITemplateSource Resolve(string key) + { + var assembly = GetType().Assembly; + using(var stream = assembly.GetManifestResourceStream(Namespace + "." + key + ".cshtml")) + { + if(stream == null) + { + throw new RazorLightException(string.Format("Couldn't load resource '{0}.{1}.cshtml'.", Namespace, key)); + } + + using(var reader = new StreamReader(stream)) + { + return new LoadedTemplateSource(reader.ReadToEnd()); + } + } + } + } + } +} diff --git a/src/Core/Services/Implementations/SendGridMailDeliveryService.cs b/src/Core/Services/Implementations/SendGridMailDeliveryService.cs new file mode 100644 index 0000000000..3dc5570a38 --- /dev/null +++ b/src/Core/Services/Implementations/SendGridMailDeliveryService.cs @@ -0,0 +1,62 @@ +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using SendGrid; +using SendGrid.Helpers.Mail; +using Bit.Core.Models.Mail; +using System.Linq; + +namespace Bit.Core.Services +{ + public class SendGridMailDeliveryService : IMailDeliveryService + { + private readonly GlobalSettings _globalSettings; + private readonly SendGridClient _client; + + public SendGridMailDeliveryService(GlobalSettings globalSettings) + { + _globalSettings = globalSettings; + _client = new SendGridClient(_globalSettings.Mail.ApiKey); + } + + public async Task SendEmailAsync(MailMessage message) + { + var sendGridMessage = new SendGridMessage + { + Subject = message.Subject, + From = new EmailAddress(_globalSettings.Mail.ReplyToEmail, _globalSettings.SiteName), + HtmlContent = message.HtmlContent, + PlainTextContent = message.TextContent, + }; + + sendGridMessage.AddTos(message.ToEmails.Select(e => new EmailAddress(e)).ToList()); + + if(message.MetaData.ContainsKey("SendGridTemplateId")) + { + sendGridMessage.HtmlContent = " "; + sendGridMessage.PlainTextContent = " "; + sendGridMessage.TemplateId = message.MetaData["SendGridTemplateId"].ToString(); + } + + if(message.MetaData.ContainsKey("SendGridSubstitutions")) + { + var subs = message.MetaData["SendGridSubstitutions"] as Dictionary; + sendGridMessage.AddSubstitutions(subs); + } + + if(message.MetaData.ContainsKey("SendGridCategories")) + { + var cats = message.MetaData["SendGridCategories"] as List; + sendGridMessage.AddCategories(cats); + } + + if(message.MetaData.ContainsKey("SendGridBypassListManagement")) + { + var bypass = message.MetaData["SendGridBypassListManagement"] as bool?; + sendGridMessage.SetBypassListManagement(bypass.GetValueOrDefault(false)); + } + + await _client.SendEmailAsync(sendGridMessage); + } + } +} diff --git a/src/Core/Services/Implementations/SendGridMailService.cs b/src/Core/Services/Implementations/SendGridMailService.cs deleted file mode 100644 index 777dfe4d56..0000000000 --- a/src/Core/Services/Implementations/SendGridMailService.cs +++ /dev/null @@ -1,158 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Threading.Tasks; -using Bit.Core.Models.Table; -using SendGrid; -using SendGrid.Helpers.Mail; -using System.Net; -using System.Linq; - -namespace Bit.Core.Services -{ - public class SendGridMailService : IMailService - { - private const string WelcomeTemplateId = "045f8ad5-5547-4fa2-8d3d-6d46e401164d"; - private const string ChangeEmailAlreadyExistsTemplateId = "b69d2038-6ad9-4cf6-8f7f-7880921cba43"; - private const string ChangeEmailTemplateId = "ec2c1471-8292-4f17-b6b6-8223d514f86e"; - private const string NoMasterPasswordHintTemplateId = "136eb299-e102-495a-88bd-f96736eea159"; - private const string MasterPasswordHintTemplateId = "be77cfde-95dd-4cb9-b5e0-8286b53885f1"; - private const string OrganizationInviteTemplateId = "1eff5512-e36c-49a8-b9e2-2b215d6bbced"; - private const string OrganizationAcceptedTemplateId = "28f7f741-598e-449c-85fe-601e1cc32ba3"; - private const string OrganizationConfirmedTemplateId = "a8afe2a0-6161-4eb9-b40c-08a7f520ec50"; - - private const string AdministrativeCategoryName = "Administrative"; - private const string MarketingCategoryName = "Marketing"; - - private readonly GlobalSettings _globalSettings; - private readonly SendGridClient _client; - - public SendGridMailService(GlobalSettings globalSettings) - { - _globalSettings = globalSettings; - _client = new SendGridClient(_globalSettings.Mail.ApiKey); - } - - public async Task SendWelcomeEmailAsync(User user) - { - var message = CreateDefaultMessage(WelcomeTemplateId); - - message.Subject = "Welcome"; - message.AddTo(new EmailAddress(user.Email)); - message.AddCategories(new List { AdministrativeCategoryName, "Welcome" }); - - await _client.SendEmailAsync(message); - } - - public async Task SendChangeEmailAlreadyExistsEmailAsync(string fromEmail, string toEmail) - { - var message = CreateDefaultMessage(ChangeEmailAlreadyExistsTemplateId); - - message.Subject = "Your Email Change"; - message.AddTo(new EmailAddress(toEmail)); - message.AddSubstitution("{{fromEmail}}", fromEmail); - message.AddSubstitution("{{toEmail}}", toEmail); - message.AddCategories(new List { AdministrativeCategoryName, "Change Email Alrady Exists" }); - - await _client.SendEmailAsync(message); - } - - public async Task SendChangeEmailEmailAsync(string newEmailAddress, string token) - { - var message = CreateDefaultMessage(ChangeEmailTemplateId); - - message.Subject = "Change Your Email"; - message.AddTo(new EmailAddress(newEmailAddress)); - message.AddSubstitution("{{token}}", Uri.EscapeDataString(token)); - message.AddCategories(new List { AdministrativeCategoryName, "Change Email" }); - message.SetBypassListManagement(true); - - await _client.SendEmailAsync(message); - } - - public async Task SendNoMasterPasswordHintEmailAsync(string email) - { - var message = CreateDefaultMessage(NoMasterPasswordHintTemplateId); - - message.Subject = "Your Master Password Hint"; - message.AddTo(new EmailAddress(email)); - message.AddCategories(new List { AdministrativeCategoryName, "No Master Password Hint" }); - - await _client.SendEmailAsync(message); - } - - public async Task SendMasterPasswordHintEmailAsync(string email, string hint) - { - var message = CreateDefaultMessage(MasterPasswordHintTemplateId); - - message.Subject = "Your Master Password Hint"; - message.AddTo(new EmailAddress(email)); - message.AddSubstitution("{{hint}}", hint); - message.AddCategories(new List { AdministrativeCategoryName, "Master Password Hint" }); - - await _client.SendEmailAsync(message); - } - - public async Task SendOrganizationInviteEmailAsync(string organizationName, OrganizationUser orgUser, string token) - { - var message = CreateDefaultMessage(OrganizationInviteTemplateId); - - message.Subject = $"Join {organizationName}"; - message.AddTo(new EmailAddress(orgUser.Email)); - message.AddSubstitution("{{organizationName}}", organizationName); - message.AddSubstitution("{{organizationId}}", orgUser.OrganizationId.ToString()); - message.AddSubstitution("{{organizationUserId}}", orgUser.Id.ToString()); - message.AddSubstitution("{{token}}", token); - message.AddSubstitution("{{email}}", WebUtility.UrlEncode(orgUser.Email)); - message.AddSubstitution("{{organizationNameUrlEncoded}}", WebUtility.UrlEncode(organizationName)); - message.AddCategories(new List { AdministrativeCategoryName, "Organization User Invite" }); - - await _client.SendEmailAsync(message); - } - - public async Task SendOrganizationAcceptedEmailAsync(string organizationName, string userEmail, - IEnumerable adminEmails) - { - var message = CreateDefaultMessage(OrganizationAcceptedTemplateId); - - message.Subject = $"User {userEmail} Has Accepted Invite"; - message.AddTos(adminEmails.Select(e => new EmailAddress(e)).ToList()); - message.AddSubstitution("{{userEmail}}", userEmail); - message.AddSubstitution("{{organizationName}}", organizationName); - message.AddCategories(new List { AdministrativeCategoryName, "Organization User Accepted" }); - - await _client.SendEmailAsync(message); - } - - public async Task SendOrganizationConfirmedEmailAsync(string organizationName, string email) - { - var message = CreateDefaultMessage(OrganizationConfirmedTemplateId); - - message.Subject = $"You Have Been Confirmed To {organizationName}"; - message.AddTo(new EmailAddress(email)); - message.AddSubstitution("{{organizationName}}", organizationName); - message.AddCategories(new List { AdministrativeCategoryName, "Organization User Confirmed" }); - - await _client.SendEmailAsync(message); - } - - private SendGridMessage CreateDefaultMessage(string templateId) - { - var message = new SendGridMessage - { - From = new EmailAddress(_globalSettings.Mail.ReplyToEmail, _globalSettings.SiteName), - HtmlContent = " ", - PlainTextContent = " " - }; - - if(!string.IsNullOrWhiteSpace(templateId)) - { - message.TemplateId = templateId; - } - - message.AddSubstitution("{{siteName}}", _globalSettings.SiteName); - message.AddSubstitution("{{baseVaultUri}}", _globalSettings.BaseVaultUri); - - return message; - } - } -} diff --git a/src/Core/Services/Implementations/SendGridTemplateMailService.cs b/src/Core/Services/Implementations/SendGridTemplateMailService.cs new file mode 100644 index 0000000000..00f89d153c --- /dev/null +++ b/src/Core/Services/Implementations/SendGridTemplateMailService.cs @@ -0,0 +1,205 @@ +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using Bit.Core.Models.Table; +using System.Net; +using Bit.Core.Models.Mail; + +namespace Bit.Core.Services +{ + public class SendGridTemplateMailService : IMailService + { + private const string WelcomeTemplateId = "045f8ad5-5547-4fa2-8d3d-6d46e401164d"; + private const string ChangeEmailAlreadyExistsTemplateId = "b69d2038-6ad9-4cf6-8f7f-7880921cba43"; + private const string ChangeEmailTemplateId = "ec2c1471-8292-4f17-b6b6-8223d514f86e"; + private const string NoMasterPasswordHintTemplateId = "136eb299-e102-495a-88bd-f96736eea159"; + private const string MasterPasswordHintTemplateId = "be77cfde-95dd-4cb9-b5e0-8286b53885f1"; + private const string OrganizationInviteTemplateId = "1eff5512-e36c-49a8-b9e2-2b215d6bbced"; + private const string OrganizationAcceptedTemplateId = "28f7f741-598e-449c-85fe-601e1cc32ba3"; + private const string OrganizationConfirmedTemplateId = "a8afe2a0-6161-4eb9-b40c-08a7f520ec50"; + + private const string AdministrativeCategoryName = "Administrative"; + private const string MarketingCategoryName = "Marketing"; + + private readonly GlobalSettings _globalSettings; + private readonly IMailDeliveryService _mailDeliveryService; + + public SendGridTemplateMailService( + GlobalSettings globalSettings, + IMailDeliveryService mailDeliveryService) + { + _globalSettings = globalSettings; + _mailDeliveryService = mailDeliveryService; + } + + public async Task SendWelcomeEmailAsync(User user) + { + var message = CreateDefaultMessage( + "Welcome", + user.Email, + WelcomeTemplateId); + + AddCategories(message, new List { AdministrativeCategoryName, "Welcome" }); + + await _mailDeliveryService.SendEmailAsync(message); + } + + public async Task SendChangeEmailAlreadyExistsEmailAsync(string fromEmail, string toEmail) + { + var message = CreateDefaultMessage( + "Your Email Change", + toEmail, + ChangeEmailAlreadyExistsTemplateId); + + AddSubstitution(message, "{{fromEmail}}", fromEmail); + AddSubstitution(message, "{{toEmail}}", toEmail); + AddCategories(message, new List { AdministrativeCategoryName, "Change Email Alrady Exists" }); + + await _mailDeliveryService.SendEmailAsync(message); + } + + public async Task SendChangeEmailEmailAsync(string newEmailAddress, string token) + { + var message = CreateDefaultMessage( + "Your Email Change", + newEmailAddress, + ChangeEmailTemplateId); + + AddSubstitution(message, "{{token}}", Uri.EscapeDataString(token)); + AddCategories(message, new List { AdministrativeCategoryName, "Change Email" }); + message.MetaData.Add("SendGridBypassListManagement", true); + + await _mailDeliveryService.SendEmailAsync(message); + } + + public async Task SendNoMasterPasswordHintEmailAsync(string email) + { + var message = CreateDefaultMessage( + "Your Master Password Hint", + email, + NoMasterPasswordHintTemplateId); + AddCategories(message, new List { AdministrativeCategoryName, "No Master Password Hint" }); + + await _mailDeliveryService.SendEmailAsync(message); + } + + public async Task SendMasterPasswordHintEmailAsync(string email, string hint) + { + var message = CreateDefaultMessage( + "Your Master Password Hint", + email, + MasterPasswordHintTemplateId); + + message.Subject = "Your Master Password Hint"; + AddSubstitution(message, "{{hint}}", hint); + AddCategories(message, new List { AdministrativeCategoryName, "Master Password Hint" }); + + await _mailDeliveryService.SendEmailAsync(message); + } + + public async Task SendOrganizationInviteEmailAsync(string organizationName, OrganizationUser orgUser, string token) + { + var message = CreateDefaultMessage( + $"Join {organizationName}", + orgUser.Email, + OrganizationInviteTemplateId); + + AddSubstitution(message, "{{organizationName}}", organizationName); + AddSubstitution(message, "{{organizationId}}", orgUser.OrganizationId.ToString()); + AddSubstitution(message, "{{organizationUserId}}", orgUser.Id.ToString()); + AddSubstitution(message, "{{token}}", token); + AddSubstitution(message, "{{email}}", WebUtility.UrlEncode(orgUser.Email)); + AddSubstitution(message, "{{organizationNameUrlEncoded}}", WebUtility.UrlEncode(organizationName)); + AddCategories(message, new List { AdministrativeCategoryName, "Organization User Invite" }); + + await _mailDeliveryService.SendEmailAsync(message); + } + + public async Task SendOrganizationAcceptedEmailAsync(string organizationName, string userEmail, + IEnumerable adminEmails) + { + var message = CreateDefaultMessage( + $"User {userEmail} Has Accepted Invite", + adminEmails, + OrganizationAcceptedTemplateId); + + AddSubstitution(message, "{{userEmail}}", userEmail); + AddSubstitution(message, "{{organizationName}}", organizationName); + AddCategories(message, new List { AdministrativeCategoryName, "Organization User Accepted" }); + + await _mailDeliveryService.SendEmailAsync(message); + } + + public async Task SendOrganizationConfirmedEmailAsync(string organizationName, string email) + { + var message = CreateDefaultMessage( + $"You Have Been Confirmed To {organizationName}", + email, + OrganizationConfirmedTemplateId); + + AddSubstitution(message, "{{organizationName}}", organizationName); + AddCategories(message, new List { AdministrativeCategoryName, "Organization User Confirmed" }); + + await _mailDeliveryService.SendEmailAsync(message); + } + + private MailMessage CreateDefaultMessage(string subject, string toEmail, string templateId) + { + return CreateDefaultMessage(subject, new List { toEmail }, templateId); + } + + private MailMessage CreateDefaultMessage(string subject, IEnumerable toEmails, string templateId) + { + var message = new MailMessage + { + HtmlContent = " ", + TextContent = " ", + MetaData = new Dictionary(), + ToEmails = toEmails, + Subject = subject + }; + + if(!string.IsNullOrWhiteSpace(templateId)) + { + message.MetaData.Add("SendGridTemplateId", templateId); + } + + AddSubstitution(message, "{{siteName}}", _globalSettings.SiteName); + AddSubstitution(message, "{{baseVaultUri}}", _globalSettings.BaseVaultUri); + + return message; + } + + private void AddSubstitution(MailMessage message, string key, string value) + { + Dictionary dict; + if(!message.MetaData.ContainsKey("SendGridSubstitutions")) + { + dict = new Dictionary(); + } + else + { + dict = message.MetaData["SendGridSubstitutions"] as Dictionary; + } + + dict.Add(key, value); + message.MetaData["SendGridSubstitutions"] = dict; + } + + private void AddCategories(MailMessage message, List categories) + { + List cats; + if(!message.MetaData.ContainsKey("SendGridCategories")) + { + cats = categories; + } + else + { + cats = message.MetaData["SendGridCategories"] as List; + cats.AddRange(categories); + } + + message.MetaData["SendGridCategories"] = cats; + } + } +} diff --git a/src/Core/Services/Implementations/SmtpMailDeliveryService.cs b/src/Core/Services/Implementations/SmtpMailDeliveryService.cs new file mode 100644 index 0000000000..0f368b5fea --- /dev/null +++ b/src/Core/Services/Implementations/SmtpMailDeliveryService.cs @@ -0,0 +1,21 @@ +using System; +using System.Threading.Tasks; +using Bit.Core.Models.Mail; + +namespace Bit.Core.Services +{ + public class SmtpMailDeliveryService : IMailDeliveryService + { + private readonly GlobalSettings _globalSettings; + + public SmtpMailDeliveryService(GlobalSettings globalSettings) + { + + } + + public Task SendEmailAsync(MailMessage message) + { + throw new NotImplementedException(); + } + } +} diff --git a/src/Core/Utilities/ServiceCollectionExtensions.cs b/src/Core/Utilities/ServiceCollectionExtensions.cs index 2c7f0945c3..6dbf2751bc 100644 --- a/src/Core/Utilities/ServiceCollectionExtensions.cs +++ b/src/Core/Utilities/ServiceCollectionExtensions.cs @@ -49,7 +49,9 @@ namespace Bit.Core.Utilities public static void AddDefaultServices(this IServiceCollection services) { - services.AddSingleton(); + //services.AddSingleton(); + services.AddSingleton(); + services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); services.AddSingleton();