public void ParseRegularSubjects(string subject) { var r = new SubjectParser().Parse(subject); r.Prefix.Should().BeNullOrEmpty(); r.RelayTarget.Should().BeNullOrEmpty(); r.Subject.Should().Be(subject); }
public void ParseRelaySubjects(string prefix, string subject) { var r = new SubjectParser().Parse($"{prefix}Relay for [email protected]: {subject}"); r.Prefix.Should().Be(prefix); r.RelayTarget.Should().Be("*****@*****.**"); r.Subject.Should().Be(subject); }
public void ParseCustomPrefix(string subject) { var r = new SubjectParser("Email for").Parse($"Email for [email protected]: {subject}"); r.Prefix.Should().BeNullOrEmpty(); r.RelayTarget.Should().Be("*****@*****.**"); r.Subject.Should().Be(subject); }
public void MultipleRelaysShouldBeParsedToOne(string subject) { var r = new SubjectParser().Parse($"Relay for [email protected]: Relay for [email protected]: {subject}"); r.Prefix.Should().BeNullOrEmpty(); r.RelayTarget.Should().Be("*****@*****.**"); r.Subject.Should().Be(subject); }
public void ManyRelaysShouldBeParsedToOne() { var r = new SubjectParser().Parse("Relay for [email protected]: Relay for [email protected]: Relay for [email protected]: Relay for [email protected]: Subject"); r.Prefix.Should().BeNullOrEmpty(); r.RelayTarget.Should().Be("*****@*****.**"); r.Subject.Should().Be("Subject"); }
public void PasswordResetSubjectIsParsed() { var parser = new SubjectParser(); var result = parser.ParseSubject(Resources.PasswordResetEmail); Assert.AreEqual("Umbraco: Reset Password", result); }
public async Task SendingEmailReplyFromTargetToDomainWithSpecialSubjectShouldSendAsDomainToExternalUserAndReplacePrivateMailInMetadataOfBody_WithSubjectPrefix() { var client = new Mock <ISendGridClient>(); client.Setup(x => x.SendEmailAsync(It.IsAny <SendGridMessage>(), It.IsAny <CancellationToken>())) .ReturnsAsync(new Response(System.Net.HttpStatusCode.Accepted, null, null)); var logger = new Mock <ILogger>(); var parser = new SubjectParser(); var relay = new RelayLogic(client.Object, parser, logger.Object, new[] { new OutlookWebSanitizer(parser) }); await relay.RelayAsync(new Email { From = new EmailAddress { Email = "*****@*****.**" }, To = new[] { new EmailAddress { Email = "*****@*****.**" } }, Text = @"This is my response ___________________________________________ From: [email protected] <*****@*****.**> Sent: Tuesday, September 3, 2019 11:19:42 PM To: [email protected] <*****@*****.**> Subject: RE: Relay for [email protected]: Test This is the original message from someone", Subject = "RE: Relay for [email protected]: Test", Spf = "pass", Dkim = "{@live.com : pass}" }, "*****@*****.**", "domain.com", true, CancellationToken.None); client.Verify(x => x.SendEmailAsync(It.Is <SendGridMessage>(m => m.From.Email == "*****@*****.**" && m.Personalizations.Count == 1 && m.Personalizations[0].Tos.Count == 1 && m.Personalizations[0].Tos[0].Email == "*****@*****.**" && m.Personalizations[0].Subject == "RE: Test" && !m.Contents[0].Value.Contains("From: [email protected] <*****@*****.**>") && !m.Contents[0].Value.Contains("To: [email protected] <*****@*****.**>") && !m.Contents[0].Value.Contains("Subject: RE: Relay for [email protected]: Test") && m.Contents[0].Value.Contains("From: [email protected] <*****@*****.**>") && m.Contents[0].Value.Contains("To: [email protected] <*****@*****.**>") && m.Contents[0].Value.Contains("Subject: RE: Test")), It.IsAny <CancellationToken>())); client.VerifyNoOtherCalls(); logger.VerifyNoOtherCalls(); }
public async Task MailWithMultipleRecipientsShouldBeSentToFirstDomain() { var client = new Mock <ISendGridClient>(); client.Setup(x => x.SendEmailAsync(It.IsAny <SendGridMessage>(), It.IsAny <CancellationToken>())) .ReturnsAsync(new Response(System.Net.HttpStatusCode.Accepted, null, null)); var logger = new Mock <ILogger>(); var parser = new SubjectParser(); var relay = new RelayLogic(client.Object, parser, logger.Object, GetDefaultSanitizers()); await relay.RelayAsync(new Email { From = new EmailAddress { Email = "*****@*****.**" }, To = new[] { new EmailAddress { Email = "*****@*****.**" }, new EmailAddress { Email = "*****@*****.**" } }, Cc = new[] { new EmailAddress { Email = "*****@*****.**" } }, Html = "Foo", Subject = "Inquiry" }, "*****@*****.**", "domain.com", true, CancellationToken.None); client.Verify(x => x.SendEmailAsync(It.Is <SendGridMessage>(m => m.From.Email == "*****@*****.**" && m.Personalizations.Count == 1 && m.Personalizations[0].Tos.Count == 1 && m.Personalizations[0].Tos[0].Email == "*****@*****.**" && m.Personalizations[0].Subject == "Relay for [email protected]: Inquiry"), It.IsAny <CancellationToken>())); client.VerifyNoOtherCalls(); logger.VerifyNoOtherCalls(); }
public void SanitizeHtmlWithPrefixShouldWork() { var parser = new SubjectParser(); var sanitizer = new OutlookWebSanitizer(parser); var content = File.ReadAllText("Data/outlook-html-email.txt").Replace("Relay for ", "RE: Relay for "); sanitizer.TrySanitizeHtml(ref content, new SubjectModel { Prefix = "RE: ", RelayTarget = "*****@*****.**", Subject = "Test" }, "*****@*****.**", "*****@*****.**").Should().BeTrue(); }
public void SanitizeHtmlShouldFailIfNotMatched() { var parser = new SubjectParser(); var sanitizer = new OutlookWebSanitizer(parser); var content = File.ReadAllText("Data/outlook-html-email.txt").Replace("*****@*****.**", "*****@*****.**"); sanitizer.TrySanitizeHtml(ref content, new SubjectModel { Prefix = "RE: ", RelayTarget = "*****@*****.**", Subject = "Test" }, "*****@*****.**", "*****@*****.**").Should().BeFalse(); content.Should().Contain("*****@*****.**", "because no replacement occured"); }
public void SanitizeHtmlShouldWorkWithSubjectPrefix() { var parser = new SubjectParser(); var sanitizer = new OutlookWebSanitizer(parser); // when first responding to email the new subject will have "RE: .." // while the old subject(copied into the body) doesn't have it -> should parse just fine var content = File.ReadAllText("Data/outlook-html-email.txt"); sanitizer.TrySanitizeHtml(ref content, new SubjectModel { Prefix = "RE: ", RelayTarget = "*****@*****.**", Subject = "Test" }, "*****@*****.**", "*****@*****.**").Should().BeTrue(); }
public async Task SendGridErrorShouldAbort() { var client = new Mock <ISendGridClient>(); client.Setup(x => x.SendEmailAsync(It.IsAny <SendGridMessage>(), It.IsAny <CancellationToken>())) .ReturnsAsync(new Response(System.Net.HttpStatusCode.BadRequest, new StringContent(JsonConvert.SerializeObject(new { error = "Sendgrid service unavailable" }), Encoding.UTF8, "application/json"), null)); var logger = new Mock <ILogger>(); var parser = new SubjectParser(); var relay = new RelayLogic(client.Object, parser, logger.Object, GetDefaultSanitizers()); await new Func <Task>(async() => await relay.RelayAsync(new Email { From = new EmailAddress { Email = "*****@*****.**" }, To = new[] { new EmailAddress { Email = "*****@*****.**" } }, Html = "Foo", Subject = "Inquiry" }, "*****@*****.**", "domain.com", true, CancellationToken.None)) .Should().ThrowAsync <BadRequestException>(); client.Verify(x => x.SendEmailAsync(It.Is <SendGridMessage>(m => m.From.Email == "*****@*****.**" && m.Personalizations.Count == 1 && m.Personalizations[0].Tos.Count == 1 && m.Personalizations[0].Tos[0].Email == "*****@*****.**" && m.Personalizations[0].Subject == "Relay for [email protected]: Inquiry"), It.IsAny <CancellationToken>())); client.VerifyNoOtherCalls(); logger.VerifyNoOtherCalls(); }
public async Task SpecialSubjectFromSpoofedUserShouldBeLoggedAndNotSendFromDomain() { var client = new Mock <ISendGridClient>(); client.Setup(x => x.SendEmailAsync(It.IsAny <SendGridMessage>(), It.IsAny <CancellationToken>())) .ReturnsAsync(new Response(System.Net.HttpStatusCode.Accepted, null, null)); var logger = new Mock <ILogger>(); var parser = new SubjectParser(); var relay = new RelayLogic(client.Object, parser, logger.Object, GetDefaultSanitizers()); await relay.RelayAsync(new Email { From = new EmailAddress { Name = "spoofed", Email = "*****@*****.**" }, To = new[] { new EmailAddress { Email = "*****@*****.**" } }, Html = "Foo", Subject = "Relay for [email protected]: Inquiry", Dkim = "none", Spf = "softfail" }, "*****@*****.**", "domain.com", true, CancellationToken.None); // external user should not be allowed to send as domain just by sending well crafted subject! // warning email must be issued to owner client.Verify(x => x.SendEmailAsync(It.Is <SendGridMessage>(m => m.From.Email == "*****@*****.**" && m.Personalizations.Count == 1 && m.Personalizations[0].Tos.Count == 1 && m.Personalizations[0].Tos[0].Email == "*****@*****.**" && m.Personalizations[0].Subject == "[SPOOFWARNING] Relay for [email protected]: Inquiry" && m.Contents[0].Value.Contains("Someone tried to send an email in the name of the domain")), It.IsAny <CancellationToken>())); logger.Verify(x => x.Log(LogLevel.Critical, It.IsAny <EventId>(), It.IsAny <object>(), null, It.IsAny <Func <object, Exception, string> >())); client.VerifyNoOtherCalls(); logger.VerifyNoOtherCalls(); }
public void SanitizeHtmlShouldWork(bool escapeNewlines) { var parser = new SubjectParser(); var sanitizer = new OutlookWebSanitizer(parser); var content = File.ReadAllText("Data/outlook-html-email.txt"); if (escapeNewlines) { // sendgrid escapes newlines so both should work content = content.Replace("\r\n", "\\r\\n"); } sanitizer.TrySanitizeHtml(ref content, new SubjectModel { Prefix = "", RelayTarget = "*****@*****.**", Subject = "Test" }, "*****@*****.**", "*****@*****.**").Should().BeTrue(); }
public async Task SendingEmailFromTargetToDomainWithSpecialSubjectShouldSendAsDomainToSelf() { var client = new Mock <ISendGridClient>(); client.Setup(x => x.SendEmailAsync(It.IsAny <SendGridMessage>(), It.IsAny <CancellationToken>())) .ReturnsAsync(new Response(System.Net.HttpStatusCode.Accepted, null, null)); var logger = new Mock <ILogger>(); var parser = new SubjectParser(); var relay = new RelayLogic(client.Object, parser, logger.Object, GetDefaultSanitizers()); await relay.RelayAsync(new Email { From = new EmailAddress { Email = "*****@*****.**" }, To = new[] { new EmailAddress { Email = "*****@*****.**" } }, Html = "Foo", Subject = "Relay for [email protected]: Inquiry", Spf = "pass", Dkim = "{@privatemail.example.com : pass}" }, "*****@*****.**", "domain.com", true, CancellationToken.None); client.Verify(x => x.SendEmailAsync(It.Is <SendGridMessage>(m => m.From.Email == "*****@*****.**" && m.Personalizations.Count == 1 && m.Personalizations[0].Tos.Count == 1 && m.Personalizations[0].Tos[0].Email == "*****@*****.**" && m.Personalizations[0].Subject == "Inquiry"), It.IsAny <CancellationToken>())); client.VerifyNoOtherCalls(); logger.VerifyNoOtherCalls(); }
public void SanitizePlainTextShouldWork() { var parser = new SubjectParser(); var sanitizer = new OutlookWebSanitizer(parser); var content = @"This is my response ___________________________________________ From: [email protected] <*****@*****.**> Sent: Tuesday, September 3, 2019 11:19:42 PM To: [email protected] <*****@*****.**> Subject: RE: Relay for [email protected]: Test This is the original message from someone"; sanitizer.TrySanitizePlainText(ref content, new SubjectModel { Prefix = "RE: ", RelayTarget = "*****@*****.**", Subject = "Test" }, "*****@*****.**", "*****@*****.**").Should().BeTrue(); }
public void SanitizeInitialMailShouldWorkWithoutReplacement() { var parser = new SubjectParser(); var sanitizer = new OutlookWebSanitizer(parser); var content = "Hello"; sanitizer.TrySanitizeHtml(ref content, new SubjectModel { Prefix = "", RelayTarget = "*****@*****.**", Subject = "Test" }, "*****@*****.**", "*****@*****.**").Should().BeTrue(); content.Should().Be("Hello"); sanitizer.TrySanitizePlainText(ref content, new SubjectModel { Prefix = "", RelayTarget = "*****@*****.**", Subject = "Test" }, "*****@*****.**", "*****@*****.**").Should().BeTrue(); content.Should().Be("Hello"); }
public void SanitizePlainTextShouldFailIfNotMatched() { var parser = new SubjectParser(); var sanitizer = new OutlookWebSanitizer(parser); var content = @"This is my response ___________________________________________ From: [email protected] <*****@*****.**> expects all 4 in order, by adding some random stuff inbetween it should fail to match Sent: Tuesday, September 3, 2019 11:19:42 PM To: [email protected] <*****@*****.**> Subject: RE: Relay for [email protected]: Test This is the original message from someone"; sanitizer.TrySanitizePlainText(ref content, new SubjectModel { Prefix = "RE: ", RelayTarget = "*****@*****.**", Subject = "Test" }, "*****@*****.**", "*****@*****.**").Should().BeFalse(); }
public static async Task <IActionResult> Run( [HttpTrigger(AuthorizationLevel.Function, "post", Route = null)] HttpRequest req, Microsoft.Azure.WebJobs.ExecutionContext context, ILogger log, CancellationToken cancellationToken) { try { var config = LoadConfig(context.FunctionAppDirectory, log); var container = config["ArchiveContainerName"]; var target = config["RelayTargetEmail"]; if (string.IsNullOrEmpty(container) && string.IsNullOrEmpty(target)) { throw new NotSupportedException("Neither email target nor container name where set. Please set either ArchiveContainerName or RelayTargetEmail"); } Email email; using (var stream = new MemoryStream()) { // body can only be read once req.Body.CopyTo(stream); stream.Position = 0; var parser = new SendgridEmailParser(); email = parser.Parse(stream); } if (!string.IsNullOrEmpty(container)) { IPersister auditLogger = new BlobStoragePersister(config["AzureWebJobsStorage"], container); var d = DateTimeOffset.UtcNow; // one folder per day is fine for now var id = $"{d.ToString("yyyy-MM")}/{d.ToString("dd")}/{d.ToString("HH-mm-ss")}_{email.From.Email} - {email.Subject}"; await auditLogger.PersistAsync($"{id}.json", JsonConvert.SerializeObject(email, Formatting.Indented)); // save all attachments in subfolder await Task.WhenAll(email.Attachments.Select(a => auditLogger.PersistAsync($"{id} (Attachments)/{a.FileName}", Convert.FromBase64String(a.Base64Data)))); } if (!string.IsNullOrEmpty(target)) { var domain = config["Domain"]; var key = config["SendgridApiKey"]; if (!string.IsNullOrEmpty(target) && string.IsNullOrEmpty(domain)) { throw new NotSupportedException("Domain must be set as well when relay is used."); } if (!string.IsNullOrEmpty(target) && string.IsNullOrEmpty(key)) { throw new NotSupportedException("SendgridApiKey must be set as well when relay is used."); } var client = new SendGridClient(key); var subjectParser = new SubjectParser(config["Prefix"]); var relay = new RelayLogic(client, subjectParser, log, new[] { new OutlookWebSanitizer(subjectParser) } ); var sendAsDomain = "true".Equals(config["SendAsDomain"], StringComparison.OrdinalIgnoreCase); await relay.RelayAsync(email, target, domain, sendAsDomain, cancellationToken); } return(new OkResult()); } catch (Exception e) { log.LogCritical(e, "Failed to process request!"); return(new BadRequestResult()); } }
public OutlookWebSanitizer(SubjectParser subjectParser) { _subjectParser = subjectParser ?? throw new ArgumentNullException(nameof(subjectParser)); }