Skip to content

Commit

Permalink
Merge pull request #241 from DentallApp/patch-16
Browse files Browse the repository at this point in the history
Add validation rule to check if the password is secure
  • Loading branch information
MrDave1999 authored Mar 7, 2024
2 parents 32494a1 + 550cb22 commit 7c97604
Show file tree
Hide file tree
Showing 10 changed files with 188 additions and 6 deletions.
2 changes: 1 addition & 1 deletion src/Core/Security/Employees/UseCases/Create.cs
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ public CreateEmployeeValidator()
RuleFor(request => request.UserName)
.NotEmpty()
.EmailAddress();
RuleFor(request => request.Password).NotEmpty();
RuleFor(request => request.Password).MustBeSecurePassword();
RuleFor(request => request.Document).NotEmpty();
RuleFor(request => request.Names).NotEmpty();
RuleFor(request => request.LastNames).NotEmpty();
Expand Down
2 changes: 1 addition & 1 deletion src/Core/Security/Employees/UseCases/UpdateAnyEmployee.cs
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ public UpdateAnyEmployeeValidator()
RuleFor(request => request.Email)
.NotEmpty()
.EmailAddress();
RuleFor(request => request.Password).NotEmpty();
RuleFor(request => request.Password).MustBeSecurePassword();
RuleFor(request => request.Document).NotEmpty();
RuleFor(request => request.Names).NotEmpty();
RuleFor(request => request.LastNames).NotEmpty();
Expand Down
2 changes: 1 addition & 1 deletion src/Core/Security/Users/UseCases/ChangePassword.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ public class ChangePasswordValidator : AbstractValidator<ChangePasswordRequest>
public ChangePasswordValidator()
{
RuleFor(request => request.OldPassword).NotEmpty();
RuleFor(request => request.NewPassword).NotEmpty();
RuleFor(request => request.NewPassword).MustBeSecurePassword();
}
}

Expand Down
2 changes: 1 addition & 1 deletion src/Core/Security/Users/UseCases/Create.cs
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ public CreateBasicUserValidator()
RuleFor(request => request.UserName)
.NotEmpty()
.EmailAddress();
RuleFor(request => request.Password).NotEmpty();
RuleFor(request => request.Password).MustBeSecurePassword();
RuleFor(request => request.Document).NotEmpty();
RuleFor(request => request.Names).NotEmpty();
RuleFor(request => request.LastNames).NotEmpty();
Expand Down
2 changes: 1 addition & 1 deletion src/Core/Security/Users/UseCases/ResetForgottenPassword.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ public class ResetForgottenPasswordValidator : AbstractValidator<ResetForgottenP
public ResetForgottenPasswordValidator()
{
RuleFor(request => request.Token).NotEmpty();
RuleFor(request => request.NewPassword).NotEmpty();
RuleFor(request => request.NewPassword).MustBeSecurePassword();
}
}

Expand Down
45 changes: 45 additions & 0 deletions src/Shared/Resources/ApiResponses/Messages.Designer.cs

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

15 changes: 15 additions & 0 deletions src/Shared/Resources/ApiResponses/Messages.resx
Original file line number Diff line number Diff line change
Expand Up @@ -252,6 +252,21 @@
<data name="OldPasswordIncorrect" xml:space="preserve">
<value>La contraseña antigua es incorrecta.</value>
</data>
<data name="PasswordHasNotLowerCaseLetters" xml:space="preserve">
<value>La contraseña debe tener al menos una letra minúscula.</value>
</data>
<data name="PasswordHasNotNumbers" xml:space="preserve">
<value>La contraseña debe tener al menos un número.</value>
</data>
<data name="PasswordHasNotUpperCaseLetters" xml:space="preserve">
<value>La contraseña debe tener al menos una letra mayúscula.</value>
</data>
<data name="PasswordIsEmpty" xml:space="preserve">
<value>La contraseña no puede estar vacía.</value>
</data>
<data name="PasswordMinimumCharacters" xml:space="preserve">
<value>La contraseña debe tener al menos 5 caracteres.</value>
</data>
<data name="PasswordResetTokenInvalid" xml:space="preserve">
<value>El token de restablecimiento de contraseña es inválido.</value>
</data>
Expand Down
53 changes: 53 additions & 0 deletions src/Shared/ValidationRules/PasswordValidator.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
namespace DentallApp.Shared.ValidationRules;

