Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

PasswordGeneratorService #24

Merged
merged 7 commits into from
Nov 9, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions PWManager.Application/DependencyInjection.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
using Microsoft.Extensions.DependencyInjection;
using PWManager.Application.Services;
using PWManager.Domain.Services.Interfaces;

namespace PWManager.Application;

public static class DependencyInjection {

public static IServiceCollection AddApplicationServices(this IServiceCollection services) {

services.AddTransient<IPasswordGeneratorService, PasswordGeneratorService>();

return services;
}

}
10 changes: 10 additions & 0 deletions PWManager.Application/Interfaces/IUserSettings.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
using PWManager.Domain.ValueObjects;

namespace PWManager.Application.Interfaces;

public interface IUserSettings {

public PasswordGeneratorCriteria GetPasswordGeneratorCriteria();
public ClipboardTimeoutSetting GetClipboardTimeoutSetting();
public MainGroupSetting GetMainGroupSetting();
}
4 changes: 4 additions & 0 deletions PWManager.Application/PWManager.Application.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,8 @@
<ProjectReference Include="..\PWManager.Domain\PWManager.Domain.csproj" />
</ItemGroup>

<ItemGroup>
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="7.0.0" />
</ItemGroup>

</Project>
60 changes: 60 additions & 0 deletions PWManager.Application/Services/PasswordGeneratorService.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
using System.Text;
using PWManager.Application.Interfaces;
using PWManager.Domain.Exceptions;
using PWManager.Domain.Services.Interfaces;
using PWManager.Domain.ValueObjects;

namespace PWManager.Application.Services;

public class PasswordGeneratorService : IPasswordGeneratorService {

private readonly IUserSettings _userSettings;

private const string Lowercase = "abcdefghijklmnopqrstuvwxyz";
private const string Uppercase = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
private const string SpecialChars = "!#$%&*+,-.:;<=>?^_~";
private const string Numeric = "0123456789";
private const char Space = ' ';
private const string Brackets = "()[]{}";

private readonly Random _rng;

public PasswordGeneratorService(IUserSettings userSettings) : this(userSettings, new Random()) {
}

public PasswordGeneratorService(IUserSettings userSettings, Random rng) {
_userSettings = userSettings;
_rng = rng;
}

public string GeneratePasswordWith(PasswordGeneratorCriteria criteria) {
var possibleChars = BuildPossibleChars(criteria);
if (possibleChars.Length <= 0) {
throw new PasswordGenerationException("Possible Password character set is empty!");
}

var password = new StringBuilder();
var passwordLength = _rng.Next(criteria.MinLength, criteria.MaxLength + 1);
for (int i = 0; i < passwordLength; ++i) {
var randomCharIndex = _rng.Next(possibleChars.Length);
password.Append(possibleChars[randomCharIndex]);
}

return password.ToString();
}

public string GeneratePassword() {
return GeneratePasswordWith(_userSettings.GetPasswordGeneratorCriteria());
}

private static char[] BuildPossibleChars(PasswordGeneratorCriteria criteria) {
var allPossibleChars = new StringBuilder();
if (criteria.IncludeUpperCase) allPossibleChars.Append(Uppercase);
if (criteria.IncludeBrackets) allPossibleChars.Append(Brackets);
if (criteria.IncludeNumeric) allPossibleChars.Append(Numeric);
if (criteria.IncludeSpaces) allPossibleChars.Append(Space);
if (criteria.IncludeSpecial) allPossibleChars.Append(SpecialChars);
if (criteria.IncludeLowerCase) allPossibleChars.Append(Lowercase);
return allPossibleChars.ToString().ToCharArray();
}
}
10 changes: 10 additions & 0 deletions PWManager.Domain/Exceptions/PasswordGenerationException.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
using System.Runtime.Serialization;

namespace PWManager.Domain.Exceptions;

