diff --git a/src/App/Pages/Generator/GeneratorPage.xaml b/src/App/Pages/Generator/GeneratorPage.xaml index 3bf1489cd..2989518d0 100644 --- a/src/App/Pages/Generator/GeneratorPage.xaml +++ b/src/App/Pages/Generator/GeneratorPage.xaml @@ -1,9 +1,10 @@  - @@ -11,27 +12,45 @@ - - - + diff --git a/src/App/Pages/Generator/GeneratorPage.xaml.cs b/src/App/Pages/Generator/GeneratorPage.xaml.cs index aa7b6b49e..90d0219d4 100644 --- a/src/App/Pages/Generator/GeneratorPage.xaml.cs +++ b/src/App/Pages/Generator/GeneratorPage.xaml.cs @@ -1,18 +1,42 @@ using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using Xamarin.Forms; -using Xamarin.Forms.Xaml; namespace Bit.App.Pages { - public partial class GeneratorPage : ContentPage + public partial class GeneratorPage : BaseContentPage { + private GeneratorPageViewModel _vm; + public GeneratorPage() { InitializeComponent(); + _vm = BindingContext as GeneratorPageViewModel; + _vm.Page = this; + } + + protected async override void OnAppearing() + { + base.OnAppearing(); + await _vm.InitAsync(); + } + + private async void Regenerate_Clicked(object sender, EventArgs e) + { + await _vm.RegenerateAsync(); + } + + private async void Copy_Clicked(object sender, EventArgs e) + { + await _vm.CopyAsync(); + } + + private void Select_Clicked(object sender, EventArgs e) + { + + } + + private void History_Clicked(object sender, EventArgs e) + { + } } } diff --git a/src/App/Pages/Generator/GeneratorPageViewModel.cs b/src/App/Pages/Generator/GeneratorPageViewModel.cs index 1d0a9b749..2504a8b89 100644 --- a/src/App/Pages/Generator/GeneratorPageViewModel.cs +++ b/src/App/Pages/Generator/GeneratorPageViewModel.cs @@ -1,14 +1,100 @@ -using System; +using Bit.App.Resources; +using Bit.Core.Abstractions; +using Bit.Core.Models.Domain; +using Bit.Core.Utilities; +using System; using System.Collections.Generic; using System.Text; +using System.Threading.Tasks; namespace Bit.App.Pages { public class GeneratorPageViewModel : BaseViewModel { + private readonly IPasswordGenerationService _passwordGenerationService; + private readonly IPlatformUtilsService _platformUtilsService; + + private PasswordGenerationOptions _options; + private string _password; + private bool _isPassword; + private int _typeSelectedIndex; + public GeneratorPageViewModel() { - PageTitle = "Password Generator"; + _passwordGenerationService = ServiceContainer.Resolve( + "passwordGenerationService"); + _platformUtilsService = ServiceContainer.Resolve("platformUtilsService"); + PageTitle = AppResources.PasswordGenerator; + TypeOptions = new List { AppResources.Password, AppResources.Passphrase }; + } + + public List TypeOptions { get; set; } + + public string Password + { + get => _password; + set => SetProperty(ref _password, value); + } + + public bool IsPassword + { + get => _isPassword; + set => SetProperty(ref _isPassword, value); + } + + public PasswordGenerationOptions Options + { + get => _options; + set => SetProperty(ref _options, value); + } + + public int TypeSelectedIndex + { + get => _typeSelectedIndex; + set + { + if(SetProperty(ref _typeSelectedIndex, value)) + { + TypeChanged(); + } + } + } + + public async Task InitAsync() + { + Options = await _passwordGenerationService.GetOptionsAsync(); + TypeSelectedIndex = Options.Type == "passphrase" ? 1 : 0; + Password = await _passwordGenerationService.GeneratePasswordAsync(Options); + await _passwordGenerationService.AddHistoryAsync(Password); + } + + public async Task RegenerateAsync() + { + Password = await _passwordGenerationService.GeneratePasswordAsync(Options); + await _passwordGenerationService.AddHistoryAsync(Password); + } + + public async Task SaveOptionsAsync(bool regenerate = true) + { + _passwordGenerationService.NormalizeOptions(Options); + await _passwordGenerationService.SaveOptionsAsync(Options); + if(regenerate) + { + await RegenerateAsync(); + } + } + + public async Task CopyAsync() + { + await _platformUtilsService.CopyToClipboardAsync(Password); + _platformUtilsService.ShowToast("success", null, AppResources.CopiedPassword); + } + + public async void TypeChanged() + { + IsPassword = TypeSelectedIndex == 0; + Options.Type = IsPassword ? "password" : "passphrase"; + await SaveOptionsAsync(); } } } diff --git a/src/App/Resources/AppResources.Designer.cs b/src/App/Resources/AppResources.Designer.cs index 557aa2d1f..42400d59c 100644 --- a/src/App/Resources/AppResources.Designer.cs +++ b/src/App/Resources/AppResources.Designer.cs @@ -2589,6 +2589,15 @@ namespace Bit.App.Resources { } } + /// + /// Looks up a localized string similar to Number of Words. + /// + public static string NumberOfWords { + get { + return ResourceManager.GetString("NumberOfWords", resourceCulture); + } + } + /// /// Looks up a localized string similar to October. /// @@ -2661,6 +2670,15 @@ namespace Bit.App.Resources { } } + /// + /// Looks up a localized string similar to Passphrase. + /// + public static string Passphrase { + get { + return ResourceManager.GetString("Passphrase", resourceCulture); + } + } + /// /// Looks up a localized string similar to Passport Number. /// @@ -3741,6 +3759,15 @@ namespace Bit.App.Resources { } } + /// + /// Looks up a localized string similar to Word Separator. + /// + public static string WordSeparator { + get { + return ResourceManager.GetString("WordSeparator", resourceCulture); + } + } + /// /// Looks up a localized string similar to Yes. /// diff --git a/src/App/Resources/AppResources.resx b/src/App/Resources/AppResources.resx index 5fbbaba47..500d50953 100644 --- a/src/App/Resources/AppResources.resx +++ b/src/App/Resources/AppResources.resx @@ -1447,4 +1447,13 @@ Choose an organization that you wish to share this item with. Sharing transfers ownership of the item to the organization. You will no longer be the direct owner of this item once it has been shared. + + Number of Words + + + Passphrase + + + Word Separator + \ No newline at end of file diff --git a/src/App/Styles/Base.xaml b/src/App/Styles/Base.xaml index 8313e8ef3..970c84a1a 100644 --- a/src/App/Styles/Base.xaml +++ b/src/App/Styles/Base.xaml @@ -40,7 +40,8 @@ Value="Small" /> diff --git a/src/Core/Abstractions/ICryptoFunctionService.cs b/src/Core/Abstractions/ICryptoFunctionService.cs index f0d7cf0ab..cb1e5a2a7 100644 --- a/src/Core/Abstractions/ICryptoFunctionService.cs +++ b/src/Core/Abstractions/ICryptoFunctionService.cs @@ -21,6 +21,8 @@ namespace Bit.Core.Abstractions Task RsaExtractPublicKeyAsync(byte[] privateKey); Task> RsaGenerateKeyPairAsync(int length); Task RandomBytesAsync(int length); + byte[] RandomBytes(int length); Task RandomNumberAsync(); + uint RandomNumber(); } } diff --git a/src/Core/Abstractions/IPasswordGenerationService.cs b/src/Core/Abstractions/IPasswordGenerationService.cs index 35cfa99c1..d4e37d603 100644 --- a/src/Core/Abstractions/IPasswordGenerationService.cs +++ b/src/Core/Abstractions/IPasswordGenerationService.cs @@ -14,5 +14,6 @@ namespace Bit.Core.Abstractions Task GetOptionsAsync(); Task PasswordStrength(string password, List userInputs = null); Task SaveOptionsAsync(PasswordGenerationOptions options); + void NormalizeOptions(PasswordGenerationOptions options); } } \ No newline at end of file diff --git a/src/Core/Services/PasswordGenerationService.cs b/src/Core/Services/PasswordGenerationService.cs index 428310675..f85beb502 100644 --- a/src/Core/Services/PasswordGenerationService.cs +++ b/src/Core/Services/PasswordGenerationService.cs @@ -22,7 +22,7 @@ namespace Bit.Core.Services private readonly ICryptoService _cryptoService; private readonly IStorageService _storageService; private readonly ICryptoFunctionService _cryptoFunctionService; - private PasswordGenerationOptions _defaultOptions = new PasswordGenerationOptions(); + private PasswordGenerationOptions _defaultOptions = new PasswordGenerationOptions(true); private PasswordGenerationOptions _optionsCache; private List _history; @@ -110,7 +110,7 @@ namespace Bit.Core.Services // Shuffle var positions = positionsBuilder.ToString().ToCharArray() - .OrderBy(async a => await _cryptoFunctionService.RandomNumberAsync()).ToArray(); + .OrderBy(a => _cryptoFunctionService.RandomNumber()).ToArray(); // Build out other character sets var allCharSet = string.Empty; @@ -274,6 +274,73 @@ namespace Bit.Core.Services throw new NotImplementedException(); } + public void NormalizeOptions(PasswordGenerationOptions options) + { + options.MinLowercase = 0; + options.MinUppercase = 0; + + if(!options.Uppercase.GetValueOrDefault() && !options.Lowercase.GetValueOrDefault() && + !options.Number.GetValueOrDefault() && !options.Special.GetValueOrDefault()) + { + options.Lowercase = true; + } + + var length = options.Length.GetValueOrDefault(); + if(length < 5) + { + options.Length = 5; + } + else if(length > 128) + { + options.Length = 128; + } + + if(options.MinNumber == null) + { + options.MinNumber = 0; + } + else if(options.MinNumber > options.Length) + { + options.MinNumber = options.Length; + } + else if(options.MinNumber > 9) + { + options.MinNumber = 9; + } + + if(options.MinSpecial == null) + { + options.MinSpecial = 0; + } + else if(options.MinSpecial > options.Length) + { + options.MinSpecial = options.Length; + } + else if(options.MinSpecial > 9) + { + options.MinSpecial = 9; + } + + if(options.MinSpecial + options.MinNumber > options.Length) + { + options.MinSpecial = options.Length - options.MinNumber; + } + + if(options.NumWords == null || options.Length < 3) + { + options.NumWords = 3; + } + else if(options.NumWords > 20) + { + options.NumWords = 20; + } + + if(options.WordSeparator != null && options.WordSeparator.Length > 1) + { + options.WordSeparator = options.WordSeparator[0].ToString(); + } + } + // Helpers private async Task> EncryptHistoryAsync(List history) diff --git a/src/Core/Services/PclCryptoFunctionService.cs b/src/Core/Services/PclCryptoFunctionService.cs index 325d878d5..71312c1b4 100644 --- a/src/Core/Services/PclCryptoFunctionService.cs +++ b/src/Core/Services/PclCryptoFunctionService.cs @@ -142,11 +142,21 @@ namespace Bit.Core.Services return Task.FromResult(CryptographicBuffer.GenerateRandom(length)); } + public byte[] RandomBytes(int length) + { + return CryptographicBuffer.GenerateRandom(length); + } + public Task RandomNumberAsync() { return Task.FromResult(CryptographicBuffer.GenerateRandomNumber()); } + public uint RandomNumber() + { + return CryptographicBuffer.GenerateRandomNumber(); + } + private HashAlgorithm ToHashAlgorithm(CryptoHashAlgorithm algorithm) { switch(algorithm)