diff --git a/mRemoteNGTests/Security/PasswordCreation/PasswordIncludesUpperCaseConstraintTests.cs b/mRemoteNGTests/Security/PasswordCreation/PasswordIncludesUpperCaseConstraintTests.cs new file mode 100644 index 000000000..1e104fe5b --- /dev/null +++ b/mRemoteNGTests/Security/PasswordCreation/PasswordIncludesUpperCaseConstraintTests.cs @@ -0,0 +1,51 @@ +using System; +using mRemoteNG.Security; +using mRemoteNG.Security.PasswordCreation; +using NUnit.Framework; + + +namespace mRemoteNGTests.Security.PasswordCreation +{ + public class PasswordIncludesUpperCaseConstraintTests + { + private PasswordIncludesUpperCaseConstraint _lowerCaseConstraint; + + [Test] + public void PasswordThatExceedsMinimumLowerCasePassesValidation() + { + var password = "HELLO".ConvertToSecureString(); + _lowerCaseConstraint = new PasswordIncludesUpperCaseConstraint(); + Assert.That(_lowerCaseConstraint.Validate(password), Is.True); + } + + [Test] + public void PasswordThatMeetsMinimumLowerCasePassesValidation() + { + var password = "HELLO".ConvertToSecureString(); + _lowerCaseConstraint = new PasswordIncludesUpperCaseConstraint(5); + Assert.That(_lowerCaseConstraint.Validate(password), Is.True); + } + + [Test] + public void PasswordWithFewerThanMinimumLowerCaseFailsValidation() + { + var password = "Hello".ConvertToSecureString(); + _lowerCaseConstraint = new PasswordIncludesUpperCaseConstraint(2); + Assert.That(_lowerCaseConstraint.Validate(password), Is.False); + } + + [Test] + public void PasswordWithoutUpperCaseFailsValidation() + { + var password = "hello".ConvertToSecureString(); + _lowerCaseConstraint = new PasswordIncludesUpperCaseConstraint(); + Assert.That(_lowerCaseConstraint.Validate(password), Is.False); + } + + [Test] + public void CountToRequireMustBeAPositiveValue() + { + Assert.Throws(() => new PasswordIncludesLowerCaseConstraint(-1)); + } + } +} \ No newline at end of file diff --git a/mRemoteNGTests/mRemoteNGTests.csproj b/mRemoteNGTests/mRemoteNGTests.csproj index 5b2bea597..5038277e5 100644 --- a/mRemoteNGTests/mRemoteNGTests.csproj +++ b/mRemoteNGTests/mRemoteNGTests.csproj @@ -140,6 +140,7 @@ + diff --git a/mRemoteV1/Resources/Language/Language.Designer.cs b/mRemoteV1/Resources/Language/Language.Designer.cs index efe14e853..2c1e02936 100644 --- a/mRemoteV1/Resources/Language/Language.Designer.cs +++ b/mRemoteV1/Resources/Language/Language.Designer.cs @@ -3838,6 +3838,15 @@ namespace mRemoteNG { } } + /// + /// Looks up a localized string similar to Password must contain at least {0} upper case character(s). + /// + internal static string strPasswordContainsUpperCaseConstraintHint { + get { + return ResourceManager.GetString("strPasswordContainsUpperCaseConstraintHint", resourceCulture); + } + } + /// /// Looks up a localized string similar to Password length must be between {0} and {1}. /// diff --git a/mRemoteV1/Resources/Language/Language.resx b/mRemoteV1/Resources/Language/Language.resx index 080e5a5a3..05a6a1b75 100644 --- a/mRemoteV1/Resources/Language/Language.resx +++ b/mRemoteV1/Resources/Language/Language.resx @@ -2469,6 +2469,9 @@ mRemoteNG will now quit and begin with the installation. Password must contain at least {0} number(s) + + Password must contain at least {0} upper case character(s) + Password length must be between {0} and {1} diff --git a/mRemoteV1/Security/PasswordCreation/PasswordIncludesUpperCaseConstraint.cs b/mRemoteV1/Security/PasswordCreation/PasswordIncludesUpperCaseConstraint.cs new file mode 100644 index 000000000..bf5f60e57 --- /dev/null +++ b/mRemoteV1/Security/PasswordCreation/PasswordIncludesUpperCaseConstraint.cs @@ -0,0 +1,28 @@ +using System; +using System.Security; +using System.Text.RegularExpressions; + +namespace mRemoteNG.Security.PasswordCreation +{ + public class PasswordIncludesUpperCaseConstraint : IPasswordConstraint + { + private readonly int _minimumCount; + + public string ConstraintHint { get; } + + public PasswordIncludesUpperCaseConstraint(int minimumCount = 1) + { + if (minimumCount < 0) + throw new ArgumentException($"{nameof(minimumCount)} must be a positive value"); + + _minimumCount = minimumCount; + ConstraintHint = string.Format(Language.strPasswordContainsUpperCaseConstraintHint, _minimumCount); + } + + public bool Validate(SecureString password) + { + var regex = new Regex(@"[A-Z]"); + return regex.Matches(password.ConvertToUnsecureString()).Count >= _minimumCount; + } + } +} \ No newline at end of file diff --git a/mRemoteV1/mRemoteV1.csproj b/mRemoteV1/mRemoteV1.csproj index b05fd952c..1acf3e343 100644 --- a/mRemoteV1/mRemoteV1.csproj +++ b/mRemoteV1/mRemoteV1.csproj @@ -226,6 +226,7 @@ +