mirror of
https://github.com/bitwarden/android.git
synced 2024-10-31 23:25:45 +03:00
Password generator policy enforcement (#741)
* Password generator policy enforcement * Formatting * Changed to simple cast (double unboxing for int64/long) * Added ui indication of active policy on password generator page and fixed issue with switch enable logic
This commit is contained in:
parent
02cffa01e2
commit
f78f303a79
9 changed files with 231 additions and 12 deletions
|
@ -39,6 +39,29 @@
|
|||
<ScrollView Padding="0, 0, 0, 20">
|
||||
<StackLayout Spacing="0" Padding="0">
|
||||
<StackLayout StyleClass="box">
|
||||
<Grid IsVisible="{Binding IsPolicyInEffect}"
|
||||
Margin="0, 12, 0, 0"
|
||||
RowSpacing="0"
|
||||
ColumnSpacing="0">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="*" />
|
||||
</Grid.RowDefinitions>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<Frame Padding="10"
|
||||
Margin="0"
|
||||
HasShadow="False"
|
||||
BackgroundColor="Transparent"
|
||||
BorderColor="Accent">
|
||||
<Label
|
||||
Text="{u:I18n PasswordGeneratorPolicyInEffect}"
|
||||
StyleClass="text-muted, text-sm, text-bold"
|
||||
HorizontalTextAlignment="Center" />
|
||||
</Frame>
|
||||
</Grid>
|
||||
<controls:MonoLabel
|
||||
Text="{Binding ColoredPassword, Mode=OneWay}"
|
||||
TextType="Html"
|
||||
|
@ -152,6 +175,7 @@
|
|||
HorizontalOptions="StartAndExpand" />
|
||||
<Switch
|
||||
IsToggled="{Binding Uppercase}"
|
||||
IsEnabled="{Binding IsUppercaseSwitchEnabled}"
|
||||
StyleClass="box-value"
|
||||
HorizontalOptions="End" />
|
||||
</StackLayout>
|
||||
|
@ -163,6 +187,7 @@
|
|||
HorizontalOptions="StartAndExpand" />
|
||||
<Switch
|
||||
IsToggled="{Binding Lowercase}"
|
||||
IsEnabled="{Binding IsLowercaseSwitchEnabled}"
|
||||
StyleClass="box-value"
|
||||
HorizontalOptions="End" />
|
||||
</StackLayout>
|
||||
|
@ -174,6 +199,7 @@
|
|||
HorizontalOptions="StartAndExpand" />
|
||||
<Switch
|
||||
IsToggled="{Binding Number}"
|
||||
IsEnabled="{Binding IsNumberSwitchEnabled}"
|
||||
StyleClass="box-value"
|
||||
HorizontalOptions="End" />
|
||||
</StackLayout>
|
||||
|
@ -185,6 +211,7 @@
|
|||
HorizontalOptions="StartAndExpand" />
|
||||
<Switch
|
||||
IsToggled="{Binding Special}"
|
||||
IsEnabled="{Binding IsSpecialSwitchEnabled}"
|
||||
StyleClass="box-value"
|
||||
HorizontalOptions="End" />
|
||||
</StackLayout>
|
||||
|
|
|
@ -15,6 +15,7 @@ namespace Bit.App.Pages
|
|||
private readonly IPlatformUtilsService _platformUtilsService;
|
||||
|
||||
private PasswordGenerationOptions _options;
|
||||
private PasswordGeneratorPolicyOptions _enforcedPolicyOptions;
|
||||
private string _password;
|
||||
private bool _isPassword;
|
||||
private bool _uppercase;
|
||||
|
@ -221,7 +222,34 @@ namespace Bit.App.Pages
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
public PasswordGeneratorPolicyOptions EnforcedPolicyOptions
|
||||
{
|
||||
get => _enforcedPolicyOptions;
|
||||
set => SetProperty(ref _enforcedPolicyOptions, value,
|
||||
additionalPropertyNames: new[]
|
||||
{
|
||||
nameof(IsPolicyInEffect),
|
||||
nameof(IsUppercaseSwitchEnabled),
|
||||
nameof(IsLowercaseSwitchEnabled),
|
||||
nameof(IsNumberSwitchEnabled),
|
||||
nameof(IsSpecialSwitchEnabled)
|
||||
});
|
||||
}
|
||||
|
||||
public bool IsPolicyInEffect => _enforcedPolicyOptions.MinLength > 0 || _enforcedPolicyOptions.UseUppercase ||
|
||||
_enforcedPolicyOptions.UseLowercase || _enforcedPolicyOptions.UseNumbers ||
|
||||
_enforcedPolicyOptions.NumberCount > 0 || _enforcedPolicyOptions.UseSpecial ||
|
||||
_enforcedPolicyOptions.SpecialCount > 0;
|
||||
|
||||
public bool IsUppercaseSwitchEnabled => !IsPolicyInEffect || !EnforcedPolicyOptions.UseUppercase;
|
||||
|
||||
public bool IsLowercaseSwitchEnabled => !IsPolicyInEffect || !EnforcedPolicyOptions.UseLowercase;
|
||||
|
||||
public bool IsNumberSwitchEnabled => !IsPolicyInEffect || !EnforcedPolicyOptions.UseNumbers;
|
||||
|
||||
public bool IsSpecialSwitchEnabled => !IsPolicyInEffect || !EnforcedPolicyOptions.UseSpecial;
|
||||
|
||||
public int TypeSelectedIndex
|
||||
{
|
||||
get => _typeSelectedIndex;
|
||||
|
@ -237,7 +265,7 @@ namespace Bit.App.Pages
|
|||
|
||||
public async Task InitAsync()
|
||||
{
|
||||
_options = await _passwordGenerationService.GetOptionsAsync();
|
||||
(_options, EnforcedPolicyOptions) = await _passwordGenerationService.GetOptionsAsync();
|
||||
LoadFromOptions();
|
||||
await RegenerateAsync();
|
||||
_doneIniting = true;
|
||||
|
@ -256,7 +284,7 @@ namespace Bit.App.Pages
|
|||
return;
|
||||
}
|
||||
SetOptions();
|
||||
_passwordGenerationService.NormalizeOptions(_options);
|
||||
_passwordGenerationService.NormalizeOptions(_options, _enforcedPolicyOptions);
|
||||
await _passwordGenerationService.SaveOptionsAsync(_options);
|
||||
LoadFromOptions();
|
||||
if(regenerate)
|
||||
|
@ -274,7 +302,7 @@ namespace Bit.App.Pages
|
|||
public async Task SliderInputAsync()
|
||||
{
|
||||
SetOptions();
|
||||
_passwordGenerationService.NormalizeOptions(_options);
|
||||
_passwordGenerationService.NormalizeOptions(_options, _enforcedPolicyOptions);
|
||||
Password = await _passwordGenerationService.GeneratePasswordAsync(_options);
|
||||
}
|
||||
|
||||
|
|
6
src/App/Resources/AppResources.Designer.cs
generated
6
src/App/Resources/AppResources.Designer.cs
generated
|
@ -2835,5 +2835,11 @@ namespace Bit.App.Resources {
|
|||
return ResourceManager.GetString("Clone", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
public static string PasswordGeneratorPolicyInEffect {
|
||||
get {
|
||||
return ResourceManager.GetString("PasswordGeneratorPolicyInEffect", resourceCulture);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1612,4 +1612,7 @@
|
|||
<value>Clone</value>
|
||||
<comment>Clone an entity (verb).</comment>
|
||||
</data>
|
||||
<data name="PasswordGeneratorPolicyInEffect" xml:space="preserve">
|
||||
<value>One or more organization policies are affecting your generator settings</value>
|
||||
</data>
|
||||
</root>
|
|
@ -12,9 +12,9 @@ namespace Bit.Core.Abstractions
|
|||
Task<string> GeneratePassphraseAsync(PasswordGenerationOptions options);
|
||||
Task<string> GeneratePasswordAsync(PasswordGenerationOptions options);
|
||||
Task<List<GeneratedPasswordHistory>> GetHistoryAsync();
|
||||
Task<PasswordGenerationOptions> GetOptionsAsync();
|
||||
Task<(PasswordGenerationOptions, PasswordGeneratorPolicyOptions)> GetOptionsAsync();
|
||||
Task<object> PasswordStrength(string password, List<string> userInputs = null);
|
||||
Task SaveOptionsAsync(PasswordGenerationOptions options);
|
||||
void NormalizeOptions(PasswordGenerationOptions options);
|
||||
void NormalizeOptions(PasswordGenerationOptions options, PasswordGeneratorPolicyOptions enforcedPolicyOptions);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
13
src/Core/Models/Domain/PasswordGeneratorPolicyOptions.cs
Normal file
13
src/Core/Models/Domain/PasswordGeneratorPolicyOptions.cs
Normal file
|
@ -0,0 +1,13 @@
|
|||
namespace Bit.Core.Models.Domain
|
||||
{
|
||||
public class PasswordGeneratorPolicyOptions
|
||||
{
|
||||
public int MinLength { get; set; }
|
||||
public bool UseUppercase { get; set; }
|
||||
public bool UseLowercase { get; set; }
|
||||
public bool UseNumbers { get; set; }
|
||||
public int NumberCount { get; set; }
|
||||
public bool UseSpecial { get; set; }
|
||||
public int SpecialCount { get; set; }
|
||||
}
|
||||
}
|
|
@ -7,6 +7,7 @@ using System.Linq;
|
|||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Bit.Core.Enums;
|
||||
|
||||
namespace Bit.Core.Services
|
||||
{
|
||||
|
@ -23,6 +24,7 @@ namespace Bit.Core.Services
|
|||
private readonly ICryptoService _cryptoService;
|
||||
private readonly IStorageService _storageService;
|
||||
private readonly ICryptoFunctionService _cryptoFunctionService;
|
||||
private readonly IPolicyService _policyService;
|
||||
private PasswordGenerationOptions _defaultOptions = new PasswordGenerationOptions(true);
|
||||
private PasswordGenerationOptions _optionsCache;
|
||||
private List<GeneratedPasswordHistory> _history;
|
||||
|
@ -30,11 +32,13 @@ namespace Bit.Core.Services
|
|||
public PasswordGenerationService(
|
||||
ICryptoService cryptoService,
|
||||
IStorageService storageService,
|
||||
ICryptoFunctionService cryptoFunctionService)
|
||||
ICryptoFunctionService cryptoFunctionService,
|
||||
IPolicyService policyService)
|
||||
{
|
||||
_cryptoService = cryptoService;
|
||||
_storageService = storageService;
|
||||
_cryptoFunctionService = cryptoFunctionService;
|
||||
_policyService = policyService;
|
||||
}
|
||||
|
||||
public async Task<string> GeneratePasswordAsync(PasswordGenerationOptions options)
|
||||
|
@ -240,7 +244,7 @@ namespace Bit.Core.Services
|
|||
return string.Join(options.WordSeparator, wordList);
|
||||
}
|
||||
|
||||
public async Task<PasswordGenerationOptions> GetOptionsAsync()
|
||||
public async Task<(PasswordGenerationOptions,PasswordGeneratorPolicyOptions)> GetOptionsAsync()
|
||||
{
|
||||
if(_optionsCache == null)
|
||||
{
|
||||
|
@ -255,7 +259,129 @@ namespace Bit.Core.Services
|
|||
_optionsCache = options;
|
||||
}
|
||||
}
|
||||
return _optionsCache;
|
||||
|
||||
var enforcedPolicyOptions = await GetPasswordGeneratorPolicyOptions();
|
||||
if(enforcedPolicyOptions != null)
|
||||
{
|
||||
if(_optionsCache.Length < enforcedPolicyOptions.MinLength)
|
||||
{
|
||||
_optionsCache.Length = enforcedPolicyOptions.MinLength;
|
||||
}
|
||||
|
||||
if(enforcedPolicyOptions.UseUppercase)
|
||||
{
|
||||
_optionsCache.Uppercase = true;
|
||||
}
|
||||
|
||||
if(enforcedPolicyOptions.UseLowercase)
|
||||
{
|
||||
_optionsCache.Lowercase = true;
|
||||
}
|
||||
|
||||
if(enforcedPolicyOptions.UseNumbers)
|
||||
{
|
||||
_optionsCache.Number = true;
|
||||
}
|
||||
|
||||
if(_optionsCache.MinNumber < enforcedPolicyOptions.NumberCount)
|
||||
{
|
||||
_optionsCache.MinNumber = enforcedPolicyOptions.NumberCount;
|
||||
}
|
||||
|
||||
if(enforcedPolicyOptions.UseSpecial)
|
||||
{
|
||||
_optionsCache.Special = true;
|
||||
}
|
||||
|
||||
if(_optionsCache.MinSpecial < enforcedPolicyOptions.SpecialCount)
|
||||
{
|
||||
_optionsCache.MinSpecial = enforcedPolicyOptions.SpecialCount;
|
||||
}
|
||||
|
||||
// Must normalize these fields because the receiving call expects all options to pass the current rules
|
||||
if(_optionsCache.MinSpecial + _optionsCache.MinNumber > _optionsCache.Length)
|
||||
{
|
||||
_optionsCache.MinSpecial = _optionsCache.Length - _optionsCache.MinNumber;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// UI layer expects an instantiated object to prevent more explicit null checks
|
||||
enforcedPolicyOptions = new PasswordGeneratorPolicyOptions();
|
||||
}
|
||||
|
||||
return (_optionsCache, 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 currentPolicyMinLength = currentPolicy.Data["minLength"];
|
||||
if(currentPolicyMinLength != null &&
|
||||
(int)(long)currentPolicyMinLength > enforcedOptions.MinLength)
|
||||
{
|
||||
enforcedOptions.MinLength = (int)(long)currentPolicyMinLength;
|
||||
}
|
||||
|
||||
var currentPolicyUseUpper = currentPolicy.Data["useUpper"];
|
||||
if(currentPolicyUseUpper != null && (bool)currentPolicyUseUpper)
|
||||
{
|
||||
enforcedOptions.UseUppercase = true;
|
||||
}
|
||||
|
||||
var currentPolicyUseLower = currentPolicy.Data["useLower"];
|
||||
if(currentPolicyUseLower != null && (bool)currentPolicyUseLower)
|
||||
{
|
||||
enforcedOptions.UseLowercase = true;
|
||||
}
|
||||
|
||||
var currentPolicyUseNumbers = currentPolicy.Data["useNumbers"];
|
||||
if(currentPolicyUseNumbers != null && (bool)currentPolicyUseNumbers)
|
||||
{
|
||||
enforcedOptions.UseNumbers = true;
|
||||
}
|
||||
|
||||
var currentPolicyMinNumbers = currentPolicy.Data["minNumbers"];
|
||||
if(currentPolicyMinNumbers != null &&
|
||||
(int)(long)currentPolicyMinNumbers > enforcedOptions.NumberCount)
|
||||
{
|
||||
enforcedOptions.NumberCount = (int)(long)currentPolicyMinNumbers;
|
||||
}
|
||||
|
||||
var currentPolicyUseSpecial = currentPolicy.Data["useSpecial"];
|
||||
if(currentPolicyUseSpecial != null && (bool)currentPolicyUseSpecial)
|
||||
{
|
||||
enforcedOptions.UseSpecial = true;
|
||||
}
|
||||
|
||||
var currentPolicyMinSpecial = currentPolicy.Data["minSpecial"];
|
||||
if(currentPolicyMinSpecial != null &&
|
||||
(int)(long)currentPolicyMinSpecial > enforcedOptions.SpecialCount)
|
||||
{
|
||||
enforcedOptions.SpecialCount = (int)(long)currentPolicyMinSpecial;
|
||||
}
|
||||
}
|
||||
|
||||
return enforcedOptions;
|
||||
}
|
||||
|
||||
public async Task SaveOptionsAsync(PasswordGenerationOptions options)
|
||||
|
@ -315,7 +441,8 @@ namespace Bit.Core.Services
|
|||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public void NormalizeOptions(PasswordGenerationOptions options)
|
||||
public void NormalizeOptions(PasswordGenerationOptions options,
|
||||
PasswordGeneratorPolicyOptions enforcedPolicyOptions)
|
||||
{
|
||||
options.MinLowercase = 0;
|
||||
options.MinUppercase = 0;
|
||||
|
@ -336,6 +463,11 @@ namespace Bit.Core.Services
|
|||
options.Length = 128;
|
||||
}
|
||||
|
||||
if(options.Length < enforcedPolicyOptions.MinLength)
|
||||
{
|
||||
options.Length = enforcedPolicyOptions.MinLength;
|
||||
}
|
||||
|
||||
if(options.MinNumber == null)
|
||||
{
|
||||
options.MinNumber = 0;
|
||||
|
@ -348,6 +480,11 @@ namespace Bit.Core.Services
|
|||
{
|
||||
options.MinNumber = 9;
|
||||
}
|
||||
|
||||
if(options.MinNumber < enforcedPolicyOptions.NumberCount)
|
||||
{
|
||||
options.MinNumber = enforcedPolicyOptions.NumberCount;
|
||||
}
|
||||
|
||||
if(options.MinSpecial == null)
|
||||
{
|
||||
|
@ -362,6 +499,11 @@ namespace Bit.Core.Services
|
|||
options.MinSpecial = 9;
|
||||
}
|
||||
|
||||
if(options.MinSpecial < enforcedPolicyOptions.SpecialCount)
|
||||
{
|
||||
options.MinSpecial = enforcedPolicyOptions.SpecialCount;
|
||||
}
|
||||
|
||||
if(options.MinSpecial + options.MinNumber > options.Length)
|
||||
{
|
||||
options.MinSpecial = options.Length - options.MinNumber;
|
||||
|
|
|
@ -56,7 +56,7 @@ namespace Bit.Core.Utilities
|
|||
return Task.FromResult(0);
|
||||
});
|
||||
var passwordGenerationService = new PasswordGenerationService(cryptoService, storageService,
|
||||
cryptoFunctionService);
|
||||
cryptoFunctionService, policyService);
|
||||
var totpService = new TotpService(storageService, cryptoFunctionService);
|
||||
var authService = new AuthService(cryptoService, apiService, userService, tokenService, appIdService,
|
||||
i18nService, platformUtilsService, messagingService, lockService);
|
||||
|
|
|
@ -71,7 +71,7 @@ namespace Bit.iOS.Core.Controllers
|
|||
OptionsTableViewController.TableView.SeparatorColor = ThemeHelpers.SeparatorColor;
|
||||
}
|
||||
|
||||
var options = await _passwordGenerationService.GetOptionsAsync();
|
||||
var (options, enforcedPolicyOptions) = await _passwordGenerationService.GetOptionsAsync();
|
||||
UppercaseCell.Switch.On = options.Uppercase.GetValueOrDefault();
|
||||
LowercaseCell.Switch.On = options.Lowercase.GetValueOrDefault(true);
|
||||
SpecialCell.Switch.On = options.Special.GetValueOrDefault();
|
||||
|
|
Loading…
Reference in a new issue