mirror of
https://github.com/bitwarden/android.git
synced 2024-11-01 07:35:52 +03:00
stub out password generator page functionality
This commit is contained in:
parent
29b37219c2
commit
28473dd85f
10 changed files with 280 additions and 34 deletions
|
@ -1,9 +1,10 @@
|
||||||
<?xml version="1.0" encoding="utf-8" ?>
|
<?xml version="1.0" encoding="utf-8" ?>
|
||||||
<ContentPage
|
<pages:BaseContentPage
|
||||||
xmlns="http://xamarin.com/schemas/2014/forms"
|
xmlns="http://xamarin.com/schemas/2014/forms"
|
||||||
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
|
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
|
||||||
x:Class="Bit.App.Pages.GeneratorPage"
|
x:Class="Bit.App.Pages.GeneratorPage"
|
||||||
xmlns:pages="clr-namespace:Bit.App.Pages"
|
xmlns:pages="clr-namespace:Bit.App.Pages"
|
||||||
|
xmlns:controls="clr-namespace:Bit.App.Controls"
|
||||||
xmlns:u="clr-namespace:Bit.App.Utilities"
|
xmlns:u="clr-namespace:Bit.App.Utilities"
|
||||||
x:DataType="pages:GeneratorPageViewModel"
|
x:DataType="pages:GeneratorPageViewModel"
|
||||||
Title="{Binding PageTitle}">
|
Title="{Binding PageTitle}">
|
||||||
|
@ -11,27 +12,45 @@
|
||||||
<pages:GeneratorPageViewModel />
|
<pages:GeneratorPageViewModel />
|
||||||
</ContentPage.BindingContext>
|
</ContentPage.BindingContext>
|
||||||
|
|
||||||
<StackLayout>
|
<ContentPage.ToolbarItems>
|
||||||
<!-- Place new controls here -->
|
<ToolbarItem Text="{u:I18n Select}"
|
||||||
<Label
|
Clicked="Select_Clicked"
|
||||||
Text="Generator!"
|
Order="Primary"
|
||||||
HorizontalOptions="Center"
|
x:Name="_selectItem" />
|
||||||
VerticalOptions="CenterAndExpand" />
|
<ToolbarItem Text="{u:I18n PasswordHistory}"
|
||||||
<Label
|
Clicked="History_Clicked"
|
||||||
Text="{u:I18n AttachmentLargeWarning, P1='10 MB'}"
|
Order="Secondary" />
|
||||||
HorizontalOptions="Center"
|
</ContentPage.ToolbarItems>
|
||||||
VerticalOptions="CenterAndExpand" />
|
|
||||||
<Label
|
|
||||||
Text=""
|
|
||||||
HorizontalOptions="Center"
|
|
||||||
VerticalOptions="CenterAndExpand">
|
|
||||||
|
|
||||||
<Label.FontFamily>
|
<StackLayout Spacing="20">
|
||||||
<OnPlatform x:TypeArguments="x:String"
|
<StackLayout StyleClass="box">
|
||||||
Android="FontAwesome.ttf#FontAwesome"
|
<controls:MonoLabel
|
||||||
iOS="FontAwesome" />
|
Text="{Binding Password}"
|
||||||
</Label.FontFamily>
|
Margin="0, 20"
|
||||||
</Label>
|
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>
|
</StackLayout>
|
||||||
|
|
||||||
</ContentPage>
|
</pages:BaseContentPage>
|
||||||
|
|
|
@ -1,18 +1,42 @@
|
||||||
using System;
|
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
|
namespace Bit.App.Pages
|
||||||
{
|
{
|
||||||
public partial class GeneratorPage : ContentPage
|
public partial class GeneratorPage : BaseContentPage
|
||||||
{
|
{
|
||||||
|
private GeneratorPageViewModel _vm;
|
||||||
|
|
||||||
public GeneratorPage()
|
public GeneratorPage()
|
||||||
{
|
{
|
||||||
InitializeComponent();
|
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)
|
||||||
|
{
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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.Collections.Generic;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
namespace Bit.App.Pages
|
namespace Bit.App.Pages
|
||||||
{
|
{
|
||||||
public class GeneratorPageViewModel : BaseViewModel
|
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()
|
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();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
27
src/App/Resources/AppResources.Designer.cs
generated
27
src/App/Resources/AppResources.Designer.cs
generated
|
@ -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>
|
/// <summary>
|
||||||
/// Looks up a localized string similar to October.
|
/// Looks up a localized string similar to October.
|
||||||
/// </summary>
|
/// </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>
|
/// <summary>
|
||||||
/// Looks up a localized string similar to Passport Number.
|
/// Looks up a localized string similar to Passport Number.
|
||||||
/// </summary>
|
/// </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>
|
/// <summary>
|
||||||
/// Looks up a localized string similar to Yes.
|
/// Looks up a localized string similar to Yes.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
|
@ -1447,4 +1447,13 @@
|
||||||
<data name="ShareDesc" xml:space="preserve">
|
<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>
|
<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>
|
||||||
|
<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>
|
</root>
|
|
@ -40,7 +40,8 @@
|
||||||
Value="Small" />
|
Value="Small" />
|
||||||
</Style>
|
</Style>
|
||||||
<Style TargetType="Label"
|
<Style TargetType="Label"
|
||||||
Class="text-lg">
|
Class="text-lg"
|
||||||
|
ApplyToDerivedTypes="True">
|
||||||
<Setter Property="FontSize"
|
<Setter Property="FontSize"
|
||||||
Value="Large" />
|
Value="Large" />
|
||||||
</Style>
|
</Style>
|
||||||
|
|
|
@ -21,6 +21,8 @@ namespace Bit.Core.Abstractions
|
||||||
Task<byte[]> RsaExtractPublicKeyAsync(byte[] privateKey);
|
Task<byte[]> RsaExtractPublicKeyAsync(byte[] privateKey);
|
||||||
Task<Tuple<byte[], byte[]>> RsaGenerateKeyPairAsync(int length);
|
Task<Tuple<byte[], byte[]>> RsaGenerateKeyPairAsync(int length);
|
||||||
Task<byte[]> RandomBytesAsync(int length);
|
Task<byte[]> RandomBytesAsync(int length);
|
||||||
|
byte[] RandomBytes(int length);
|
||||||
Task<uint> RandomNumberAsync();
|
Task<uint> RandomNumberAsync();
|
||||||
|
uint RandomNumber();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,5 +14,6 @@ namespace Bit.Core.Abstractions
|
||||||
Task<PasswordGenerationOptions> GetOptionsAsync();
|
Task<PasswordGenerationOptions> GetOptionsAsync();
|
||||||
Task<object> PasswordStrength(string password, List<string> userInputs = null);
|
Task<object> PasswordStrength(string password, List<string> userInputs = null);
|
||||||
Task SaveOptionsAsync(PasswordGenerationOptions options);
|
Task SaveOptionsAsync(PasswordGenerationOptions options);
|
||||||
|
void NormalizeOptions(PasswordGenerationOptions options);
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -22,7 +22,7 @@ namespace Bit.Core.Services
|
||||||
private readonly ICryptoService _cryptoService;
|
private readonly ICryptoService _cryptoService;
|
||||||
private readonly IStorageService _storageService;
|
private readonly IStorageService _storageService;
|
||||||
private readonly ICryptoFunctionService _cryptoFunctionService;
|
private readonly ICryptoFunctionService _cryptoFunctionService;
|
||||||
private PasswordGenerationOptions _defaultOptions = new PasswordGenerationOptions();
|
private PasswordGenerationOptions _defaultOptions = new PasswordGenerationOptions(true);
|
||||||
private PasswordGenerationOptions _optionsCache;
|
private PasswordGenerationOptions _optionsCache;
|
||||||
private List<GeneratedPasswordHistory> _history;
|
private List<GeneratedPasswordHistory> _history;
|
||||||
|
|
||||||
|
@ -110,7 +110,7 @@ namespace Bit.Core.Services
|
||||||
|
|
||||||
// Shuffle
|
// Shuffle
|
||||||
var positions = positionsBuilder.ToString().ToCharArray()
|
var positions = positionsBuilder.ToString().ToCharArray()
|
||||||
.OrderBy(async a => await _cryptoFunctionService.RandomNumberAsync()).ToArray();
|
.OrderBy(a => _cryptoFunctionService.RandomNumber()).ToArray();
|
||||||
|
|
||||||
// Build out other character sets
|
// Build out other character sets
|
||||||
var allCharSet = string.Empty;
|
var allCharSet = string.Empty;
|
||||||
|
@ -274,6 +274,73 @@ namespace Bit.Core.Services
|
||||||
throw new NotImplementedException();
|
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
|
// Helpers
|
||||||
|
|
||||||
private async Task<List<GeneratedPasswordHistory>> EncryptHistoryAsync(List<GeneratedPasswordHistory> history)
|
private async Task<List<GeneratedPasswordHistory>> EncryptHistoryAsync(List<GeneratedPasswordHistory> history)
|
||||||
|
|
|
@ -142,11 +142,21 @@ namespace Bit.Core.Services
|
||||||
return Task.FromResult(CryptographicBuffer.GenerateRandom(length));
|
return Task.FromResult(CryptographicBuffer.GenerateRandom(length));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public byte[] RandomBytes(int length)
|
||||||
|
{
|
||||||
|
return CryptographicBuffer.GenerateRandom(length);
|
||||||
|
}
|
||||||
|
|
||||||
public Task<uint> RandomNumberAsync()
|
public Task<uint> RandomNumberAsync()
|
||||||
{
|
{
|
||||||
return Task.FromResult(CryptographicBuffer.GenerateRandomNumber());
|
return Task.FromResult(CryptographicBuffer.GenerateRandomNumber());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public uint RandomNumber()
|
||||||
|
{
|
||||||
|
return CryptographicBuffer.GenerateRandomNumber();
|
||||||
|
}
|
||||||
|
|
||||||
private HashAlgorithm ToHashAlgorithm(CryptoHashAlgorithm algorithm)
|
private HashAlgorithm ToHashAlgorithm(CryptoHashAlgorithm algorithm)
|
||||||
{
|
{
|
||||||
switch(algorithm)
|
switch(algorithm)
|
||||||
|
|
Loading…
Reference in a new issue