-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #11 from nodew/manage-password-policy
Manage password policies.
- Loading branch information
Showing
11 changed files
with
759 additions
and
3 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
215 changes: 215 additions & 0 deletions
215
src/PwSafeClient.CLI/Commands/PolicyManagement/AddPolicyCommand.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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
126
src/PwSafeClient.CLI/Commands/PolicyManagement/ListPoliciesCommand.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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
14
src/PwSafeClient.CLI/Commands/PolicyManagement/PolicyCommand.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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()); | ||
} | ||
} |
Oops, something went wrong.