From 5280ef32cdbbab75f588a4ff29611c62907a08c1 Mon Sep 17 00:00:00 2001 From: David Sparer Date: Sat, 28 Jan 2017 14:10:04 -0700 Subject: [PATCH] created a has-special-characters constraint --- ...ncludesSpecialCharactersConstraintTests.cs | 73 +++++++++++++++++++ mRemoteNGTests/mRemoteNGTests.csproj | 1 + .../Resources/Language/Language.Designer.cs | 9 +++ mRemoteV1/Resources/Language/Language.resx | 3 + ...wordIncludesSpecialCharactersConstraint.cs | 41 +++++++++++ mRemoteV1/mRemoteV1.csproj | 1 + 6 files changed, 128 insertions(+) create mode 100644 mRemoteNGTests/Security/PasswordCreation/PasswordIncludesSpecialCharactersConstraintTests.cs create mode 100644 mRemoteV1/Security/PasswordCreation/PasswordIncludesSpecialCharactersConstraint.cs diff --git a/mRemoteNGTests/Security/PasswordCreation/PasswordIncludesSpecialCharactersConstraintTests.cs b/mRemoteNGTests/Security/PasswordCreation/PasswordIncludesSpecialCharactersConstraintTests.cs new file mode 100644 index 00000000..2ba1747e --- /dev/null +++ b/mRemoteNGTests/Security/PasswordCreation/PasswordIncludesSpecialCharactersConstraintTests.cs @@ -0,0 +1,73 @@ +using System; +using mRemoteNG.Security; +using mRemoteNG.Security.PasswordCreation; +using NUnit.Framework; + + +namespace mRemoteNGTests.Security.PasswordCreation +{ + public class PasswordIncludesSpecialCharactersConstraintTests + { + private PasswordIncludesSpecialCharactersConstraint _specialCharactersConstraint; + + [Test] + public void PasswordWithMinimumSpecialCharsPassesValidation() + { + var password = "hello$".ConvertToSecureString(); + _specialCharactersConstraint = new PasswordIncludesSpecialCharactersConstraint(); + Assert.That(_specialCharactersConstraint.Validate(password), Is.True); + } + + [Test] + public void PasswordExceedingMinimumSpecialCharsPassesValidation() + { + var password = "hello!#%$".ConvertToSecureString(); + _specialCharactersConstraint = new PasswordIncludesSpecialCharactersConstraint(3); + Assert.That(_specialCharactersConstraint.Validate(password), Is.True); + } + + [Test] + public void PasswordUnderMinimumSpecialCharsFailsValidation() + { + var password = "hello!".ConvertToSecureString(); + _specialCharactersConstraint = new PasswordIncludesSpecialCharactersConstraint(2); + Assert.That(_specialCharactersConstraint.Validate(password), Is.False); + } + + [Test] + public void PasswordWithoutSpecialCharsFailsValidation() + { + var password = "hello".ConvertToSecureString(); + _specialCharactersConstraint = new PasswordIncludesSpecialCharactersConstraint(); + Assert.That(_specialCharactersConstraint.Validate(password), Is.False); + } + + [Test] + public void PasswordMatchingCustomCharsPassesValidation() + { + var password = "hello(".ConvertToSecureString(); + _specialCharactersConstraint = new PasswordIncludesSpecialCharactersConstraint(new[] {'('}); + Assert.That(_specialCharactersConstraint.Validate(password), Is.True); + } + + [Test] + public void PasswordWithoutCustomCharsFailsValidation() + { + var password = "hello!".ConvertToSecureString(); + _specialCharactersConstraint = new PasswordIncludesSpecialCharactersConstraint(new[] { '(' }); + Assert.That(_specialCharactersConstraint.Validate(password), Is.False); + } + + [Test] + public void CantProvideNullListOfCharacters() + { + Assert.Throws(() => new PasswordIncludesSpecialCharactersConstraint(null)); + } + + [Test] + public void MinimumCountMustBeAPositiveValue() + { + Assert.Throws(() => new PasswordIncludesSpecialCharactersConstraint(-1)); + } + } +} \ No newline at end of file diff --git a/mRemoteNGTests/mRemoteNGTests.csproj b/mRemoteNGTests/mRemoteNGTests.csproj index 5038277e..5bde74f7 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 2c1e0293..ea56087c 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} of the following characters: {1}. + /// + internal static string strPasswordConstainsSpecialCharactersConstraintHint { + get { + return ResourceManager.GetString("strPasswordConstainsSpecialCharactersConstraintHint", resourceCulture); + } + } + /// /// Looks up a localized string similar to Password must contain at least {0} lower case character(s). /// diff --git a/mRemoteV1/Resources/Language/Language.resx b/mRemoteV1/Resources/Language/Language.resx index 05a6a1b7..3e7eea97 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} of the following characters: {1} + Password must contain at least {0} lower case character(s) diff --git a/mRemoteV1/Security/PasswordCreation/PasswordIncludesSpecialCharactersConstraint.cs b/mRemoteV1/Security/PasswordCreation/PasswordIncludesSpecialCharactersConstraint.cs new file mode 100644 index 00000000..46bf1616 --- /dev/null +++ b/mRemoteV1/Security/PasswordCreation/PasswordIncludesSpecialCharactersConstraint.cs @@ -0,0 +1,41 @@ +using System; +using System.Collections.Generic; +using System.Security; +using System.Text.RegularExpressions; + + +namespace mRemoteNG.Security.PasswordCreation +{ + public class PasswordIncludesSpecialCharactersConstraint : IPasswordConstraint + { + private readonly int _minimumCount; + + public IEnumerable SpecialCharacters { get; } = new []{'!','@','#','$','%','^','&','*'}; + + public string ConstraintHint { get; } + + public PasswordIncludesSpecialCharactersConstraint(int minimumCount = 1) + { + if (minimumCount < 0) + throw new ArgumentException($"{nameof(minimumCount)} must be a positive value"); + + _minimumCount = minimumCount; + } + + public PasswordIncludesSpecialCharactersConstraint(IEnumerable specialCharacters, int minimumCount = 1) + : this(minimumCount) + { + if (specialCharacters == null) + throw new ArgumentNullException(nameof(specialCharacters)); + + SpecialCharacters = specialCharacters; + ConstraintHint = string.Format(Language.strPasswordConstainsSpecialCharactersConstraintHint, _minimumCount, string.Concat(SpecialCharacters)); + } + + public bool Validate(SecureString password) + { + var regex = new Regex($"[{string.Concat(SpecialCharacters)}]"); + return regex.Matches(password.ConvertToUnsecureString()).Count >= _minimumCount; + } + } +} \ No newline at end of file diff --git a/mRemoteV1/mRemoteV1.csproj b/mRemoteV1/mRemoteV1.csproj index 1acf3e34..008e2aef 100644 --- a/mRemoteV1/mRemoteV1.csproj +++ b/mRemoteV1/mRemoteV1.csproj @@ -226,6 +226,7 @@ +