Ejemplo n.º 1
0
        public async Task <IActionResult> IndexPost([EmailAddress, MaxLength(100)] string email_address,
                                                    [FromBody] IndexPostRequestBody body)
        {
            if (!ModelState.IsValid)
            {
                return(BadRequest(ModelState));
            }

            // Make sure the alias doesn't already exist.
            if (await ef.Aliases.AnyAsync(a => a.EmailAddress == email_address))
            {
                return(StatusCode(StatusCodes.Status409Conflict));
            }

            // Create or identify a being.
            Being being;

            if (body.password != null)
            {
                // A new being should be created.
                if (body.otherEmailAddress != null)
                {
                    // Both 'password' and 'otherEmailAddress' cannot be provided.
                    return(BadRequest());
                }
                ef.Beings.Add(being = new Being
                {
                    SaltedHashedPassword = Sha512Util.SaltAndHashNewPassword(body.password)
                });
                await ef.SaveChangesAsync();  // To populate BeingID
            }
            else
            {
                // The new alias should be linked to an existing being.
                if (body.otherEmailAddress == null)
                {
                    // One of 'password' and 'otherEmailAddress' must be provided.
                    return(BadRequest());
                }
                being = (await ef.Aliases
                         .Include(a => a.Being)
                         .FirstOrDefaultAsync(a => a.EmailAddress == body.otherEmailAddress))
                        ?.Being;
                if (being == null)
                {
                    log.LogWarning($"Invalid attempt to link '{email_address}' to non-existent alias '{body.otherEmailAddress}'");
                    return(NotFound());
                }
            }

            // Create the alias.
            ef.Aliases.Add(new Alias
            {
                EmailAddress = email_address,
                BeingID      = being.BeingID
            });
            await ef.SaveChangesAsync();

            return(NoContent());
        }
        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 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 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)}'");
            }
        }
Ejemplo n.º 5
0
        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_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");
            }
        }
Ejemplo n.º 7
0
        public void Password_SaltAndHashNewPasswordTwice_ReturnsDifferentHashes()
        {
            const string PASSWORD = "******";

            Sha512Util.SaltAndHashNewPassword(PASSWORD).Should().NotBe(Sha512Util.SaltAndHashNewPassword(PASSWORD),
                                                                       "a random salt should be used for each invocation");
        }
Ejemplo n.º 8
0
        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");
            }
        }
Ejemplo n.º 9
0
        public async Task <IActionResult> Login([EmailAddress, MaxLength(100)] string email_address, [Required, MaxLength(20)] string client,
                                                [FromBody] LoginRequestBody body)
        {
            if (!ModelState.IsValid)
            {
                return(BadRequest(ModelState));
            }

            // Get the entities.
            Alias alias = await ef.Aliases
                          .Include(a => a.Being).ThenInclude(b => b.Clients)
                          .FirstOrDefaultAsync(a => a.EmailAddress == email_address);

            Being being = alias?.Being;

            if (being == null || !being.Clients.Any(c => c.ClientName == client))
            {
                return(NotFound());
            }

            // Check the number of consecutive failures.
            DateTime period_start         = now.UtcNow.AddMinutes(-1 * config.GetValue <double>("LockoutPeriodMins"));
            int      consecutive_failures = await ef.LoginAttempts
                                            .Where(a => a.Alias.BeingID == being.BeingID &&
                                                   a.DateCreated >= period_start &&
                                                   !a.Success &&
                                                   !ef.LoginAttempts.Any(a2 => a2.Alias.BeingID == being.BeingID &&
                                                                         a2.LoginAttemptID > a.LoginAttemptID &&
                                                                         a2.Success))
                                            .CountAsync();

            if (consecutive_failures >= config.GetValue <int>("MaxFailedLoginsBeforeLockout"))
            {
                return(StatusCode(StatusCodes.Status503ServiceUnavailable));
            }

            // Check the password.
            bool password_ok = Sha512Util.TestPassword(body.password, being.SaltedHashedPassword);

            // Log the attempt.
            ef.LoginAttempts.Add(new LoginAttempt
            {
                AliasID    = alias.AliasID,
                Success    = password_ok,
                ClientName = client
            });
            await ef.SaveChangesAsync();

            return(password_ok ? (IActionResult)NoContent() : Unauthorized());
        }
