Skip to content

Commit

Permalink
When no valid password could be generated, throw an Exception if desired
Browse files Browse the repository at this point in the history
Fixes Issue prjseal#13 - Should throw instead of returning "Try again" when a valid password cannot be found
  • Loading branch information
saciervo committed Apr 14, 2021
1 parent f86da8e commit bac1f90
Show file tree
Hide file tree
Showing 3 changed files with 66 additions and 2 deletions.
17 changes: 17 additions & 0 deletions PasswordGenerator.Tests/BasicTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,23 @@ public void PasswordGenerator_LengthOnly_ShouldNotThrowAnError()
string result = pwd.Next();
Assert.AreEqual(21,result.Length);
}

[Test]
public void PasswordGenerator_ThrowOnMaximumAttemptsExceededEnabled_ShouldThrow()
{
Assert.Throws<MaximumAttemptsExceededException>(() =>
{
// Multiple attempts are needed to make sure the test throws
for (int i = 0; i < 1000; i++)
{
// Maximum complexity, minimal allowed attempts
var pwd = new Password(includeLowercase: true, includeUppercase: true, includeNumeric: true, includeSpecial: true, passwordLength: 4, maximumAttempts: 1)
.ThrowOnMaximumAttemptsExceeded();

pwd.Next();
}
});
}

[Test]
public void PasswordGenerator_NoLengthTest_ShouldNotThrowAnError()
Expand Down
21 changes: 21 additions & 0 deletions PasswordGenerator/MaximumAttemptsExceededException.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
using System;
using System.Runtime.Serialization;

namespace PasswordGenerator
{
/// <summary>
/// Represents errors that occurs during random password generation when the configured maximum attempts are exceeded.
/// </summary>
[Serializable]
public sealed class MaximumAttemptsExceededException : Exception
{
public MaximumAttemptsExceededException()
{
}

private MaximumAttemptsExceededException(SerializationInfo info, StreamingContext context)
: base(info, context)
{
}
}
}
30 changes: 28 additions & 2 deletions PasswordGenerator/Password.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

namespace PasswordGenerator
{
/// <summary>
/// <summary>
/// Generates random passwords and validates that they meet the rules passed in
/// </summary>
public class Password : IPassword
Expand All @@ -18,6 +18,7 @@ public class Password : IPassword
private const bool DefaultIncludeNumeric = true;
private const bool DefaultIncludeSpecial = true;
private static RNGCryptoServiceProvider _rng;
private bool _throwOnMaximumAttemptsExceeded;

public Password()
{
Expand Down Expand Up @@ -106,10 +107,25 @@ public IPassword LengthRequired(int passwordLength)
Settings.PasswordLength = passwordLength;
return this;
}

/// <summary>
/// Configures the password generator to throw an exception instead of returning "Try again" as a password when the maximum number of attempts exceeded.
/// </summary>
/// <remarks>See: https://github.com/prjseal/PasswordGenerator/issues/13</remarks>
/// <returns>The <see cref="IPassword"/> instance.</returns>
public IPassword ThrowOnMaximumAttemptsExceeded()
{
_throwOnMaximumAttemptsExceeded = true;
return this;
}

/// <summary>
/// Gets the next random password which meets the requirements
/// </summary>
/// <exception cref="MaximumAttemptsExceededException">
/// Thrown when no valid password could be generated (within the configured attempts threshold).
/// Only throws when throwing is configured.
/// </exception>
/// <returns>A password as a string</returns>
public string Next()
{
Expand All @@ -128,7 +144,17 @@ public string Next()
passwordAttempts++;
} while (passwordAttempts < Settings.MaximumAttempts && !PasswordIsValid(Settings, password));

password = PasswordIsValid(Settings, password) ? password : "Try again";
if (PasswordIsValid(Settings, password))
{
return password;
}

if (_throwOnMaximumAttemptsExceeded)
{
throw new MaximumAttemptsExceededException();
}

password = "Try again";
}

return password;
Expand Down

0 comments on commit bac1f90

Please sign in to comment.