public class PasswordGenerationException : Exception {
public PasswordGenerationException() { }
protected PasswordGenerationException(SerializationInfo info, StreamingContext context) : base(info, context) { }
public PasswordGenerationException(string? message) : base(message) { }
public PasswordGenerationException(string? message, Exception? innerException) : base(message, innerException) { }
}
25 changes: 20 additions & 5 deletions PWManager.Domain/ValueObjects/PasswordGeneratorCriteria.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,19 +10,30 @@ public class PasswordGeneratorCriteria : ValueObject {
public bool IncludeSpecial { get; }
public bool IncludeSpaces { get; }
public bool IncludeBrackets { get; }
public uint MinLength { get; }
public uint MaxLength { get; }
public int MinLength { get; }
public int MaxLength { get; }

public PasswordGeneratorCriteria(bool includeLowerCase, bool includeUppercase, bool includeNumeric, bool includeSpecial, bool includeBrackets, bool includeSpaces, uint minLength, uint maxLength)
{
public PasswordGeneratorCriteria(bool includeLowerCase, bool includeUpperCase, bool includeNumeric, bool includeSpecial, bool includeBrackets, bool includeSpaces, int minLength, int maxLength) {
if (minLength <= 0) {
throw new ArgumentException("MinLength cannot be less than or equal to 0");
}

if (maxLength < minLength) {
throw new ArgumentException("MaxLength cannot be less than MinLength");
}

IncludeLowerCase = includeLowerCase;
IncludeUpperCase = includeUppercase;
IncludeUpperCase = includeUpperCase;
IncludeNumeric = includeNumeric;
IncludeSpecial = includeSpecial;
IncludeBrackets = includeBrackets;
IncludeSpaces = includeSpaces;
MinLength = minLength;
MaxLength = maxLength;

if (NoCharactersIncluded()) {
throw new ArgumentException("Password Generator must have some characters enabled!");
}
}

protected override IEnumerable<object> GetEqualityComponents() {
Expand All @@ -35,5 +46,9 @@ protected override IEnumerable<object> GetEqualityComponents() {
yield return MinLength;
yield return MaxLength;
}

private bool NoCharactersIncluded() {
return !IncludeLowerCase && !IncludeUpperCase && !IncludeNumeric && !IncludeSpecial && !IncludeBrackets;
}
}
}
47 changes: 47 additions & 0 deletions PWManager.UnitTests/Domain/ValueObjects/PasswordCriteriaTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
using System.Reflection;
using PWManager.Domain.ValueObjects;

namespace PWManager.UnitTests.Domain.ValueObjects;

public class PasswordCriteriaTests {

[Theory]
[InlineData(0)]
[InlineData(-1)]
public void Criteria_ShouldNot_AcceptInvalidMinLength(int minLength) {
var exception = Assert.Throws<ArgumentException>(() => {
var criteria = new PasswordGeneratorCriteria(true, true, true, true, true, true, minLength, 10);
});

Assert.Equal("MinLength cannot be less than or equal to 0", exception.Message);
}

[Fact]
public void Criteria_ShouldNot_AcceptInvalidMaxLength() {
var exception = Assert.Throws<ArgumentException>(() => {
var criteria = new PasswordGeneratorCriteria(true, true, true, true, true, true, 10, 5);
});

Assert.Equal("MaxLength cannot be less than MinLength", exception.Message);
}

[Fact]
public void Criteria_Should_AcceptEqualMinMaxLength() {
try {
var criteria = new PasswordGeneratorCriteria(true, true, true, true, true, true, 5, 5);
}
catch (Exception ex) {
Assert.Fail("Exception was thrown when Min and MaxLength were equal " + ex.Message);
}
}

[Fact]
public void Criteria_ShouldNot_AcceptNoIncludeSelected() {
var exception = Assert.Throws<ArgumentException>(() => {
var criteria = new PasswordGeneratorCriteria(false,false,false,false,false,false,5,5);
});

Assert.Equal("Password Generator must have some characters enabled!", exception.Message);

}
}
Loading