public static class PasswordValidator
{
public static IRuleBuilderOptions<T, string> MustBeSecurePassword<T>(
this IRuleBuilder<T, string> ruleBuilder)
{
return ruleBuilder.Must((rootObject, password, context) =>
{
if (string.IsNullOrWhiteSpace(password))
{
context.AddFailure(Messages.PasswordIsEmpty);
return false;
}
Result result = IsPasswordSecure(password);
if (result.IsSuccess)
return true;
foreach(string error in result.Errors)
context.AddFailure(error);
return false;
});
}

private static Result IsPasswordSecure(string password)
{
var errors = new List<string>();
if (password.Length < 5)
errors.Add(Messages.PasswordMinimumCharacters);

if (HasNotUpperCaseLetters(password))
errors.Add(Messages.PasswordHasNotUpperCaseLetters);

if (HasNotLowerCaseLetters(password))
errors.Add(Messages.PasswordHasNotLowerCaseLetters);

if (HasNotNumbers(password))
errors.Add(Messages.PasswordHasNotNumbers);

return errors.Count > 0 ? Result.Invalid(errors) : Result.Success();
}

private static bool HasNotUpperCaseLetters(string password)
=> !password.Where(c => c is >= 'A' and <= 'Z').Any();

private static bool HasNotLowerCaseLetters(string password)
=> !password.Where(c => c is >= 'a' and <= 'z').Any();

private static bool HasNotNumbers(string password)
=> !password.Where(c => c is >= '1' and <= '9').Any();
}
5 changes: 4 additions & 1 deletion tests/UnitTests/GlobalUsings.cs
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
global using System.Collections;
global using System.Text.Json;
global using System.Text.Json.Serialization;
global using System.Security.Claims;
global using NUnit.Framework;
global using FluentAssertions;
global using FluentValidation;
global using FluentValidation.TestHelper;
global using Microsoft.Bot.Schema;
global using Telerik.JustMock;
global using System.Security.Claims;
global using DotEnv.Core;

global using DentallApp.Core.Appointments.UseCases;
Expand All @@ -25,6 +27,7 @@
global using DentallApp.Shared.Reasons;
global using DentallApp.Shared.Constants;
global using DentallApp.Shared.Configuration;
global using DentallApp.Shared.ValidationRules;

global using Plugin.ChatBot.Factories;
global using Plugin.ChatBot.Models;
Expand Down
66 changes: 66 additions & 0 deletions tests/UnitTests/Shared/ValidationRules/PasswordValidatorTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
namespace UnitTests.Shared.ValidationRules;

public class PasswordValidatorTests
{
private class User
{
public string Password { get; init; }
}

private class UserValidator : AbstractValidator<User>
{
public UserValidator()
{
RuleFor(user => user.Password)
.MustBeSecurePassword();
}
}

[TestCase("1234")]
[TestCase("D234")]
[TestCase("DD34")]
[TestCase("d234")]
[TestCase("dd34")]
[TestCase("Da34")]
[TestCase("DaV4")]
[TestCase("Dav4")]
[TestCase("dAv4")]
[TestCase("DAVE")]
[TestCase("dave")]
[TestCase("DAve")]
[TestCase("123456789")]
[TestCase("dave12354")]
[TestCase("DAVE12354")]
[TestCase("daveeclop")]
[TestCase("DAVEECLOP")]
[TestCase("DaVeecLop")]
public void ShouldHaveErrorWhenPasswordIsInsecure(string password)
{
var validator = new UserValidator();
var model = new User { Password = password };
var result = validator.TestValidate(model);
result.ShouldHaveValidationErrorFor(user => user.Password);
}

[TestCase("Dsr2799")]
[TestCase("Dsr27")]
public void ShouldNotHaveErrorWhenPasswordIsSecure(string password)
{
var validator = new UserValidator();
var model = new User { Password = password };
var result = validator.TestValidate(model);
result.ShouldNotHaveValidationErrorFor(user => user.Password);
}

[TestCase("")]
[TestCase(" ")]
[TestCase(" ")]
[TestCase(null)]
public void ShouldHaveErrorWhenPasswordIsEmpty(string password)
{
var validator = new UserValidator();
var model = new User { Password = password };
var result = validator.TestValidate(model);
result.ShouldHaveValidationErrorFor(user => user.Password);
}
}

0 comments on commit 7c97604

Please sign in to comment.