stub out password generator page functionality

This commit is contained in:
Kyle Spearrin 2019-05-13 12:13:23 -04:00
parent 29b37219c2
commit 28473dd85f
10 changed files with 280 additions and 34 deletions

View file

@ -1,9 +1,10 @@
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage
<pages:BaseContentPage
xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="Bit.App.Pages.GeneratorPage"
xmlns:pages="clr-namespace:Bit.App.Pages"
xmlns:controls="clr-namespace:Bit.App.Controls"
xmlns:u="clr-namespace:Bit.App.Utilities"
x:DataType="pages:GeneratorPageViewModel"
Title="{Binding PageTitle}">
@ -11,27 +12,45 @@
<pages:GeneratorPageViewModel />
</ContentPage.BindingContext>
<StackLayout>
<!-- Place new controls here -->
<Label
Text="Generator!"
HorizontalOptions="Center"
VerticalOptions="CenterAndExpand" />
<Label
Text="{u:I18n AttachmentLargeWarning, P1='10 MB'}"
HorizontalOptions="Center"
VerticalOptions="CenterAndExpand" />
<Label
Text="&#xf2b9;"
HorizontalOptions="Center"
VerticalOptions="CenterAndExpand">
<ContentPage.ToolbarItems>
<ToolbarItem Text="{u:I18n Select}"
Clicked="Select_Clicked"
Order="Primary"
x:Name="_selectItem" />
<ToolbarItem Text="{u:I18n PasswordHistory}"
Clicked="History_Clicked"
Order="Secondary" />
</ContentPage.ToolbarItems>
<Label.FontFamily>
<OnPlatform x:TypeArguments="x:String"
Android="FontAwesome.ttf#FontAwesome"
iOS="FontAwesome" />
</Label.FontFamily>
</Label>
<StackLayout Spacing="20">
<StackLayout StyleClass="box">
<controls:MonoLabel
Text="{Binding Password}"
Margin="0, 20"
StyleClass="text-lg"
HorizontalOptions="Center" />
<StackLayout Orientation="Horizontal">
<Button Text="{u:I18n RegeneratePassword}"
HorizontalOptions="FillAndExpand"
Clicked="Regenerate_Clicked"></Button>
<Button Text="{u:I18n CopyPassword}"
HorizontalOptions="FillAndExpand"
Clicked="Copy_Clicked"></Button>
</StackLayout>
<StackLayout StyleClass="box-row-header">
<Label Text="{u:I18n Options}"
StyleClass="box-header, box-header-platform" />
</StackLayout>
<StackLayout StyleClass="box-row, box-row-input">
<Label
Text="{u:I18n Type}"
StyleClass="box-label" />
<Picker
ItemsSource="{Binding TypeOptions, Mode=OneTime}"
SelectedIndex="{Binding TypeSelectedIndex}"
StyleClass="box-value" />
</StackLayout>
</StackLayout>
</StackLayout>
</ContentPage>
</pages:BaseContentPage>

View file

@ -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)
{
}
}
}

View file

@ -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<IPasswordGenerationService>(
"passwordGenerationService");
_platformUtilsService = ServiceContainer.Resolve<IPlatformUtilsService>("platformUtilsService");
PageTitle = AppResources.PasswordGenerator;
TypeOptions = new List<string> { AppResources.Password, AppResources.Passphrase };
}
public List<string> 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();
}
}
}

View file

@ -2589,6 +2589,15 @@ namespace Bit.App.Resources {
}
}
/// <summary>
/// Looks up a localized string similar to Number of Words.
/// </summary>
public static string NumberOfWords {
get {
return ResourceManager.GetString("NumberOfWords", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to October.
/// </summary>
@ -2661,6 +2670,15 @@ namespace Bit.App.Resources {
}
}
/// <summary>
/// Looks up a localized string similar to Passphrase.
/// </summary>
public static string Passphrase {
get {
return ResourceManager.GetString("Passphrase", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Passport Number.
/// </summary>
@ -3741,6 +3759,15 @@ namespace Bit.App.Resources {
}
}
/// <summary>
/// Looks up a localized string similar to Word Separator.
/// </summary>
public static string WordSeparator {
get {
return ResourceManager.GetString("WordSeparator", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Yes.
/// </summary>

View file

@ -1447,4 +1447,13 @@
<data name="ShareDesc" xml:space="preserve">
<value>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.</value>
</data>
<data name="NumberOfWords" xml:space="preserve">
<value>Number of Words</value>
</data>
<data name="Passphrase" xml:space="preserve">
<value>Passphrase</value>
</data>
<data name="WordSeparator" xml:space="preserve">
<value>Word Separator</value>
</data>
</root>

View file

@ -40,7 +40,8 @@
Value="Small" />
</Style>
<Style TargetType="Label"
Class="text-lg">
Class="text-lg"
ApplyToDerivedTypes="True">
<Setter Property="FontSize"
Value="Large" />
</Style>

View file

@ -21,6 +21,8 @@ namespace Bit.Core.Abstractions
Task<byte[]> RsaExtractPublicKeyAsync(byte[] privateKey);
Task<Tuple<byte[], byte[]>> RsaGenerateKeyPairAsync(int length);
Task<byte[]> RandomBytesAsync(int length);
byte[] RandomBytes(int length);
Task<uint> RandomNumberAsync();
uint RandomNumber();
}
}

View file

@ -14,5 +14,6 @@ namespace Bit.Core.Abstractions
Task<PasswordGenerationOptions> GetOptionsAsync();
Task<object> PasswordStrength(string password, List<string> userInputs = null);
Task SaveOptionsAsync(PasswordGenerationOptions options);
void NormalizeOptions(PasswordGenerationOptions options);
}
}

View file

@ -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<GeneratedPasswordHistory> _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<List<GeneratedPasswordHistory>> EncryptHistoryAsync(List<GeneratedPasswordHistory> history)

View file

@ -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<uint> RandomNumberAsync()
{
return Task.FromResult(CryptographicBuffer.GenerateRandomNumber());
}
public uint RandomNumber()
{
return CryptographicBuffer.GenerateRandomNumber();
}
private HashAlgorithm ToHashAlgorithm(CryptoHashAlgorithm algorithm)
{
switch(algorithm)