Skip to content

Commit

Permalink
Merge pull request #11 from nodew/manage-password-policy
Browse files Browse the repository at this point in the history
Manage password policies.
  • Loading branch information
nodew authored Feb 1, 2024
2 parents 2ca6456 + 3677669 commit 780af04
Show file tree
Hide file tree
Showing 11 changed files with 759 additions and 3 deletions.
4 changes: 2 additions & 2 deletions Directory.Packages.props
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,11 @@
<ManagePackageVersionsCentrally>true</ManagePackageVersionsCentrally>
</PropertyGroup>
<ItemGroup>
<PackageVersion Include="Medo.PasswordSafe" Version="6.2.0" />
<PackageVersion Include="Medo.PasswordSafe" Version="6.2.1" />
<PackageVersion Include="System.CommandLine" Version="2.0.0-beta4.22272.1" />
<PackageVersion Include="System.CommandLine.Hosting" Version="0.4.0-alpha.22272.1" />
<PackageVersion Include="System.CommandLine.NamingConventionBinder" Version="2.0.0-beta4.22272.1" />
<PackageVersion Include="System.Text.Json" Version="8.0.0" />
<PackageVersion Include="System.Text.Json" Version="8.0.1" />
<PackageVersion Include="TextCopy" Version="6.2.1" />
<PackageVersion Include="Microsoft.NET.Test.Sdk" Version="17.6.0" />
<PackageVersion Include="MSTest.TestAdapter" Version="3.0.4" />
Expand Down
215 changes: 215 additions & 0 deletions src/PwSafeClient.CLI/Commands/PolicyManagement/AddPolicyCommand.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,215 @@
using System;
using System.CommandLine;
using System.CommandLine.Invocation;
using System.IO;
using System.Linq;
using System.Threading.Tasks;

using Medo.Security.Cryptography.PasswordSafe;

using PwSafeClient.CLI.Contracts.Helpers;
using PwSafeClient.CLI.Contracts.Services;
using PwSafeClient.CLI.Options;
using PwSafeClient.Shared;

namespace PwSafeClient.CLI.Commands;

public class AddPolicyCommand : Command
{
public AddPolicyCommand() : base("add", "Add new password policy")
{
AddOption(CommonOptions.AliasOption());

AddOption(CommonOptions.FileOption());

AddOption(PasswordPolicyOptions.NameOption());

AddOption(PasswordPolicyOptions.DigitsOption());

AddOption(PasswordPolicyOptions.UppercaseOption());

AddOption(PasswordPolicyOptions.LowercaseOption());

AddOption(PasswordPolicyOptions.SymbolsOption());

AddOption(PasswordPolicyOptions.SymbolCharsOption());

AddOption(PasswordPolicyOptions.HexDigitsOnlyOption());

AddOption(PasswordPolicyOptions.EasyVisionOption());

AddOption(PasswordPolicyOptions.PronounceableOption());

AddOption(PasswordPolicyOptions.LengthOption());
}

public class AddPolicyCommandHandler : CommandHandler
{
private readonly IConsoleService consoleService;
private readonly IDocumentHelper documentHelper;

public AddPolicyCommandHandler(IConsoleService consoleService, IDocumentHelper documentHelper)
{
this.consoleService = consoleService;
this.documentHelper = documentHelper;
}

public string? Alias { get; set; }

public FileInfo? File { get; set; }

public string Name { get; set; } = string.Empty;

public bool HexOnly { get; set; }

public bool EasyVision { get; set; }

public bool Pronounceable { get; set; }

public int Digits { get; set; }

public int Uppercase { get; set; }

public int Lowercase { get; set; }

public int Symbols { get; set; }

public int Length { get; set; }

public string? SymbolChars { get; set; }

public override async Task<int> InvokeAsync(InvocationContext context)
{
if (!HasValidatedInput())
{
return 1;
}

Document? document = await documentHelper.TryLoadDocumentAsync(Alias, File, false);
if (document == null)
{
return 1;
}

try
{
if (document.NamedPasswordPolicies.Any(policy => policy.Name == Name))
{
consoleService.LogError($"The password policy with name '{Name}' already exists.");
}

var policy = new NamedPasswordPolicy(Name, Length);

PasswordPolicyStyle style = 0;

if (HexOnly)
{
style |= PasswordPolicyStyle.UseHexDigits;
}

if (EasyVision)
{
style |= PasswordPolicyStyle.UseEasyVision;
}

if (Pronounceable)
{
style |= PasswordPolicyStyle.MakePronounceable;
}

if (Digits >= 0)
{
style |= PasswordPolicyStyle.UseDigits;
}

if (Uppercase >= 0)
{
style |= PasswordPolicyStyle.UseUppercase;
}

if (Lowercase >= 0)
{
style |= PasswordPolicyStyle.UseLowercase;
}

if (Symbols >= 0)
{
style |= PasswordPolicyStyle.UseSymbols;
}

policy.Style = style;
policy.MinimumDigitCount = FilterNegativeValue(Digits);
policy.MinimumUppercaseCount = FilterNegativeValue(Uppercase);
policy.MinimumLowercaseCount = FilterNegativeValue(Lowercase);
policy.MinimumSymbolCount = FilterNegativeValue(Symbols);

if (!string.IsNullOrWhiteSpace(SymbolChars))
{
policy.SetSpecialSymbolSet(SymbolChars.ToArray());
}
else
{
if (policy.Style.HasFlag(PasswordPolicyStyle.UseEasyVision))
{
policy.SetSpecialSymbolSet(PwCharPool.EasyVisionSymbolChars);
}
else
{
policy.SetSpecialSymbolSet(PwCharPool.StdSymbolChars);
}
}

document.NamedPasswordPolicies.Add(policy);

await documentHelper.SaveDocumentAsync(Alias, File);
consoleService.LogSuccess($"Password policy '{policy.Name}' is added");
}
catch (Exception ex)
{
consoleService.LogError($"Error: {ex.Message}");
return 1;
}

return 0;
}

private static int FilterNegativeValue(int value)
{
return value < 0 ? 0 : value;
}

private bool HasValidatedInput()
{
if (Length < 6)
{
consoleService.LogError("The password must contain no less than 6 characters.");
return false;
}

int constraintsLength = FilterNegativeValue(Digits) + FilterNegativeValue(Uppercase) + FilterNegativeValue(Lowercase) + FilterNegativeValue(Symbols);

if (constraintsLength > Length)
{
consoleService.LogError("The password length is less than the sum of 'at least' constraints.");
return false;
}

if (!string.IsNullOrWhiteSpace(SymbolChars))
{
if (PwCharPool.HasDuplicatedCharacters(SymbolChars))
{
consoleService.LogError("The symbol characters contain duplicated characters.");
return false;
}

if (!PwCharPool.IsValidSymbols(SymbolChars))
{
consoleService.LogError("The symbol characters contain invalid characters.");
return false;
}
}

return true;
}
}
}
126 changes: 126 additions & 0 deletions src/PwSafeClient.CLI/Commands/PolicyManagement/ListPoliciesCommand.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
using System;
using System.CommandLine;
using System.CommandLine.Invocation;
using System.IO;
using System.Linq;
using System.Threading.Tasks;