Ejemplo n.º 10
0
        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");
            }
        }
Ejemplo n.º 11
0
        public async Task RightPasswordLockedOut_Login_ServiceUnavailable()
        {
            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(-15)
                        },
                        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 <StatusCodeResult>()
                .Which.StatusCode.Should().Be(StatusCodes.Status503ServiceUnavailable, "the being is locked");
                (await ef.LoginAttempts.CountAsync()).Should().Be(2, "no new record should be added");
            }
        }
        public async Task Alias_IndexPatchNoOldPasswordOrResetToken_BadRequest()
        {
            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
                {
                    password = "******"
                };
                IActionResult result = await patient.IndexPatch("*****@*****.**", body);

                result.Should().BeOfType <BadRequestResult>($"neither {nameof(body.resetToken)} nor {nameof(body.oldPassword)} was provided");
            }
        }
Ejemplo n.º 13
0
        public async Task <IActionResult> IndexPatch([EmailAddress, MaxLength(100)] string email_address,
                                                     [FromBody] IndexPatchRequestBody body)
        {
            if (!ModelState.IsValid)
            {
                return(BadRequest(ModelState));
            }

            // Get the being.
            Being being = (await ef.Aliases
                           .Include(a => a.Being)
                           .FirstOrDefaultAsync(a => a.EmailAddress == email_address))
                          ?.Being;

            if (being == null)
            {
                return(NotFound());
            }

            if (body.resetToken != null)
            {
                // Authenticate via reset token.
                if (body.oldPassword != null)
                {
                    // Only one of resetToken or oldPassword may be supplied.
                    return(BadRequest());
                }
                if (!being.PasswordResetTokenValidUntil.HasValue || being.PasswordResetTokenValidUntil <= now.UtcNow ||
                    being.PasswordResetToken != body.resetToken)
                {
                    return(Unauthorized());
                }
                if (Sha512Util.TestPassword(body.password, being.SaltedHashedPassword))
                {
                    // Cannot change password to itself.
                    return(StatusCode(StatusCodes.Status409Conflict));
                }
                // The token is used up.
                being.PasswordResetToken           = null;
                being.PasswordResetTokenValidUntil = null;
            }
            else
            {
                // Authenticate via old password.
                if (body.oldPassword == null)
                {
                    // One of resetToken or oldPassword must be supplied.
                    return(BadRequest());
                }
                if (!Sha512Util.TestPassword(body.oldPassword, being.SaltedHashedPassword))
                {
                    return(Unauthorized());
                }
                if (body.oldPassword == body.password)
                {
                    // Cannot change password to itself.
                    return(StatusCode(StatusCodes.Status409Conflict));
                }
            }

            // Change the password.
            being.SaltedHashedPassword = Sha512Util.SaltAndHashNewPassword(body.password);
            await ef.SaveChangesAsync();

            return(NoContent());
        }
Ejemplo n.º 14
0
 public void PasswordAndDifferentSalt_Crypt_ReturnsDifferentHash()
 {
     Sha512Util.Crypt("Hello world!", "$6$saltsdiffs").Should()
     .NotEndWith("svn8UoSVapNtMuq1ukKS4tPQd8iKwSMHWjl/O817G3uBnIFNjnQJuesI68u4OTLiBFdcbYEdFCoEOfaS35inz1",
                 "the salt has been changed");
 }
Ejemplo n.º 15
0
 public void PasswordAndSalt_Crypt_ReturnsExpectedHash()
 {
     Sha512Util.Crypt("Hello world!", "$6$saltstring").Should()
     .Be("$6$saltstring$svn8UoSVapNtMuq1ukKS4tPQd8iKwSMHWjl/O817G3uBnIFNjnQJuesI68u4OTLiBFdcbYEdFCoEOfaS35inz1",
         "that's what the PHP test says");
 }
Ejemplo n.º 16
0
 public void MismatchingPasswordAndHash_TestPassword_ReturnsFalse()
 {
     Sha512Util.TestPassword("Hello worldx!", "$6$saltstring$svn8UoSVapNtMuq1ukKS4tPQd8iKwSMHWjl/O817G3uBnIFNjnQJuesI68u4OTLiBFdcbYEdFCoEOfaS35inz1")
     .Should().BeFalse("the hash does not match the password");
 }