public async Task Alias_IndexPatchMismatchingOld_NotAuthorized() { using (IdentityWsDbContext ef = CreateEf()) { ef.Aliases.Add(new Alias { EmailAddress = "*****@*****.**", Being = new Being { SaltedHashedPassword = Sha512Util.SaltAndHashNewPassword("password1") } }); await ef.SaveChangesAsync(); AliasesController patient = new AliasesController(ef, dummyLog, now, dummyRunner); AliasesController.IndexPatchRequestBody body = new AliasesController.IndexPatchRequestBody { oldPassword = "******", password = "******" }; IActionResult result = await patient.IndexPatch("*****@*****.**", body); result.Should().BeOfType <UnauthorizedResult>($"'{nameof(body.oldPassword)}' does not match"); } }
public async Task AliasClientWithoutData_Index_EmptyJsonArray() { using (IdentityWsDbContext ef = CreateEf()) { ef.Aliases.Add(new Alias { EmailAddress = "*****@*****.**", Being = new Being { Clients = new[] { new BeingClient { ClientName = "testclient" } } } }); await ef.SaveChangesAsync(); ClientsController patient = new ClientsController(ef, dummyLog, now, null); IActionResult result = await patient.Index("*****@*****.**", "testclient"); result.Should().BeOfType <JsonResult>() .Which.Value.Should().BeOfType <Dictionary <string, string> >() .Which.Should().HaveCount(0, "there is no data for the client and being"); } }
public async Task Alias_IndexPatchOldSameAsNew_Conflict() { using (IdentityWsDbContext ef = CreateEf()) { ef.Aliases.Add(new Alias { EmailAddress = "*****@*****.**", Being = new Being { SaltedHashedPassword = Sha512Util.SaltAndHashNewPassword("password1") } }); await ef.SaveChangesAsync(); AliasesController patient = new AliasesController(ef, dummyLog, now, dummyRunner); AliasesController.IndexPatchRequestBody body = new AliasesController.IndexPatchRequestBody { oldPassword = "******", password = "******" }; IActionResult result = await patient.IndexPatch("*****@*****.**", body); result.Should().BeOfType <StatusCodeResult>() .Which.StatusCode.Should().Be(409, $"'{nameof(body.password)}' must differ from '{nameof(body.oldPassword)}'"); } }
public async Task Alias_IndexPatchExpiredReset_NotAuthorized() { using (IdentityWsDbContext ef = CreateEf()) { ef.Aliases.Add(new Alias { EmailAddress = "*****@*****.**", Being = new Being { PasswordResetToken = "abracadabra", PasswordResetTokenValidUntil = now.UtcNow, SaltedHashedPassword = Sha512Util.SaltAndHashNewPassword("password1") } }); await ef.SaveChangesAsync(); AliasesController patient = new AliasesController(ef, dummyLog, now, dummyRunner); AliasesController.IndexPatchRequestBody body = new AliasesController.IndexPatchRequestBody { resetToken = "abracadabra", password = "******" }; IActionResult result = await patient.IndexPatch("*****@*****.**", body); result.Should().BeOfType <UnauthorizedResult>($"the reset token is expired"); } }
public async Task WrongClient_Login_NotFound() { const string PASSWORD = "******", CLIENT = "testing"; using (IdentityWsDbContext ef = CreateEf()) { Alias alias; ef.Aliases.Add(alias = new Alias { EmailAddress = "*****@*****.**", Being = new Being { SaltedHashedPassword = Sha512Util.SaltAndHashNewPassword(PASSWORD), Clients = new HashSet <BeingClient> { new BeingClient { ClientName = CLIENT } } } }); await ef.SaveChangesAsync(); ClientsController patient = new ClientsController(ef, dummyLog, now, null); IActionResult result = await patient.Login("*****@*****.**", "wrong", new ClientsController.LoginRequestBody { password = PASSWORD }); result.Should().BeOfType <NotFoundResult>("the client does not match"); } }
public async Task Alias_IndexPatchMatchingOld_NoContentWithChangedPassword() { using (IdentityWsDbContext ef = CreateEf()) { Being being = new Being { SaltedHashedPassword = Sha512Util.SaltAndHashNewPassword("password1") }; ef.Aliases.Add(new Alias { EmailAddress = "*****@*****.**", Being = being }); await ef.SaveChangesAsync(); AliasesController patient = new AliasesController(ef, dummyLog, now, dummyRunner); AliasesController.IndexPatchRequestBody body = new AliasesController.IndexPatchRequestBody { oldPassword = "******", password = "******" }; IActionResult result = await patient.IndexPatch("*****@*****.**", body); result.Should().BeOfType <NoContentResult>($"'{nameof(body.oldPassword)}' matched"); Sha512Util.TestPassword("p@ssword1", being.SaltedHashedPassword).Should().BeTrue("the password should have been changed"); } }
public async Task BeingAndClient_IndexPost_Conflict() { using (IdentityWsDbContext ef = CreateEf()) { ef.Aliases.Add(new Alias { EmailAddress = "*****@*****.**", Being = new Being { Clients = new[] { new BeingClient { ClientName = "testclient" } } } }); await ef.SaveChangesAsync(); ClientsController patient = new ClientsController(ef, dummyLog, now, null); IActionResult result = await patient.IndexPost("*****@*****.**", "testclient", new Dictionary <string, string>()); result.Should().BeOfType <StatusCodeResult>().Which.StatusCode.Should().Be(StatusCodes.Status409Conflict, "the client already exists for the being"); } }
public async Task Being_IndexPost_NoContentWithNewBeingClientAndData() { using (IdentityWsDbContext ef = CreateEf()) { Dictionary <string, string> expected_data = new Dictionary <string, string>() { ["key1"] = "value1", ["key2"] = "value2", }; ef.Aliases.Add(new Alias { EmailAddress = "*****@*****.**", Being = new Being() }); await ef.SaveChangesAsync(); ClientsController patient = new ClientsController(ef, dummyLog, now, null); IActionResult result = await patient.IndexPost("*****@*****.**", "testclient", expected_data); result.Should().BeOfType <NoContentResult>("no such client existed previously for the being"); Dictionary <string, string> actual_data = new Dictionary <string, string>(); await ef.BeingClientData .Where(d => d.BeingClient.Being.Aliases.Any(a => a.EmailAddress == "*****@*****.**")) .ForEachAsync(d => actual_data.Add(d.Key, d.Value)); actual_data.ShouldBeEquivalentTo(expected_data, "such data were provided with the POST request"); } }
public async Task Confirmed_Run_SendsEmail() { using (IdentityWsDbContext ef = CreateEf()) { Email email; ef.Emails.Add(email = new Email { To = new Alias { DateConfirmed = now.UtcNow } }); await ef.SaveChangesAsync(); EmailQueueProcessor patient = new EmailQueueProcessor(dummyLog, now); bool email_sent = false; Mock <IEmailSender> mock_sender = new Mock <IEmailSender>(); mock_sender.Setup(m => m.Send(It.IsAny <string>(), It.IsAny <string>(), It.IsAny <string>(), It.IsAny <string>(), It.IsAny <string>(), It.IsAny <string>())) .Callback <string, string, string, string, string, string>((a, b, c, d, e, f) => email_sent = true); IServiceProvider services = MakeServices(mock_sender.Object, ef); patient.Run(services, null); email.Should().Match <Email>(e => e.ProcessingCount == 1 && e.DateLastProcessed == now.UtcNow && string.IsNullOrEmpty(e.LastProcessingError), "the alias is confirmed"); email_sent.Should().BeTrue("the email can be sent"); } }
public async Task OneAlias_IndexDelete_Forbidden() { using (IdentityWsDbContext ef = CreateEf()) { ef.Beings.Add(new Being { Aliases = new HashSet <Alias> { new Alias { EmailAddress = "*****@*****.**" } } }); await ef.SaveChangesAsync(); AliasesController patient = new AliasesController(ef, dummyLog, now, dummyRunner); IActionResult result = await patient.IndexDelete("*****@*****.**"); result.Should().BeOfType <StatusCodeResult>() .Which.StatusCode.Should().Be(StatusCodes.Status403Forbidden, "a being must not be without an alias"); (await ef.Aliases.CountAsync()).Should().Be(1, "the single alias was not deleted"); } }
public async Task ProhibitedUnconfirmed_Run_FailsPermanently() { using (IdentityWsDbContext ef = CreateEf()) { Email email; ef.Emails.Add(email = new Email { To = new Alias() }); await ef.SaveChangesAsync(); EmailQueueProcessor patient = new EmailQueueProcessor(dummyLog, now); bool email_sent = false; Mock <IEmailSender> mock_sender = new Mock <IEmailSender>(); mock_sender.Setup(m => m.Send(It.IsAny <string>(), It.IsAny <string>(), It.IsAny <string>(), It.IsAny <string>(), It.IsAny <string>(), It.IsAny <string>())) .Callback <string, string, string, string, string, string>((a, b, c, d, e, f) => email_sent = true); IServiceProvider services = MakeServices(mock_sender.Object, ef); patient.Run(services, null); email.Should().Match <Email>(e => e.ProcessingCount == 10 && e.DateLastProcessed == now.UtcNow && e.LastProcessingError == "Unconfirmed", "the alias is unconfirmed and Email.SendIfUnconfirmed is false"); email_sent.Should().BeFalse("an error ocurred"); } }
public async Task BeingAndResetToken_ResetPost_JsonNewResetToken() { string old_tok = "Prev"; using (IdentityWsDbContext ef = CreateEf()) { Being being = new Being { PasswordResetToken = old_tok, PasswordResetTokenValidUntil = now.UtcNow.AddMinutes(2) }; ef.Aliases.Add(new Alias { EmailAddress = "*****@*****.**", Being = being }); await ef.SaveChangesAsync(); AliasesController patient = new AliasesController(ef, dummyLog, now, dummyRunner); IActionResult result = await patient.ResetPost("*****@*****.**"); result.Should().BeOfType <JsonResult>() .Which.Value.Should().BeOfType <Dictionary <string, string> >() .Which["resetToken"].Should().NotBe(old_tok, "a new token should have been generated"); being.PasswordResetTokenValidUntil.Should().Be(now.UtcNow.AddHours(1), "the validity period should also reset"); } }
public async Task OutsideBackoffPeriod_Run_SendsEmail() { using (IdentityWsDbContext ef = CreateEf()) { DateTime last_processed = now.UtcNow.AddMinutes(-16); Email email; ef.Emails.Add(email = new Email { To = new Alias { DateConfirmed = now.UtcNow }, ProcessingCount = 4, // Processing should be allowed 16 mins after error (2^^4). LastProcessingError = "some weird error", DateLastProcessed = last_processed }); await ef.SaveChangesAsync(); EmailQueueProcessor patient = new EmailQueueProcessor(dummyLog, now); bool email_sent = false; Mock <IEmailSender> mock_sender = new Mock <IEmailSender>(); mock_sender.Setup(m => m.Send(It.IsAny <string>(), It.IsAny <string>(), It.IsAny <string>(), It.IsAny <string>(), It.IsAny <string>(), It.IsAny <string>())) .Callback <string, string, string, string, string, string>((a, b, c, d, e, f) => email_sent = true); IServiceProvider services = MakeServices(mock_sender.Object, ef); patient.Run(services, null); email.Should().Match <Email>(e => e.ProcessingCount == 5 && e.DateLastProcessed == now.UtcNow && string.IsNullOrEmpty(e.LastProcessingError), "the last processing date is (just) outside the back-off period"); email_sent.Should().BeTrue("the email can be sent"); } }
public ClientsController(IdentityWsDbContext ef, ILogger <ClientsController> log, IUtcNow now, IConfiguration config) { this.ef = ef; this.log = log; this.now = now; this.config = config; }
IServiceProvider MakeServices(IEmailSender sender, IdentityWsDbContext ef) { Mock <IServiceProvider> mock_provider = new Mock <IServiceProvider>(); mock_provider.Setup(m => m.GetService(It.IsAny <Type>())) .Returns <Type>(t => t.IsAssignableFrom(ef.GetType()) ? (object)ef : sender); return(mock_provider.Object); }
public AliasesController(IdentityWsDbContext ef, ILogger <AliasesController> log, IUtcNow now, IBackgroundJobRunner <EmailQueueProcessor> runner) { this.ef = ef; this.log = log; this.now = now; this.runner = runner; }
public async Task RightPassword_Login_NoContentAndDbTrue() { const string PASSWORD = "******", CLIENT = "testing"; using (IdentityWsDbContext ef = CreateEf()) { Alias alias; ef.Aliases.Add(alias = new Alias { EmailAddress = "*****@*****.**", Being = new Being { SaltedHashedPassword = Sha512Util.SaltAndHashNewPassword(PASSWORD), Clients = new HashSet <BeingClient> { new BeingClient { ClientName = CLIENT } } }, LoginAttempts = new List <LoginAttempt> { new LoginAttempt { DateCreated = now.UtcNow.AddMinutes(-7) }, new LoginAttempt { DateCreated = now.UtcNow.AddMinutes(-6) }, new LoginAttempt { DateCreated = now.UtcNow.AddMinutes(-5), // The account is not locked because this success breaks the sequence. Success = true }, new LoginAttempt { DateCreated = now.UtcNow.AddMinutes(-4) } } }); await ef.SaveChangesAsync(); ClientsController patient = new ClientsController(ef, dummyLog, now, config); IActionResult result = await patient.Login("*****@*****.**", CLIENT, new ClientsController.LoginRequestBody { password = PASSWORD }); result.Should().BeOfType <NoContentResult>("the password matches"); (await ef.LoginAttempts.CountAsync()).Should().Be(5, "a new record should be added"); (await ef.LoginAttempts.LastAsync()).Should().Match <LoginAttempt>(a => a.Success && a.ClientName == CLIENT, "such a client was supplied"); } }
public async Task NoAlias_IndexGet_NotFound() { using (IdentityWsDbContext ef = CreateEf()) { AliasesController patient = new AliasesController(ef, dummyLog, now, dummyRunner); IActionResult result = await patient.Index("*****@*****.**"); result.Should().BeOfType <NotFoundResult>("the alias does not exist in the database"); } }
public async Task NoBeing_ResetPost_NotFound() { using (IdentityWsDbContext ef = CreateEf()) { AliasesController patient = new AliasesController(ef, dummyLog, now, dummyRunner); IActionResult result = await patient.ResetPost("*****@*****.**"); result.Should().BeOfType <NotFoundResult>("the being does not exist"); } }
public async Task NoBeing_IndexPost_NotFound() { using (IdentityWsDbContext ef = CreateEf()) { ClientsController patient = new ClientsController(ef, dummyLog, now, null); IActionResult result = await patient.IndexPost("*****@*****.**", "testclient", new Dictionary <string, string>()); result.Should().BeOfType <NotFoundResult>("the being does not exist"); } }
public async Task NoAliasNoClient_Index_NotFound() { using (IdentityWsDbContext ef = CreateEf()) { ClientsController patient = new ClientsController(ef, dummyLog, now, null); IActionResult result = await patient.Index("*****@*****.**", "*****@*****.**"); result.Should().BeOfType <NotFoundResult>("the alias does not exist"); } }
public async Task ExceptionDuringSend_Run_SavesMessageAndContinues() { using (IdentityWsDbContext ef = CreateEf()) { Email email1, email2; ef.Emails.AddRange(new[] { email1 = new Email { To = new Alias { DateConfirmed = now.UtcNow }, BodyText = "Hello" }, email2 = new Email { To = new Alias { DateConfirmed = now.UtcNow }, BodyText = "World" } }); await ef.SaveChangesAsync(); EmailQueueProcessor patient = new EmailQueueProcessor(dummyLog, now); string emailed_text = null; bool called_back = false; Mock <IEmailSender> mock_sender = new Mock <IEmailSender>(); mock_sender.Setup(m => m.Send(It.IsAny <string>(), It.IsAny <string>(), It.IsAny <string>(), It.IsAny <string>(), It.IsAny <string>(), It.IsAny <string>())) .Callback <string, string, string, string, string, string>((a, b, c, d, e, f) => { if (!called_back) { called_back = true; throw new Exception("Exceptional!"); } emailed_text = e; }); IServiceProvider services = MakeServices(mock_sender.Object, ef); patient.Run(services, null); email1.Should().Match <Email>(e => e.ProcessingCount == 1 && e.DateLastProcessed == now.UtcNow && e.LastProcessingError == "Exceptional!", "an exception was thrown during sending"); email2.Should().Match <Email>(e => e.ProcessingCount == 1 && e.DateLastProcessed == now.UtcNow && string.IsNullOrEmpty(e.LastProcessingError), "sending should succeed"); emailed_text.Should().Be("World", "that's the subject of email2"); } }
public void Run(IServiceProvider services, IConfigurationSection section) { // Work out when to delete before (from the config). int days = section.GetSection("DeleteCreatedBeforeDays").GetValue <int>(entityName); DateTime delete_before = now.UtcNow.AddDays(-days); // Delete any older rows. IdentityWsDbContext ef = services.GetRequiredService <IdentityWsDbContext>(); DbSet <T> table = (DbSet <T>)tableProperty.GetValue(ef); table.RemoveRange(table.Where(e => e.DateCreated < delete_before).ToList()); ef.SaveChanges(); }
public async Task NoAlias_Confirm_NotFound() { using (IdentityWsDbContext ef = CreateEf()) { AliasesController patient = new AliasesController(ef, dummyLog, now, dummyRunner); IActionResult result = await patient.ConfirmPost("*****@*****.**", new AliasesController.ConfirmPostRequestBody { confirmToken = "abc" }); result.Should().BeOfType <NotFoundResult>("the alias does not exist"); } }
public async Task NoAlias_IndexPatch_NotFound() { using (IdentityWsDbContext ef = CreateEf()) { AliasesController patient = new AliasesController(ef, dummyLog, now, dummyRunner); AliasesController.IndexPatchRequestBody body = new AliasesController.IndexPatchRequestBody { password = "******" }; IActionResult result = await patient.IndexPatch("*****@*****.**", body); result.Should().BeOfType <NotFoundResult>("the alias does not exist in the database"); } }
public async Task WrongPassword_Login_UnauthorizedAndDbFalse() { const string PASSWORD = "******", CLIENT = "testing"; using (IdentityWsDbContext ef = CreateEf()) { Alias alias; ef.Aliases.Add(alias = new Alias { EmailAddress = "*****@*****.**", Being = new Being { SaltedHashedPassword = Sha512Util.SaltAndHashNewPassword(PASSWORD), Clients = new HashSet <BeingClient> { new BeingClient { ClientName = CLIENT } } }, LoginAttempts = new List <LoginAttempt> { new LoginAttempt { // This is too old to count. DateCreated = now.UtcNow.AddMinutes(-15).AddMilliseconds(-100) }, new LoginAttempt { DateCreated = now.UtcNow.AddMinutes(-5) } } }); await ef.SaveChangesAsync(); ClientsController patient = new ClientsController(ef, dummyLog, now, config); IActionResult result = await patient.Login("*****@*****.**", CLIENT, new ClientsController.LoginRequestBody { password = "******" }); LoginAttempt attempt = await ef.LoginAttempts.FirstAsync(a => a.AliasID == alias.AliasID); result.Should().BeOfType <UnauthorizedResult>("the password doesn't match"); (await ef.LoginAttempts.CountAsync()).Should().Be(3, "a new record should be added"); (await ef.LoginAttempts.LastAsync()).Should().Match <LoginAttempt>(a => !a.Success && a.ClientName == CLIENT, "such a client was supplied"); } }
public async Task Alias_IndexPostWithNeitherPasswordNorEmail_BadRequest() { using (IdentityWsDbContext ef = CreateEf()) { ef.Aliases.Add(new Alias { EmailAddress = "*****@*****.**" }); await ef.SaveChangesAsync(); AliasesController patient = new AliasesController(ef, dummyLog, now, dummyRunner); AliasesController.IndexPostRequestBody body = new AliasesController.IndexPostRequestBody(); IActionResult result = await patient.IndexPost("*****@*****.**", body); result.Should().BeOfType <BadRequestResult>($"exactly one of '{nameof(body.otherEmailAddress)}' or '{nameof(body.password)} is required'"); } }
public async Task WrongAlias_Email_NotFound() { using (IdentityWsDbContext ef = CreateEf()) { Alias alias; ef.Aliases.Add(alias = new Alias { EmailAddress = "*****@*****.**" }); await ef.SaveChangesAsync(); AliasesController patient = new AliasesController(ef, dummyLog, now, dummyRunner); IActionResult result = await patient.Email("*****@*****.**", new AliasesController.EmailRequestBody()); result.Should().BeOfType <NotFoundResult>("the email address doesn't match"); } }
public async Task UnconfirmedAlias_Index_ConfirmationToken() { using (IdentityWsDbContext ef = CreateEf()) { ef.Aliases.Add(new Alias { EmailAddress = "*****@*****.**" }); await ef.SaveChangesAsync(); AliasesController patient = new AliasesController(ef, dummyLog, now, dummyRunner); IActionResult result = await patient.Index("*****@*****.**"); result.Should().BeOfType <JsonResult>() .Which.Value.Should().BeOfType <Dictionary <string, string> >() .Which["confirmToken"].Should().NotBeNull("the alias has not yet been confirmed"); } }
public async Task AliasNoClient_Index_NotFound() { using (IdentityWsDbContext ef = CreateEf()) { ef.Aliases.Add(new Alias { EmailAddress = "*****@*****.**", Being = new Being() }); await ef.SaveChangesAsync(); ClientsController patient = new ClientsController(ef, dummyLog, now, null); IActionResult result = await patient.Index("*****@*****.**", "*****@*****.**"); result.Should().BeOfType <NotFoundResult>("the client does not exist"); } }