using Medo.Security.Cryptography.PasswordSafe;

using PwSafeClient.CLI.Contracts.Helpers;
using PwSafeClient.CLI.Contracts.Services;
using PwSafeClient.CLI.Options;

namespace PwSafeClient.CLI.Commands;

public class ListPoliciesCommand : Command
{
public ListPoliciesCommand() : base("list", "List all password policies")
{
AddOption(CommonOptions.AliasOption());

AddOption(CommonOptions.FileOption());
}

public class ListPoliciesCommandHandler : CommandHandler
{
private readonly IConsoleService consoleService;
private readonly IDocumentHelper documentHelper;

public ListPoliciesCommandHandler(IConsoleService consoleService, IDocumentHelper documentHelper)
{
this.consoleService = consoleService;
this.documentHelper = documentHelper;
}

public string? Alias { get; set; }

public FileInfo? File { get; set; }

public override async Task<int> InvokeAsync(InvocationContext context)
{
Document? document = await documentHelper.TryLoadDocumentAsync(Alias, File, true);
if (document == null)
{
return 1;
}

try
{
if (document.NamedPasswordPolicies.Any())
{
foreach (NamedPasswordPolicy policy in document.NamedPasswordPolicies)
{
PrintPasswordPolicy(policy);
}

return 0;
}
else
{
Console.WriteLine("No password policies found.");
return 1;
}
}
catch (Exception ex)
{
consoleService.LogError(ex.Message);
return 1;
}
}

private void PrintPasswordPolicy(NamedPasswordPolicy policy)
{
bool isPronounceable = policy.Style.HasFlag(PasswordPolicyStyle.MakePronounceable);
bool useLowercase = policy.Style.HasFlag(PasswordPolicyStyle.UseLowercase);
bool useUppercase = policy.Style.HasFlag(PasswordPolicyStyle.UseUppercase);
bool useDigits = policy.Style.HasFlag(PasswordPolicyStyle.UseDigits);
bool useSymbols = policy.Style.HasFlag(PasswordPolicyStyle.UseSymbols);

Console.WriteLine($"{policy.Name}:");
Console.WriteLine($" Password length: {policy.TotalPasswordLength}.");

if (policy.Style.HasFlag(PasswordPolicyStyle.UseHexDigits))
{
Console.WriteLine($" Hex digits only.");
return;
}

Console.WriteLine($" Pronounceable: {isPronounceable}.");
Console.WriteLine($" Easy vision: {policy.Style.HasFlag(PasswordPolicyStyle.UseEasyVision)}.");

Console.WriteLine();
Console.WriteLine($" Use lowercase: {useLowercase}.");
if (useLowercase && !isPronounceable)
{
Console.WriteLine($" Minimum lowercase: {policy.MinimumLowercaseCount}.");
}

Console.WriteLine();
Console.WriteLine($" Use uppercase: {useUppercase}.");
if (useUppercase && !isPronounceable)
{
Console.WriteLine($" Minimum uppercase: {policy.MinimumUppercaseCount}.");
}

Console.WriteLine();
Console.WriteLine($" Use digits: {useDigits}.");
if (useDigits && !isPronounceable)
{
Console.WriteLine($" Minimum digits: {policy.MinimumDigitCount}.");
}

Console.WriteLine();
if (!isPronounceable)
{
Console.WriteLine($" Use symbols: {useSymbols}.");

if (useSymbols)
{
Console.WriteLine($" Minimum symbols: {policy.MinimumSymbolCount}.");
Console.WriteLine($" Symbols: {policy.GetSpecialSymbolSet()}.");
}
}
}
}
}
14 changes: 14 additions & 0 deletions src/PwSafeClient.CLI/Commands/PolicyManagement/PolicyCommand.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
using System.CommandLine;

namespace PwSafeClient.CLI.Commands;

public class PolicyCommand : Command
{
public PolicyCommand() : base("policy", "Manage your password policies")
{
AddCommand(new ListPoliciesCommand());
AddCommand(new AddPolicyCommand());
AddCommand(new RemovePolicyCommand());
AddCommand(new UpdatePolicyCommand());
}
}
Loading

0 comments on commit 780af04

Please sign in to comment.