diff --git a/mRemoteNGTests/Security/PasswordCreation/PasswordIncludesNumbersConstraintTests.cs b/mRemoteNGTests/Security/PasswordCreation/PasswordIncludesNumbersConstraintTests.cs new file mode 100644 index 000000000..17ccf2efe --- /dev/null +++ b/mRemoteNGTests/Security/PasswordCreation/PasswordIncludesNumbersConstraintTests.cs @@ -0,0 +1,43 @@ +using System; +using mRemoteNG.Security; +using mRemoteNG.Security.PasswordCreation; +using NUnit.Framework; + + +namespace mRemoteNGTests.Security.PasswordCreation +{ + public class PasswordIncludesNumbersConstraintTests + { + private PasswordIncludesNumbersConstraint _includesNumbersConstraint; + + [Test] + public void PasswordWithANumberPassesConstraint() + { + var password = "hello1".ConvertToSecureString(); + _includesNumbersConstraint = new PasswordIncludesNumbersConstraint(); + Assert.That(_includesNumbersConstraint.Validate(password), Is.True); + } + + [Test] + public void PasswordWithFewerThanTheMinimumCountOfNumbersFailsConstraint() + { + var password = "hello1".ConvertToSecureString(); + _includesNumbersConstraint = new PasswordIncludesNumbersConstraint(2); + Assert.That(_includesNumbersConstraint.Validate(password), Is.False); + } + + [Test] + public void PasswordWithoutNumbersFailsConstraint() + { + var password = "hello".ConvertToSecureString(); + _includesNumbersConstraint = new PasswordIncludesNumbersConstraint(); + Assert.That(_includesNumbersConstraint.Validate(password), Is.False); + } + + [Test] + public void CountOfNumbersToRequireMustBeAPositiveValue() + { + Assert.Throws(() => new PasswordIncludesNumbersConstraint(-1)); + } + } +} \ No newline at end of file diff --git a/mRemoteNGTests/mRemoteNGTests.csproj b/mRemoteNGTests/mRemoteNGTests.csproj index dfd426f4c..a1c10653a 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 a8e5851bb..3df8d126c 100644 --- a/mRemoteV1/Resources/Language/Language.Designer.cs +++ b/mRemoteV1/Resources/Language/Language.Designer.cs @@ -3820,6 +3820,15 @@ namespace mRemoteNG { } } + /// + /// Looks up a localized string similar to Password must contain at least {0} number(s). + /// + internal static string strPasswordContainsNumbersConstraint { + get { + return ResourceManager.GetString("strPasswordContainsNumbersConstraint", 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 8ec584a29..32c5e21ce 100644 --- a/mRemoteV1/Resources/Language/Language.resx +++ b/mRemoteV1/Resources/Language/Language.resx @@ -2463,6 +2463,9 @@ mRemoteNG will now quit and begin with the installation. Alert on Idle Disconnect + + Password must contain at least {0} number(s) + Password length must be between {0} and {1} diff --git a/mRemoteV1/Security/PasswordCreation/PasswordIncludesNumbersConstraint.cs b/mRemoteV1/Security/PasswordCreation/PasswordIncludesNumbersConstraint.cs new file mode 100644 index 000000000..581337128 --- /dev/null +++ b/mRemoteV1/Security/PasswordCreation/PasswordIncludesNumbersConstraint.cs @@ -0,0 +1,28 @@ +using System; +using System.Security; +using System.Text.RegularExpressions; + +namespace mRemoteNG.Security.PasswordCreation +{ + public class PasswordIncludesNumbersConstraint : IPasswordConstraint + { + private readonly int _minimumCount; + + public string ConstraintHint { get; } + + public PasswordIncludesNumbersConstraint(int minimumCount = 1) + { + if (minimumCount < 0) + throw new ArgumentException($"{nameof(minimumCount)} must be a positive value"); + + _minimumCount = minimumCount; + ConstraintHint = string.Format(Language.strPasswordContainsNumbersConstraint, _minimumCount); + } + + public bool Validate(SecureString password) + { + var regex = new Regex(@"\d"); + return regex.Matches(password.ConvertToUnsecureString()).Count >= _minimumCount; + } + } +} \ No newline at end of file diff --git a/mRemoteV1/mRemoteV1.csproj b/mRemoteV1/mRemoteV1.csproj index dae99cf1c..d40a9bb67 100644 --- a/mRemoteV1/mRemoteV1.csproj +++ b/mRemoteV1/mRemoteV1.csproj @@ -226,6 +226,7 @@ +