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 SpecialSubjectFromExternalUserShouldBeLoggedAndNotSendFromDomain() { 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" }, "*****@*****.**", "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 async Task MailFromExternalUserShouldBeRelayedToTarget() { 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 = "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 async Task SendingEmailFromTargetToDomainWithWrongSubjectShouldSendRegularEmailToOwner() { 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("this is the correct prefix for"); 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 = "this is the wrong prefix 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 == "this is the correct prefix for [email protected]: this is the wrong prefix for [email protected]: Inquiry"), It.IsAny <CancellationToken>())); client.VerifyNoOtherCalls(); logger.VerifyNoOtherCalls(); }
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()); } }