Skip to content

Instantly share code, notes, and snippets.

@makafanpeter
Last active July 18, 2024 17:22
Show Gist options
  • Save makafanpeter/c7c393b3d5dd29d2aa001ef92fd24fde to your computer and use it in GitHub Desktop.
Save makafanpeter/c7c393b3d5dd29d2aa001ef92fd24fde to your computer and use it in GitHub Desktop.
using System.Text.RegularExpressions;
var pRequirement = new PasswordRequirement(8, true, true, true, true);
var strategies = new List<IPasswordRequirementStrategy>
{
new MinimumLengthRequirementStrategy(pRequirement.MinimumLength),
};
if (pRequirement.RequireUppercase)
{
strategies.Add(new UppercaseRequirementStrategy());
}
if (pRequirement.RequireDigit)
{
strategies.Add(new DigitRequirementStrategy());
}
if (pRequirement.RequireLowercase)
{
strategies.Add(new LowercaseRequirementStrategy());
}
if (pRequirement.RequireNonAlphanumeric)
{
strategies.Add(new NonAlphanumericRequirementStrategy());
}
var validationContext = new PasswordValidationContext(strategies);
var testPassword = "SecureP@ssword123";
var validationResults = validationContext.ValidatePassword(testPassword);
Console.WriteLine(validationResults.ToString());
public interface IPasswordRequirementStrategy
{
SecurityResult Validate(string password);
}
public class NonAlphanumericRequirementStrategy : IPasswordRequirementStrategy
{
public SecurityResult Validate(string password)
{
Match specialCharacter = Regex.Match(password, @"^(?=.*[^\da-zA-Z])");
if (!specialCharacter.Success)
{
return SecurityResult.Failed("The password must contain at least one non-alphanumeric character.");
}
return SecurityResult.Success;
}
}
public class DigitRequirementStrategy : IPasswordRequirementStrategy
{
public SecurityResult Validate(string password)
{
Match digit = Regex.Match(password, @"^(?=.*\d)");
if (!digit.Success)
{
SecurityResult.Failed("The password must contain at least one digit.");
}
return SecurityResult.Success;
}
}
public class LowercaseRequirementStrategy : IPasswordRequirementStrategy
{
public SecurityResult Validate(string password)
{
Match lowercase = Regex.Match(password, @"^(?=.*[a-z])");
return !lowercase.Success ? SecurityResult.Failed("The password must contain at least one lowercase character.") : SecurityResult.Success;
}
}
public class UppercaseRequirementStrategy : IPasswordRequirementStrategy
{
public SecurityResult Validate(string password)
{
Match uppercase = Regex.Match(password, @"^(?=.*[A-Z])");
return !uppercase.Success ? SecurityResult.Failed("The password must contain at least one lowercase character.") : SecurityResult.Success;
}
}
public class MinimumLengthRequirementStrategy(int minimumLength) : IPasswordRequirementStrategy
{
public SecurityResult Validate(string password)
{
return password.Length >= minimumLength ? SecurityResult.Success : SecurityResult.Failed($"The password must be over {minimumLength} characters.");
}
}
public class PasswordValidationContext(IList<IPasswordRequirementStrategy> strategies)
{
public SecurityResult ValidatePassword(string password)
{
SecurityResult result;
if (string.IsNullOrEmpty(password))
{
result = SecurityResult.Failed("The password cannot be empty");
return result;
}
foreach (var strategy in strategies)
{
result = strategy.Validate(password);
if (!result.Succeeded)
{
return result;
}
}
return SecurityResult.Success;
}
}
public class SecurityResult
{
private static readonly SecurityResult _success = new SecurityResult { Succeeded = true };
private readonly List<string> _errors = new List<string>();
/// <summary>
/// Flag indicating whether if the operation succeeded or not.
/// </summary>
/// <value>True if the operation succeeded, otherwise false.</value>
public bool Succeeded { get; protected set; }
/// <summary>
/// An <see cref="IEnumerable{T}"/> of <see cref="SecurityResult"/>s containing an errors
/// that occurred during the identity operation.
/// </summary>
/// <value>An <see cref="IEnumerable{T}"/> of <see cref="SecurityResult"/>s.</value>
public IEnumerable<string> Errors => _errors;
/// <summary>
/// Returns an <see cref="SecurityResult"/> indicating a successful identity operation.
/// </summary>
/// <returns>An <see cref="SecurityResult"/> indicating a successful operation.</returns>
public static SecurityResult Success => _success;
/// <summary>
/// Creates an <see cref="SecurityResult"/> indicating a failed identity operation, with a list of <paramref name="errors"/> if applicable.
/// </summary>
/// <param name="errors">An optional array of <see cref="SecurityResult"/>s which caused the operation to fail.</param>
/// <returns>An <see cref="SecurityResult"/> indicating a failed identity operation, with a list of <paramref name="errors"/> if applicable.</returns>
public static SecurityResult Failed(params string[] errors)
{
var result = new SecurityResult { Succeeded = false };
if (errors != null)
{
result._errors.AddRange(errors);
}
return result;
}
public static SecurityResult Failed(string errors)
{
var result = new SecurityResult { Succeeded = false };
if (errors != null)
{
result._errors.Add(errors);
}
return result;
}
/// <summary>
/// Converts the value of the current <see cref="SecurityResult"/> object to its equivalent string representation.
/// </summary>
/// <returns>A string representation of the current <see cref="SecurityResult"/> object.</returns>
/// <remarks>
/// If the operation was successful the ToString() will return "Succeeded" otherwise it returned
/// "Failed : " followed by a comma delimited list of error codes from its <see cref="Errors"/> collection, if any.
/// </remarks>
public override string ToString()
{
return Succeeded ? "Succeeded" : $"Failed : {string.Join(",", Errors.Select(x => x).ToList())}";
}
}
public record PasswordRequirement(
int MinimumLength,
bool RequireUppercase,
bool RequireLowercase,
bool RequireNonAlphanumeric,
bool RequireDigit
);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment