mirror of
https://github.com/bitwarden/android.git
synced 2025-01-11 18:57:39 +03:00
[PM-1402] Refactor PasswordGenerationService alongside PolicyService (#2443)
* PM-1402 Refactor pass generation service alongside policyservice * PM-1402 Refactor PasswordGenerationService and PolicyService to have a simpler code and more specific to each class * PM-1402 Fix format * PM-1402 Moved policy consts from PolicyService to Policy * PM-1402 fix crash due to lack of null checking * PM-1402 fix format * PM-1402 removed GetValueOrDefault() given that it was not needed and was changing the behavior
This commit is contained in:
parent
f24b82f345
commit
1c8328f62d
12 changed files with 282 additions and 352 deletions
|
@ -121,9 +121,8 @@ namespace Bit.App.Pages
|
|||
|
||||
var ssoToken = response.Token;
|
||||
|
||||
|
||||
var passwordOptions = new PasswordGenerationOptions(true);
|
||||
passwordOptions.Length = 64;
|
||||
var passwordOptions = PasswordGenerationOptions.CreateDefault
|
||||
.WithLength(64);
|
||||
|
||||
var codeVerifier = await _passwordGenerationService.GeneratePasswordAsync(passwordOptions);
|
||||
var codeVerifierHash = await _cryptoFunctionService.HashAsync(codeVerifier, CryptoHashAlgorithm.Sha256);
|
||||
|
|
|
@ -827,7 +827,7 @@ namespace Bit.App.Pages
|
|||
private void SetOptions()
|
||||
{
|
||||
_options.AllowAmbiguousChar = AllowAmbiguousChars;
|
||||
_options.Type = PasswordTypeSelectedIndex == 1 ? "passphrase" : "password";
|
||||
_options.Type = PasswordTypeSelectedIndex == 1 ? PasswordGenerationOptions.TYPE_PASSPHRASE : PasswordGenerationOptions.TYPE_PASSWORD;
|
||||
_options.MinNumber = MinNumber;
|
||||
_options.MinSpecial = MinSpecial;
|
||||
_options.Special = Special;
|
||||
|
|
|
@ -131,7 +131,7 @@ namespace Bit.App.Pages
|
|||
{
|
||||
// if we have a vault timeout policy, we need to filter the timeout options
|
||||
_vaultTimeoutPolicy = (await _policyService.GetAll(PolicyType.MaximumVaultTimeout)).First();
|
||||
var policyMinutes = _policyService.GetPolicyInt(_vaultTimeoutPolicy, PolicyService.TIMEOUT_POLICY_MINUTES);
|
||||
var policyMinutes = _vaultTimeoutPolicy.GetInt(Policy.MINUTES_KEY);
|
||||
_vaultTimeoutOptions = _vaultTimeoutOptions.Where(t =>
|
||||
t.Value <= policyMinutes &&
|
||||
(t.Value > 0 || t.Value == CustomVaultTimeoutValue) &&
|
||||
|
@ -302,7 +302,7 @@ namespace Bit.App.Pages
|
|||
|
||||
if (_vaultTimeoutPolicy != null)
|
||||
{
|
||||
var maximumTimeout = _policyService.GetPolicyInt(_vaultTimeoutPolicy, PolicyService.TIMEOUT_POLICY_MINUTES);
|
||||
var maximumTimeout = _vaultTimeoutPolicy.GetInt(Policy.MINUTES_KEY);
|
||||
|
||||
if (newTimeout > maximumTimeout)
|
||||
{
|
||||
|
@ -382,7 +382,7 @@ namespace Bit.App.Pages
|
|||
public async Task VaultTimeoutActionAsync()
|
||||
{
|
||||
if (_vaultTimeoutPolicy != null &&
|
||||
!string.IsNullOrEmpty(_policyService.GetPolicyString(_vaultTimeoutPolicy, PolicyService.TIMEOUT_POLICY_ACTION)))
|
||||
!string.IsNullOrEmpty(_vaultTimeoutPolicy.GetString(Policy.MINUTES_KEY)))
|
||||
{
|
||||
// do nothing if we have a policy set
|
||||
return;
|
||||
|
@ -610,8 +610,8 @@ namespace Bit.App.Pages
|
|||
}
|
||||
if (_vaultTimeoutPolicy != null)
|
||||
{
|
||||
var policyMinutes = _policyService.GetPolicyInt(_vaultTimeoutPolicy, PolicyService.TIMEOUT_POLICY_MINUTES);
|
||||
var policyAction = _policyService.GetPolicyString(_vaultTimeoutPolicy, PolicyService.TIMEOUT_POLICY_ACTION);
|
||||
var policyMinutes = _vaultTimeoutPolicy.GetInt(Policy.MINUTES_KEY);
|
||||
var policyAction = _vaultTimeoutPolicy.GetString(Policy.ACTION_KEY);
|
||||
|
||||
if (policyMinutes.HasValue || !string.IsNullOrWhiteSpace(policyAction))
|
||||
{
|
||||
|
@ -625,14 +625,14 @@ namespace Bit.App.Pages
|
|||
else if (!policyMinutes.HasValue && !string.IsNullOrWhiteSpace(policyAction))
|
||||
{
|
||||
policyAlert = string.Format(AppResources.VaultTimeoutActionPolicyInEffect,
|
||||
policyAction == PolicyService.TIMEOUT_POLICY_ACTION_LOCK ? AppResources.Lock : AppResources.LogOut);
|
||||
policyAction == Policy.ACTION_LOCK ? AppResources.Lock : AppResources.LogOut);
|
||||
}
|
||||
else
|
||||
{
|
||||
policyAlert = string.Format(AppResources.VaultTimeoutPolicyWithActionInEffect,
|
||||
Math.Floor((float)policyMinutes / 60),
|
||||
policyMinutes % 60,
|
||||
policyAction == PolicyService.TIMEOUT_POLICY_ACTION_LOCK ? AppResources.Lock : AppResources.LogOut);
|
||||
policyAction == Policy.ACTION_LOCK ? AppResources.Lock : AppResources.LogOut);
|
||||
}
|
||||
securityItems.Insert(0, new SettingsPageListItem
|
||||
{
|
||||
|
|
|
@ -15,8 +15,6 @@ namespace Bit.Core.Abstractions
|
|||
Task<string> GeneratePasswordAsync(PasswordGenerationOptions options);
|
||||
Task<List<GeneratedPasswordHistory>> GetHistoryAsync();
|
||||
Task<(PasswordGenerationOptions, PasswordGeneratorPolicyOptions)> GetOptionsAsync();
|
||||
Task<(PasswordGenerationOptions, PasswordGeneratorPolicyOptions)>
|
||||
EnforcePasswordGeneratorPoliciesOnOptionsAsync(PasswordGenerationOptions options);
|
||||
Result PasswordStrength(string password, List<string> userInputs = null);
|
||||
Task SaveOptionsAsync(PasswordGenerationOptions options);
|
||||
void NormalizeOptions(PasswordGenerationOptions options, PasswordGeneratorPolicyOptions enforcedPolicyOptions);
|
||||
|
|
|
@ -19,8 +19,7 @@ namespace Bit.Core.Abstractions
|
|||
Tuple<ResetPasswordPolicyOptions, bool> GetResetPasswordPolicyOptions(IEnumerable<Policy> policies,
|
||||
string orgId);
|
||||
Task<bool> PolicyAppliesToUser(PolicyType policyType, Func<Policy, bool> policyFilter = null, string userId = null);
|
||||
int? GetPolicyInt(Policy policy, string key);
|
||||
string GetPolicyString(Policy policy, string key);
|
||||
Task<bool> ShouldShowVaultFilterAsync();
|
||||
Task<PasswordGeneratorPolicyOptions> GetPasswordGeneratorPolicyOptionsAsync();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,29 +2,29 @@
|
|||
{
|
||||
public class PasswordGenerationOptions
|
||||
{
|
||||
public PasswordGenerationOptions() { }
|
||||
public const string TYPE_PASSWORD = "password";
|
||||
public const string TYPE_PASSPHRASE = "passphrase";
|
||||
|
||||
public PasswordGenerationOptions(bool defaultOptions)
|
||||
public static PasswordGenerationOptions CreateDefault => new PasswordGenerationOptions
|
||||
{
|
||||
if (defaultOptions)
|
||||
{
|
||||
Length = 14;
|
||||
AllowAmbiguousChar = true;
|
||||
Number = true;
|
||||
MinNumber = 1;
|
||||
Uppercase = true;
|
||||
MinUppercase = 0;
|
||||
Lowercase = true;
|
||||
MinLowercase = 0;
|
||||
Special = false;
|
||||
MinSpecial = 1;
|
||||
Type = "password";
|
||||
NumWords = 3;
|
||||
WordSeparator = "-";
|
||||
Capitalize = false;
|
||||
IncludeNumber = false;
|
||||
}
|
||||
}
|
||||
Length = 14,
|
||||
AllowAmbiguousChar = true,
|
||||
Number = true,
|
||||
MinNumber = 1,
|
||||
Uppercase = true,
|
||||
MinUppercase = 0,
|
||||
Lowercase = true,
|
||||
MinLowercase = 0,
|
||||
Special = false,
|
||||
MinSpecial = 1,
|
||||
Type = TYPE_PASSWORD,
|
||||
NumWords = 3,
|
||||
WordSeparator = "-",
|
||||
Capitalize = false,
|
||||
IncludeNumber = false
|
||||
};
|
||||
|
||||
public PasswordGenerationOptions() { }
|
||||
|
||||
public int? Length { get; set; }
|
||||
public bool? AllowAmbiguousChar { get; set; }
|
||||
|
@ -42,6 +42,12 @@
|
|||
public bool? Capitalize { get; set; }
|
||||
public bool? IncludeNumber { get; set; }
|
||||
|
||||
public PasswordGenerationOptions WithLength(int? length)
|
||||
{
|
||||
Length = length;
|
||||
return this;
|
||||
}
|
||||
|
||||
public void Merge(PasswordGenerationOptions defaults)
|
||||
{
|
||||
Length = Length ?? defaults.Length;
|
||||
|
@ -60,5 +66,75 @@
|
|||
Capitalize = Capitalize ?? defaults.Capitalize;
|
||||
IncludeNumber = IncludeNumber ?? defaults.IncludeNumber;
|
||||
}
|
||||
|
||||
public void EnforcePolicy(PasswordGeneratorPolicyOptions enforcedPolicyOptions)
|
||||
{
|
||||
if (enforcedPolicyOptions is null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (Length < enforcedPolicyOptions.MinLength)
|
||||
{
|
||||
Length = enforcedPolicyOptions.MinLength;
|
||||
}
|
||||
|
||||
if (enforcedPolicyOptions.UseUppercase)
|
||||
{
|
||||
Uppercase = true;
|
||||
}
|
||||
|
||||
if (enforcedPolicyOptions.UseLowercase)
|
||||
{
|
||||
Lowercase = true;
|
||||
}
|
||||
|
||||
if (enforcedPolicyOptions.UseNumbers)
|
||||
{
|
||||
Number = true;
|
||||
}
|
||||
|
||||
if (MinNumber < enforcedPolicyOptions.NumberCount)
|
||||
{
|
||||
MinNumber = enforcedPolicyOptions.NumberCount;
|
||||
}
|
||||
|
||||
if (enforcedPolicyOptions.UseSpecial)
|
||||
{
|
||||
Special = true;
|
||||
}
|
||||
|
||||
if (MinSpecial < enforcedPolicyOptions.SpecialCount)
|
||||
{
|
||||
MinSpecial = enforcedPolicyOptions.SpecialCount;
|
||||
}
|
||||
|
||||
// Must normalize these fields because the receiving call expects all options to pass the current rules
|
||||
if (MinSpecial + MinNumber > Length)
|
||||
{
|
||||
MinSpecial = Length - MinNumber;
|
||||
}
|
||||
|
||||
if (NumWords < enforcedPolicyOptions.MinNumberOfWords)
|
||||
{
|
||||
NumWords = enforcedPolicyOptions.MinNumberOfWords;
|
||||
}
|
||||
|
||||
if (enforcedPolicyOptions.Capitalize)
|
||||
{
|
||||
Capitalize = true;
|
||||
}
|
||||
|
||||
if (enforcedPolicyOptions.IncludeNumber)
|
||||
{
|
||||
IncludeNumber = true;
|
||||
}
|
||||
|
||||
// Force default type if password/passphrase selected via policy
|
||||
if (enforcedPolicyOptions.DefaultType == TYPE_PASSWORD || enforcedPolicyOptions.DefaultType == TYPE_PASSPHRASE)
|
||||
{
|
||||
Type = enforcedPolicyOptions.DefaultType;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,6 +6,11 @@ namespace Bit.Core.Models.Domain
|
|||
{
|
||||
public class Policy : Domain
|
||||
{
|
||||
public const string MINUTES_KEY = "minutes";
|
||||
public const string ACTION_KEY = "action";
|
||||
public const string ACTION_LOCK = "lock";
|
||||
public const string ACTION_LOGOUT = "logOut";
|
||||
|
||||
public Policy() { }
|
||||
|
||||
public Policy(PolicyData obj)
|
||||
|
@ -22,5 +27,32 @@ namespace Bit.Core.Models.Domain
|
|||
public PolicyType Type { get; set; }
|
||||
public Dictionary<string, object> Data { get; set; }
|
||||
public bool Enabled { get; set; }
|
||||
|
||||
public int? GetInt(string key)
|
||||
{
|
||||
if (Data.TryGetValue(key, out var val) && val != null)
|
||||
{
|
||||
return (int)(long)val;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public bool? GetBool(string key)
|
||||
{
|
||||
if (Data.TryGetValue(key, out var val) && val != null)
|
||||
{
|
||||
return (bool)val;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public string GetString(string key)
|
||||
{
|
||||
if (Data.TryGetValue(key, out var val))
|
||||
{
|
||||
return (string)val;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -605,7 +605,7 @@ namespace Bit.Core.Services
|
|||
var generatedFingerprintPhrase = await _cryptoService.GetFingerprintAsync(email, keyPair.Item1);
|
||||
var fingerprintPhrase = string.Join("-", generatedFingerprintPhrase);
|
||||
var publicB64 = Convert.ToBase64String(keyPair.Item1);
|
||||
var accessCode = await _passwordGenerationService.GeneratePasswordAsync(new PasswordGenerationOptions(true) { Length = 25 });
|
||||
var accessCode = await _passwordGenerationService.GeneratePasswordAsync(PasswordGenerationOptions.CreateDefault.WithLength(25));
|
||||
var passwordlessCreateLoginRequest = new PasswordlessCreateLoginRequest(email, publicB64, deviceId, accessCode, AuthRequestType.AuthenticateAndUnlock, fingerprintPhrase);
|
||||
var response = await _apiService.PostCreateRequestAsync(passwordlessCreateLoginRequest);
|
||||
|
||||
|
|
|
@ -6,7 +6,6 @@ using System.Text.RegularExpressions;
|
|||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Bit.Core.Abstractions;
|
||||
using Bit.Core.Enums;
|
||||
using Bit.Core.Models.Domain;
|
||||
using Bit.Core.Utilities;
|
||||
using Zxcvbn;
|
||||
|
@ -15,17 +14,17 @@ namespace Bit.Core.Services
|
|||
{
|
||||
public class PasswordGenerationService : IPasswordGenerationService
|
||||
{
|
||||
private const int MaxPasswordsInHistory = 100;
|
||||
private const string LowercaseCharSet = "abcdefghijkmnopqrstuvwxyz";
|
||||
private const string UppercaseCharSet = "ABCDEFGHJKLMNPQRSTUVWXYZ";
|
||||
private const string NumberCharSet = "23456789";
|
||||
private const string SpecialCharSet = "!@#$%^&*";
|
||||
private const int MAX_PASSWORDS_IN_HISTORY = 100;
|
||||
private const string LOWERCASE_CHAR_SET = "abcdefghijkmnopqrstuvwxyz";
|
||||
private const string UPPERCASE_CHAR_SET = "ABCDEFGHJKLMNPQRSTUVWXYZ";
|
||||
private const string NUMER_CHAR_SET = "23456789";
|
||||
private const string SPECIAL_CHAR_SET = "!@#$%^&*";
|
||||
|
||||
private readonly ICryptoService _cryptoService;
|
||||
private readonly IStateService _stateService;
|
||||
private readonly ICryptoFunctionService _cryptoFunctionService;
|
||||
private readonly IPolicyService _policyService;
|
||||
private PasswordGenerationOptions _defaultOptions = new PasswordGenerationOptions(true);
|
||||
private PasswordGenerationOptions _defaultOptions = PasswordGenerationOptions.CreateDefault;
|
||||
private PasswordGenerationOptions _optionsCache;
|
||||
private List<GeneratedPasswordHistory> _history;
|
||||
|
||||
|
@ -45,7 +44,7 @@ namespace Bit.Core.Services
|
|||
{
|
||||
// Overload defaults with given options
|
||||
options.Merge(_defaultOptions);
|
||||
if (options.Type == "passphrase")
|
||||
if (options.Type == PasswordGenerationOptions.TYPE_PASSPHRASE)
|
||||
{
|
||||
return await GeneratePassphraseAsync(options);
|
||||
}
|
||||
|
@ -54,30 +53,30 @@ namespace Bit.Core.Services
|
|||
SanitizePasswordLength(options, true);
|
||||
|
||||
var positionsBuilder = new StringBuilder();
|
||||
if (options.Lowercase.GetValueOrDefault() && options.MinLowercase.GetValueOrDefault() > 0)
|
||||
if (options.Lowercase.GetValueOrDefault() && options.MinLowercase > 0)
|
||||
{
|
||||
for (int i = 0; i < options.MinLowercase.GetValueOrDefault(); i++)
|
||||
for (int i = 0; i < options.MinLowercase; i++)
|
||||
{
|
||||
positionsBuilder.Append("l");
|
||||
}
|
||||
}
|
||||
if (options.Uppercase.GetValueOrDefault() && options.MinUppercase.GetValueOrDefault() > 0)
|
||||
if (options.Uppercase.GetValueOrDefault() && options.MinUppercase > 0)
|
||||
{
|
||||
for (int i = 0; i < options.MinUppercase.GetValueOrDefault(); i++)
|
||||
for (int i = 0; i < options.MinUppercase; i++)
|
||||
{
|
||||
positionsBuilder.Append("u");
|
||||
}
|
||||
}
|
||||
if (options.Number.GetValueOrDefault() && options.MinNumber.GetValueOrDefault() > 0)
|
||||
if (options.Number.GetValueOrDefault() && options.MinNumber > 0)
|
||||
{
|
||||
for (int i = 0; i < options.MinNumber.GetValueOrDefault(); i++)
|
||||
for (int i = 0; i < options.MinNumber; i++)
|
||||
{
|
||||
positionsBuilder.Append("n");
|
||||
}
|
||||
}
|
||||
if (options.Special.GetValueOrDefault() && options.MinSpecial.GetValueOrDefault() > 0)
|
||||
if (options.Special.GetValueOrDefault() && options.MinSpecial > 0)
|
||||
{
|
||||
for (int i = 0; i < options.MinSpecial.GetValueOrDefault(); i++)
|
||||
for (int i = 0; i < options.MinSpecial; i++)
|
||||
{
|
||||
positionsBuilder.Append("s");
|
||||
}
|
||||
|
@ -92,68 +91,68 @@ namespace Bit.Core.Services
|
|||
.OrderBy(a => _cryptoFunctionService.RandomNumber()).ToArray();
|
||||
|
||||
// Build out other character sets
|
||||
var allCharSet = string.Empty;
|
||||
var lowercaseCharSet = LowercaseCharSet;
|
||||
var allCharSet = new StringBuilder();
|
||||
|
||||
var lowercaseCharSet = LOWERCASE_CHAR_SET;
|
||||
if (options.AllowAmbiguousChar.GetValueOrDefault())
|
||||
{
|
||||
lowercaseCharSet = string.Concat(lowercaseCharSet, "l");
|
||||
}
|
||||
if (options.Lowercase.GetValueOrDefault())
|
||||
{
|
||||
allCharSet = string.Concat(allCharSet, lowercaseCharSet);
|
||||
allCharSet.Append(lowercaseCharSet);
|
||||
}
|
||||
|
||||
var uppercaseCharSet = UppercaseCharSet;
|
||||
var uppercaseCharSet = UPPERCASE_CHAR_SET;
|
||||
if (options.AllowAmbiguousChar.GetValueOrDefault())
|
||||
{
|
||||
uppercaseCharSet = string.Concat(uppercaseCharSet, "IO");
|
||||
}
|
||||
if (options.Uppercase.GetValueOrDefault())
|
||||
{
|
||||
allCharSet = string.Concat(allCharSet, uppercaseCharSet);
|
||||
allCharSet.Append(uppercaseCharSet);
|
||||
}
|
||||
|
||||
var numberCharSet = NumberCharSet;
|
||||
var numberCharSet = NUMER_CHAR_SET;
|
||||
if (options.AllowAmbiguousChar.GetValueOrDefault())
|
||||
{
|
||||
numberCharSet = string.Concat(numberCharSet, "01");
|
||||
}
|
||||
if (options.Number.GetValueOrDefault())
|
||||
{
|
||||
allCharSet = string.Concat(allCharSet, numberCharSet);
|
||||
allCharSet.Append(numberCharSet);
|
||||
}
|
||||
|
||||
var specialCharSet = SpecialCharSet;
|
||||
if (options.Special.GetValueOrDefault())
|
||||
{
|
||||
allCharSet = string.Concat(allCharSet, specialCharSet);
|
||||
allCharSet.Append(SPECIAL_CHAR_SET);
|
||||
}
|
||||
|
||||
var password = new StringBuilder();
|
||||
for (var i = 0; i < options.Length.GetValueOrDefault(); i++)
|
||||
{
|
||||
var positionChars = string.Empty;
|
||||
var charSetOnCurrentPosition = string.Empty;
|
||||
switch (positions[i])
|
||||
{
|
||||
case 'l':
|
||||
positionChars = lowercaseCharSet;
|
||||
charSetOnCurrentPosition = lowercaseCharSet;
|
||||
break;
|
||||
case 'u':
|
||||
positionChars = uppercaseCharSet;
|
||||
charSetOnCurrentPosition = uppercaseCharSet;
|
||||
break;
|
||||
case 'n':
|
||||
positionChars = numberCharSet;
|
||||
charSetOnCurrentPosition = numberCharSet;
|
||||
break;
|
||||
case 's':
|
||||
positionChars = specialCharSet;
|
||||
charSetOnCurrentPosition = SPECIAL_CHAR_SET;
|
||||
break;
|
||||
case 'a':
|
||||
positionChars = allCharSet;
|
||||
charSetOnCurrentPosition = allCharSet.ToString();
|
||||
break;
|
||||
}
|
||||
|
||||
var randomCharIndex = await _cryptoService.RandomNumberAsync(0, positionChars.Length - 1);
|
||||
password.Append(positionChars[randomCharIndex]);
|
||||
var randomCharIndex = await _cryptoService.RandomNumberAsync(0, charSetOnCurrentPosition.Length - 1);
|
||||
password.Append(charSetOnCurrentPosition[randomCharIndex]);
|
||||
}
|
||||
|
||||
return password.ToString();
|
||||
|
@ -168,7 +167,7 @@ namespace Bit.Core.Services
|
|||
public async Task<string> GeneratePassphraseAsync(PasswordGenerationOptions options)
|
||||
{
|
||||
options.Merge(_defaultOptions);
|
||||
if (options.NumWords.GetValueOrDefault() <= 2)
|
||||
if (options.NumWords <= 2)
|
||||
{
|
||||
options.NumWords = _defaultOptions.NumWords;
|
||||
}
|
||||
|
@ -221,179 +220,10 @@ namespace Bit.Core.Services
|
|||
}
|
||||
}
|
||||
|
||||
var (enforcedOptions, enforcedPolicyOptions) = await EnforcePasswordGeneratorPoliciesOnOptionsAsync(
|
||||
_optionsCache);
|
||||
_optionsCache = enforcedOptions;
|
||||
return (_optionsCache, enforcedPolicyOptions);
|
||||
}
|
||||
var policyOptions = await _policyService.GetPasswordGeneratorPolicyOptionsAsync();
|
||||
_optionsCache.EnforcePolicy(policyOptions);
|
||||
|
||||
public async Task<(PasswordGenerationOptions, PasswordGeneratorPolicyOptions)>
|
||||
EnforcePasswordGeneratorPoliciesOnOptionsAsync(PasswordGenerationOptions options)
|
||||
{
|
||||
var enforcedPolicyOptions = await GetPasswordGeneratorPolicyOptions();
|
||||
if (enforcedPolicyOptions != null)
|
||||
{
|
||||
if (options.Length < enforcedPolicyOptions.MinLength)
|
||||
{
|
||||
options.Length = enforcedPolicyOptions.MinLength;
|
||||
}
|
||||
|
||||
if (enforcedPolicyOptions.UseUppercase)
|
||||
{
|
||||
options.Uppercase = true;
|
||||
}
|
||||
|
||||
if (enforcedPolicyOptions.UseLowercase)
|
||||
{
|
||||
options.Lowercase = true;
|
||||
}
|
||||
|
||||
if (enforcedPolicyOptions.UseNumbers)
|
||||
{
|
||||
options.Number = true;
|
||||
}
|
||||
|
||||
if (options.MinNumber < enforcedPolicyOptions.NumberCount)
|
||||
{
|
||||
options.MinNumber = enforcedPolicyOptions.NumberCount;
|
||||
}
|
||||
|
||||
if (enforcedPolicyOptions.UseSpecial)
|
||||
{
|
||||
options.Special = true;
|
||||
}
|
||||
|
||||
if (options.MinSpecial < enforcedPolicyOptions.SpecialCount)
|
||||
{
|
||||
options.MinSpecial = enforcedPolicyOptions.SpecialCount;
|
||||
}
|
||||
|
||||
// Must normalize these fields because the receiving call expects all options to pass the current rules
|
||||
if (options.MinSpecial + options.MinNumber > options.Length)
|
||||
{
|
||||
options.MinSpecial = options.Length - options.MinNumber;
|
||||
}
|
||||
|
||||
if (options.NumWords < enforcedPolicyOptions.MinNumberOfWords)
|
||||
{
|
||||
options.NumWords = enforcedPolicyOptions.MinNumberOfWords;
|
||||
}
|
||||
|
||||
if (enforcedPolicyOptions.Capitalize)
|
||||
{
|
||||
options.Capitalize = true;
|
||||
}
|
||||
|
||||
if (enforcedPolicyOptions.IncludeNumber)
|
||||
{
|
||||
options.IncludeNumber = true;
|
||||
}
|
||||
|
||||
// Force default type if password/passphrase selected via policy
|
||||
if (enforcedPolicyOptions.DefaultType == "password" || enforcedPolicyOptions.DefaultType == "passphrase")
|
||||
{
|
||||
options.Type = enforcedPolicyOptions.DefaultType;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// UI layer expects an instantiated object to prevent more explicit null checks
|
||||
enforcedPolicyOptions = new PasswordGeneratorPolicyOptions();
|
||||
}
|
||||
|
||||
return (options, enforcedPolicyOptions);
|
||||
}
|
||||
|
||||
public async Task<PasswordGeneratorPolicyOptions> GetPasswordGeneratorPolicyOptions()
|
||||
{
|
||||
var policies = await _policyService.GetAll(PolicyType.PasswordGenerator);
|
||||
PasswordGeneratorPolicyOptions enforcedOptions = null;
|
||||
|
||||
if (policies == null || !policies.Any())
|
||||
{
|
||||
return enforcedOptions;
|
||||
}
|
||||
|
||||
foreach (var currentPolicy in policies)
|
||||
{
|
||||
if (!currentPolicy.Enabled || currentPolicy.Data == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (enforcedOptions == null)
|
||||
{
|
||||
enforcedOptions = new PasswordGeneratorPolicyOptions();
|
||||
}
|
||||
|
||||
var defaultType = GetPolicyString(currentPolicy, "defaultType");
|
||||
if (defaultType != null && enforcedOptions.DefaultType != "password")
|
||||
{
|
||||
enforcedOptions.DefaultType = defaultType;
|
||||
}
|
||||
|
||||
var minLength = GetPolicyInt(currentPolicy, "minLength");
|
||||
if (minLength != null && (int)(long)minLength > enforcedOptions.MinLength)
|
||||
{
|
||||
enforcedOptions.MinLength = (int)(long)minLength;
|
||||
}
|
||||
|
||||
var useUpper = GetPolicyBool(currentPolicy, "useUpper");
|
||||
if (useUpper != null && (bool)useUpper)
|
||||
{
|
||||
enforcedOptions.UseUppercase = true;
|
||||
}
|
||||
|
||||
var useLower = GetPolicyBool(currentPolicy, "useLower");
|
||||
if (useLower != null && (bool)useLower)
|
||||
{
|
||||
enforcedOptions.UseLowercase = true;
|
||||
}
|
||||
|
||||
var useNumbers = GetPolicyBool(currentPolicy, "useNumbers");
|
||||
if (useNumbers != null && (bool)useNumbers)
|
||||
{
|
||||
enforcedOptions.UseNumbers = true;
|
||||
}
|
||||
|
||||
var minNumbers = GetPolicyInt(currentPolicy, "minNumbers");
|
||||
if (minNumbers != null && (int)(long)minNumbers > enforcedOptions.NumberCount)
|
||||
{
|
||||
enforcedOptions.NumberCount = (int)(long)minNumbers;
|
||||
}
|
||||
|
||||
var useSpecial = GetPolicyBool(currentPolicy, "useSpecial");
|
||||
if (useSpecial != null && (bool)useSpecial)
|
||||
{
|
||||
enforcedOptions.UseSpecial = true;
|
||||
}
|
||||
|
||||
var minSpecial = GetPolicyInt(currentPolicy, "minSpecial");
|
||||
if (minSpecial != null && (int)(long)minSpecial > enforcedOptions.SpecialCount)
|
||||
{
|
||||
enforcedOptions.SpecialCount = (int)(long)minSpecial;
|
||||
}
|
||||
|
||||
var minNumberWords = GetPolicyInt(currentPolicy, "minNumberWords");
|
||||
if (minNumberWords != null && (int)(long)minNumberWords > enforcedOptions.MinNumberOfWords)
|
||||
{
|
||||
enforcedOptions.MinNumberOfWords = (int)(long)minNumberWords;
|
||||
}
|
||||
|
||||
var capitalize = GetPolicyBool(currentPolicy, "capitalize");
|
||||
if (capitalize != null && (bool)capitalize)
|
||||
{
|
||||
enforcedOptions.Capitalize = true;
|
||||
}
|
||||
|
||||
var includeNumber = GetPolicyBool(currentPolicy, "includeNumber");
|
||||
if (includeNumber != null && (bool)includeNumber)
|
||||
{
|
||||
enforcedOptions.IncludeNumber = true;
|
||||
}
|
||||
}
|
||||
|
||||
return enforcedOptions;
|
||||
return (_optionsCache, policyOptions ?? new PasswordGeneratorPolicyOptions());
|
||||
}
|
||||
|
||||
public List<string> GetPasswordStrengthUserInput(string email)
|
||||
|
@ -409,45 +239,6 @@ namespace Bit.Core.Services
|
|||
return new List<string>(data);
|
||||
}
|
||||
|
||||
private int? GetPolicyInt(Policy policy, string key)
|
||||
{
|
||||
if (policy.Data.ContainsKey(key))
|
||||
{
|
||||
var value = policy.Data[key];
|
||||
if (value != null)
|
||||
{
|
||||
return (int)(long)value;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private bool? GetPolicyBool(Policy policy, string key)
|
||||
{
|
||||
if (policy.Data.ContainsKey(key))
|
||||
{
|
||||
var value = policy.Data[key];
|
||||
if (value != null)
|
||||
{
|
||||
return (bool)value;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private string GetPolicyString(Policy policy, string key)
|
||||
{
|
||||
if (policy.Data.ContainsKey(key))
|
||||
{
|
||||
var value = policy.Data[key];
|
||||
if (value != null)
|
||||
{
|
||||
return (string)value;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public async Task SaveOptionsAsync(PasswordGenerationOptions options)
|
||||
{
|
||||
await _stateService.SetPasswordGenerationOptionsAsync(options);
|
||||
|
@ -485,7 +276,7 @@ namespace Bit.Core.Services
|
|||
token.ThrowIfCancellationRequested();
|
||||
currentHistory.Insert(0, new GeneratedPasswordHistory { Password = password, Date = DateTime.UtcNow });
|
||||
// Remove old items.
|
||||
if (currentHistory.Count > MaxPasswordsInHistory)
|
||||
if (currentHistory.Count > MAX_PASSWORDS_IN_HISTORY)
|
||||
{
|
||||
currentHistory.RemoveAt(currentHistory.Count - 1);
|
||||
}
|
||||
|
|
|
@ -17,11 +17,6 @@ namespace Bit.Core.Services
|
|||
|
||||
private IEnumerable<Policy> _policyCache;
|
||||
|
||||
public const string TIMEOUT_POLICY_MINUTES = "minutes";
|
||||
public const string TIMEOUT_POLICY_ACTION = "action";
|
||||
public const string TIMEOUT_POLICY_ACTION_LOCK = "lock";
|
||||
public const string TIMEOUT_POLICY_ACTION_LOGOUT = "logOut";
|
||||
|
||||
public PolicyService(
|
||||
IStateService stateService,
|
||||
IOrganizationService organizationService)
|
||||
|
@ -51,10 +46,8 @@ namespace Bit.Core.Services
|
|||
{
|
||||
return _policyCache.Where(p => p.Type == type).ToList();
|
||||
}
|
||||
else
|
||||
{
|
||||
return _policyCache;
|
||||
}
|
||||
|
||||
return _policyCache;
|
||||
}
|
||||
|
||||
public async Task Replace(Dictionary<string, PolicyData> policies, string userId = null)
|
||||
|
@ -77,7 +70,7 @@ namespace Bit.Core.Services
|
|||
|
||||
public async Task UpdateVaultTimeoutFromPolicyAsync(Policy policy, string userId = null)
|
||||
{
|
||||
var policyTimeout = GetPolicyInt(policy, PolicyService.TIMEOUT_POLICY_MINUTES);
|
||||
var policyTimeout = policy.GetInt(Policy.MINUTES_KEY);
|
||||
if (policyTimeout != null)
|
||||
{
|
||||
var vaultTimeout = await _stateService.GetVaultTimeoutAsync(userId);
|
||||
|
@ -92,11 +85,11 @@ namespace Bit.Core.Services
|
|||
}
|
||||
}
|
||||
|
||||
var policyAction = GetPolicyString(policy, PolicyService.TIMEOUT_POLICY_ACTION);
|
||||
var policyAction = policy.GetString(Policy.ACTION_KEY);
|
||||
if (!string.IsNullOrEmpty(policyAction))
|
||||
{
|
||||
var vaultTimeoutAction = await _stateService.GetVaultTimeoutActionAsync(userId);
|
||||
var action = policyAction == PolicyService.TIMEOUT_POLICY_ACTION_LOCK ? VaultTimeoutAction.Lock : VaultTimeoutAction.Logout;
|
||||
var action = policyAction == Policy.ACTION_LOCK ? VaultTimeoutAction.Lock : VaultTimeoutAction.Logout;
|
||||
if (vaultTimeoutAction != action)
|
||||
{
|
||||
await _stateService.SetVaultTimeoutActionAsync(action, userId);
|
||||
|
@ -107,71 +100,63 @@ namespace Bit.Core.Services
|
|||
public async Task<MasterPasswordPolicyOptions> GetMasterPasswordPolicyOptions(
|
||||
IEnumerable<Policy> policies = null, string userId = null)
|
||||
{
|
||||
MasterPasswordPolicyOptions enforcedOptions = null;
|
||||
|
||||
if (policies == null)
|
||||
{
|
||||
policies = await GetAll(PolicyType.MasterPassword, userId);
|
||||
if (policies == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
policies = policies.Where(p => p.Type == PolicyType.MasterPassword);
|
||||
}
|
||||
|
||||
if (policies == null || !policies.Any())
|
||||
policies = policies.Where(p => p.Enabled && p.Data != null);
|
||||
|
||||
if (!policies.Any())
|
||||
{
|
||||
return enforcedOptions;
|
||||
return null;
|
||||
}
|
||||
|
||||
var enforcedOptions = new MasterPasswordPolicyOptions();
|
||||
|
||||
foreach (var currentPolicy in policies)
|
||||
{
|
||||
if (!currentPolicy.Enabled || currentPolicy.Data == null)
|
||||
var minComplexity = currentPolicy.GetInt("minComplexity");
|
||||
if (minComplexity > enforcedOptions.MinComplexity)
|
||||
{
|
||||
continue;
|
||||
enforcedOptions.MinComplexity = minComplexity.Value;
|
||||
}
|
||||
|
||||
if (enforcedOptions == null)
|
||||
var minLength = currentPolicy.GetInt("minLength");
|
||||
if (minLength > enforcedOptions.MinLength)
|
||||
{
|
||||
enforcedOptions = new MasterPasswordPolicyOptions();
|
||||
enforcedOptions.MinLength = minLength.Value;
|
||||
}
|
||||
|
||||
var minComplexity = GetPolicyInt(currentPolicy, "minComplexity");
|
||||
if (minComplexity != null && (int)(long)minComplexity > enforcedOptions.MinComplexity)
|
||||
{
|
||||
enforcedOptions.MinComplexity = (int)(long)minComplexity;
|
||||
}
|
||||
|
||||
var minLength = GetPolicyInt(currentPolicy, "minLength");
|
||||
if (minLength != null && (int)(long)minLength > enforcedOptions.MinLength)
|
||||
{
|
||||
enforcedOptions.MinLength = (int)(long)minLength;
|
||||
}
|
||||
|
||||
var requireUpper = GetPolicyBool(currentPolicy, "requireUpper");
|
||||
if (requireUpper == true)
|
||||
if (currentPolicy.GetBool("requireUpper") == true)
|
||||
{
|
||||
enforcedOptions.RequireUpper = true;
|
||||
}
|
||||
|
||||
var requireLower = GetPolicyBool(currentPolicy, "requireLower");
|
||||
if (requireLower == true)
|
||||
if (currentPolicy.GetBool("requireLower") == true)
|
||||
{
|
||||
enforcedOptions.RequireLower = true;
|
||||
}
|
||||
|
||||
var requireNumbers = GetPolicyBool(currentPolicy, "requireNumbers");
|
||||
if (requireNumbers == true)
|
||||
if (currentPolicy.GetBool("requireNumbers") == true)
|
||||
{
|
||||
enforcedOptions.RequireNumbers = true;
|
||||
}
|
||||
|
||||
var requireSpecial = GetPolicyBool(currentPolicy, "requireSpecial");
|
||||
if (requireSpecial == true)
|
||||
if (currentPolicy.GetBool("requireSpecial") == true)
|
||||
{
|
||||
enforcedOptions.RequireSpecial = true;
|
||||
}
|
||||
|
||||
var enforceOnLogin = GetPolicyBool(currentPolicy, "enforceOnLogin");
|
||||
var enforceOnLogin = currentPolicy.GetBool("enforceOnLogin");
|
||||
if (enforceOnLogin == true)
|
||||
{
|
||||
enforcedOptions.EnforceOnLogin = true;
|
||||
|
@ -234,7 +219,7 @@ namespace Bit.Core.Services
|
|||
|
||||
var policy = policies.FirstOrDefault(p =>
|
||||
p.OrganizationId == orgId && p.Type == PolicyType.ResetPassword && p.Enabled);
|
||||
resetPasswordPolicyOptions.AutoEnrollEnabled = GetPolicyBool(policy, "autoEnrollEnabled") ?? false;
|
||||
resetPasswordPolicyOptions.AutoEnrollEnabled = policy.GetBool("autoEnrollEnabled") ?? false;
|
||||
|
||||
return new Tuple<ResetPasswordPolicyOptions, bool>(resetPasswordPolicyOptions, policy != null);
|
||||
}
|
||||
|
@ -280,23 +265,6 @@ namespace Bit.Core.Services
|
|||
return organization.isExemptFromPolicies;
|
||||
}
|
||||
|
||||
public int? GetPolicyInt(Policy policy, string key)
|
||||
{
|
||||
if (policy.Data.ContainsKey(key))
|
||||
{
|
||||
var value = policy.Data[key];
|
||||
if (value != null)
|
||||
{
|
||||
return (int)(long)value;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public string GetPolicyString(Policy policy, string key) =>
|
||||
policy.Data.TryGetValue(key, out var val) ? val as string : null;
|
||||
|
||||
|
||||
public async Task<bool> ShouldShowVaultFilterAsync()
|
||||
{
|
||||
var personalOwnershipPolicyApplies = await PolicyAppliesToUser(PolicyType.PersonalOwnership);
|
||||
|
@ -309,19 +277,86 @@ namespace Bit.Core.Services
|
|||
return organizations?.Any() ?? false;
|
||||
}
|
||||
|
||||
private bool? GetPolicyBool(Policy policy, string key)
|
||||
public async Task<PasswordGeneratorPolicyOptions> GetPasswordGeneratorPolicyOptionsAsync()
|
||||
{
|
||||
if (policy.Data.ContainsKey(key))
|
||||
var policies = await GetAll(PolicyType.PasswordGenerator);
|
||||
if (policies == null)
|
||||
{
|
||||
var value = policy.Data[key];
|
||||
if (value != null)
|
||||
return null;
|
||||
}
|
||||
|
||||
var actualPolicies = policies.Where(p => p.Enabled && p.Data != null);
|
||||
if (!actualPolicies.Any())
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var enforcedOptions = new PasswordGeneratorPolicyOptions();
|
||||
|
||||
foreach (var currentPolicy in actualPolicies)
|
||||
{
|
||||
var defaultType = currentPolicy.GetString("defaultType");
|
||||
if (defaultType != null && enforcedOptions.DefaultType != PasswordGenerationOptions.TYPE_PASSWORD)
|
||||
{
|
||||
return (bool)value;
|
||||
enforcedOptions.DefaultType = defaultType;
|
||||
}
|
||||
|
||||
var minLength = currentPolicy.GetInt("minLength");
|
||||
if (minLength > enforcedOptions.MinLength)
|
||||
{
|
||||
enforcedOptions.MinLength = minLength.Value;
|
||||
}
|
||||
|
||||
if (currentPolicy.GetBool("useUpper") == true)
|
||||
{
|
||||
enforcedOptions.UseUppercase = true;
|
||||
}
|
||||
|
||||
if (currentPolicy.GetBool("useLower") == true)
|
||||
{
|
||||
enforcedOptions.UseLowercase = true;
|
||||
}
|
||||
|
||||
if (currentPolicy.GetBool("useNumbers") == true)
|
||||
{
|
||||
enforcedOptions.UseNumbers = true;
|
||||
}
|
||||
|
||||
var minNumbers = currentPolicy.GetInt("minNumbers");
|
||||
if (minNumbers > enforcedOptions.NumberCount)
|
||||
{
|
||||
enforcedOptions.NumberCount = minNumbers.Value;
|
||||
}
|
||||
|
||||
if (currentPolicy.GetBool("useSpecial") == true)
|
||||
{
|
||||
enforcedOptions.UseSpecial = true;
|
||||
}
|
||||
|
||||
var minSpecial = currentPolicy.GetInt("minSpecial");
|
||||
if (minSpecial > enforcedOptions.SpecialCount)
|
||||
{
|
||||
enforcedOptions.SpecialCount = minSpecial.Value;
|
||||
}
|
||||
|
||||
var minNumberWords = currentPolicy.GetInt("minNumberWords");
|
||||
if (minNumberWords > enforcedOptions.MinNumberOfWords)
|
||||
{
|
||||
enforcedOptions.MinNumberOfWords = minNumberWords.Value;
|
||||
}
|
||||
|
||||
if (currentPolicy.GetBool("capitalize") == true)
|
||||
{
|
||||
enforcedOptions.Capitalize = true;
|
||||
}
|
||||
|
||||
if (currentPolicy.GetBool("includeNumber") == true)
|
||||
{
|
||||
enforcedOptions.IncludeNumber = true;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
|
||||
return enforcedOptions;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,6 +3,7 @@ using System.Linq;
|
|||
using System.Threading.Tasks;
|
||||
using Bit.Core.Abstractions;
|
||||
using Bit.Core.Enums;
|
||||
using Bit.Core.Models.Domain;
|
||||
|
||||
namespace Bit.Core.Services
|
||||
{
|
||||
|
|
|
@ -75,8 +75,7 @@ namespace Bit.Core.Utilities
|
|||
messagingService.Send("logout", extras);
|
||||
return Task.CompletedTask;
|
||||
});
|
||||
var passwordGenerationService = new PasswordGenerationService(cryptoService, stateService,
|
||||
cryptoFunctionService, policyService);
|
||||
var passwordGenerationService = new PasswordGenerationService(cryptoService, stateService, cryptoFunctionService, policyService);
|
||||
var totpService = new TotpService(cryptoFunctionService);
|
||||
var authService = new AuthService(cryptoService, cryptoFunctionService, apiService, stateService,
|
||||
tokenService, appIdService, i18nService, platformUtilsService, messagingService, vaultTimeoutService,
|
||||
|
|
Loading…
Reference in a new issue