diff --git a/src/App/Pages/Accounts/LoginSsoPageViewModel.cs b/src/App/Pages/Accounts/LoginSsoPageViewModel.cs index c6da63df8..fbf9bf4f2 100644 --- a/src/App/Pages/Accounts/LoginSsoPageViewModel.cs +++ b/src/App/Pages/Accounts/LoginSsoPageViewModel.cs @@ -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); diff --git a/src/App/Pages/Generator/GeneratorPageViewModel.cs b/src/App/Pages/Generator/GeneratorPageViewModel.cs index 8af5548c5..78436352b 100644 --- a/src/App/Pages/Generator/GeneratorPageViewModel.cs +++ b/src/App/Pages/Generator/GeneratorPageViewModel.cs @@ -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; diff --git a/src/App/Pages/Settings/SettingsPage/SettingsPageViewModel.cs b/src/App/Pages/Settings/SettingsPage/SettingsPageViewModel.cs index d71bef46e..99e509373 100644 --- a/src/App/Pages/Settings/SettingsPage/SettingsPageViewModel.cs +++ b/src/App/Pages/Settings/SettingsPage/SettingsPageViewModel.cs @@ -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 { diff --git a/src/Core/Abstractions/IPasswordGenerationService.cs b/src/Core/Abstractions/IPasswordGenerationService.cs index 74f46ea7c..25062f2ac 100644 --- a/src/Core/Abstractions/IPasswordGenerationService.cs +++ b/src/Core/Abstractions/IPasswordGenerationService.cs @@ -15,8 +15,6 @@ namespace Bit.Core.Abstractions Task GeneratePasswordAsync(PasswordGenerationOptions options); Task> GetHistoryAsync(); Task<(PasswordGenerationOptions, PasswordGeneratorPolicyOptions)> GetOptionsAsync(); - Task<(PasswordGenerationOptions, PasswordGeneratorPolicyOptions)> - EnforcePasswordGeneratorPoliciesOnOptionsAsync(PasswordGenerationOptions options); Result PasswordStrength(string password, List userInputs = null); Task SaveOptionsAsync(PasswordGenerationOptions options); void NormalizeOptions(PasswordGenerationOptions options, PasswordGeneratorPolicyOptions enforcedPolicyOptions); diff --git a/src/Core/Abstractions/IPolicyService.cs b/src/Core/Abstractions/IPolicyService.cs index 8acca2c9a..75ca6168e 100644 --- a/src/Core/Abstractions/IPolicyService.cs +++ b/src/Core/Abstractions/IPolicyService.cs @@ -19,8 +19,7 @@ namespace Bit.Core.Abstractions Tuple GetResetPasswordPolicyOptions(IEnumerable policies, string orgId); Task PolicyAppliesToUser(PolicyType policyType, Func policyFilter = null, string userId = null); - int? GetPolicyInt(Policy policy, string key); - string GetPolicyString(Policy policy, string key); Task ShouldShowVaultFilterAsync(); + Task GetPasswordGeneratorPolicyOptionsAsync(); } } diff --git a/src/Core/Models/Domain/PasswordGenerationOptions.cs b/src/Core/Models/Domain/PasswordGenerationOptions.cs index 16afec6bf..bab849661 100644 --- a/src/Core/Models/Domain/PasswordGenerationOptions.cs +++ b/src/Core/Models/Domain/PasswordGenerationOptions.cs @@ -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; + } + } } } diff --git a/src/Core/Models/Domain/Policy.cs b/src/Core/Models/Domain/Policy.cs index a84b095d0..3d58c2fb8 100644 --- a/src/Core/Models/Domain/Policy.cs +++ b/src/Core/Models/Domain/Policy.cs @@ -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 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; + } } } diff --git a/src/Core/Services/AuthService.cs b/src/Core/Services/AuthService.cs index 5fa8a2115..92cbadd3b 100644 --- a/src/Core/Services/AuthService.cs +++ b/src/Core/Services/AuthService.cs @@ -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); diff --git a/src/Core/Services/PasswordGenerationService.cs b/src/Core/Services/PasswordGenerationService.cs index e689616c2..3210cccad 100644 --- a/src/Core/Services/PasswordGenerationService.cs +++ b/src/Core/Services/PasswordGenerationService.cs @@ -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 _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 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 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 GetPasswordStrengthUserInput(string email) @@ -409,45 +239,6 @@ namespace Bit.Core.Services return new List(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); } diff --git a/src/Core/Services/PolicyService.cs b/src/Core/Services/PolicyService.cs index b6922dd56..9301763ac 100644 --- a/src/Core/Services/PolicyService.cs +++ b/src/Core/Services/PolicyService.cs @@ -17,11 +17,6 @@ namespace Bit.Core.Services private IEnumerable _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 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 GetMasterPasswordPolicyOptions( IEnumerable 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, 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 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 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; } - - } } diff --git a/src/Core/Services/VaultTimeoutService.cs b/src/Core/Services/VaultTimeoutService.cs index eb800c984..f5bd7d4bd 100644 --- a/src/Core/Services/VaultTimeoutService.cs +++ b/src/Core/Services/VaultTimeoutService.cs @@ -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 { diff --git a/src/Core/Utilities/ServiceContainer.cs b/src/Core/Utilities/ServiceContainer.cs index 3f8400497..c3b6bbd65 100644 --- a/src/Core/Utilities/ServiceContainer.cs +++ b/src/Core/Utilities/ServiceContainer.cs @@ -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,