Password generator page. Password generation service. Tests. Renamed some settings constants.

This commit is contained in:
Kyle Spearrin 2016-07-02 02:01:47 -04:00
parent cd4f1f4c2f
commit 55ed801fe7
17 changed files with 6955 additions and 98 deletions

View file

@ -121,6 +121,7 @@ namespace Bit.Android
.RegisterType<IClipboardService, ClipboardService>(new ContainerControlledLifetimeManager()) .RegisterType<IClipboardService, ClipboardService>(new ContainerControlledLifetimeManager())
.RegisterType<IPushNotificationListener, PushNotificationListener>(new ContainerControlledLifetimeManager()) .RegisterType<IPushNotificationListener, PushNotificationListener>(new ContainerControlledLifetimeManager())
.RegisterType<IAppIdService, AppIdService>(new ContainerControlledLifetimeManager()) .RegisterType<IAppIdService, AppIdService>(new ContainerControlledLifetimeManager())
.RegisterType<IPasswordGenerationService, PasswordGenerationService>(new ContainerControlledLifetimeManager())
// Repositories // Repositories
.RegisterType<IFolderRepository, FolderRepository>(new ContainerControlledLifetimeManager()) .RegisterType<IFolderRepository, FolderRepository>(new ContainerControlledLifetimeManager())
.RegisterType<IFolderApiRepository, FolderApiRepository>(new ContainerControlledLifetimeManager()) .RegisterType<IFolderApiRepository, FolderApiRepository>(new ContainerControlledLifetimeManager())

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,7 @@
namespace Bit.App.Abstractions
{
public interface IPasswordGenerationService
{
string GeneratePassword();
}
}

View file

@ -99,6 +99,7 @@
<Compile Include="Models\Folder.cs" /> <Compile Include="Models\Folder.cs" />
<Compile Include="Models\Page\SettingsFolderPageModel.cs" /> <Compile Include="Models\Page\SettingsFolderPageModel.cs" />
<Compile Include="Models\Page\PinPageModel.cs" /> <Compile Include="Models\Page\PinPageModel.cs" />
<Compile Include="Models\Page\PasswordGeneratorPageModel.cs" />
<Compile Include="Models\PushNotification.cs" /> <Compile Include="Models\PushNotification.cs" />
<Compile Include="Models\Site.cs" /> <Compile Include="Models\Site.cs" />
<Compile Include="Models\Page\VaultViewSitePageModel.cs" /> <Compile Include="Models\Page\VaultViewSitePageModel.cs" />
@ -109,6 +110,8 @@
<Compile Include="Pages\MainPage.cs" /> <Compile Include="Pages\MainPage.cs" />
<Compile Include="Pages\Settings\SettingsEditFolderPage.cs" /> <Compile Include="Pages\Settings\SettingsEditFolderPage.cs" />
<Compile Include="Pages\Lock\LockFingerprintPage.cs" /> <Compile Include="Pages\Lock\LockFingerprintPage.cs" />
<Compile Include="Pages\Tools\ToolsPasswordGeneratorSettingsPage.cs" />
<Compile Include="Pages\Tools\ToolsPasswordGeneratorPage.cs" />
<Compile Include="Pages\Tools\ToolsPage.cs" /> <Compile Include="Pages\Tools\ToolsPage.cs" />
<Compile Include="Pages\Settings\SettingsSyncPage.cs" /> <Compile Include="Pages\Settings\SettingsSyncPage.cs" />
<Compile Include="Pages\Settings\SettingsPage.cs" /> <Compile Include="Pages\Settings\SettingsPage.cs" />
@ -144,6 +147,7 @@
<Compile Include="Abstractions\Services\ICryptoService.cs" /> <Compile Include="Abstractions\Services\ICryptoService.cs" />
<Compile Include="Abstractions\Services\IDatabaseService.cs" /> <Compile Include="Abstractions\Services\IDatabaseService.cs" />
<Compile Include="Abstractions\Services\ISyncService.cs" /> <Compile Include="Abstractions\Services\ISyncService.cs" />
<Compile Include="Abstractions\Services\IPasswordGenerationService.cs" />
<Compile Include="Services\SyncService.cs" /> <Compile Include="Services\SyncService.cs" />
<Compile Include="Services\SiteService.cs" /> <Compile Include="Services\SiteService.cs" />
<Compile Include="Services\AuthService.cs" /> <Compile Include="Services\AuthService.cs" />
@ -155,6 +159,7 @@
<Compile Include="Pages\Vault\VaultViewSitePage.cs" /> <Compile Include="Pages\Vault\VaultViewSitePage.cs" />
<Compile Include="Pages\Vault\VaultEditSitePage.cs" /> <Compile Include="Pages\Vault\VaultEditSitePage.cs" />
<Compile Include="Pages\Vault\VaultListSitesPage.cs" /> <Compile Include="Pages\Vault\VaultListSitesPage.cs" />
<Compile Include="Services\PasswordGenerationService.cs" />
<Compile Include="Utilities\Extentions.cs" /> <Compile Include="Utilities\Extentions.cs" />
<Compile Include="Utilities\ExtendedObservableCollection.cs" /> <Compile Include="Utilities\ExtendedObservableCollection.cs" />
<Compile Include="Utilities\ApiHttpClient.cs" /> <Compile Include="Utilities\ApiHttpClient.cs" />

View file

@ -2,12 +2,21 @@
{ {
public static class Constants public static class Constants
{ {
public const string SettingFingerprintUnlockOn = "fingerprintUnlockOn"; public const string SettingFingerprintUnlockOn = "setting:fingerprintUnlockOn";
public const string SettingPinUnlockOn = "pinUnlockOn"; public const string SettingPinUnlockOn = "setting:pinUnlockOn";
public const string SettingLockSeconds = "lockSeconds"; public const string SettingLockSeconds = "setting:lockSeconds";
public const string SettingLastBackgroundedDate = "lastBackgroundedDate"; public const string SettingLastBackgroundedDate = "lastBackgroundedDate";
public const string PushPromptShown = "initialPushPromptShown"; public const string PasswordGeneratorLength = "pwGenerator:length";
public const string PushLastRegistration = "lastPushRegistration"; public const string PasswordGeneratorUppercase = "pwGenerator:uppercase";
public const string PasswordGeneratorLowercase = "pwGenerator:lowercase";
public const string PasswordGeneratorNumbers = "pwGenerator:numbers";
public const string PasswordGeneratorMinNumbers = "pwGenerator:minNumbers";
public const string PasswordGeneratorSpecial = "pwGenerator:special";
public const string PasswordGeneratorMinSpecial = "pwGenerator:minSpecial";
public const string PasswordGeneratorAmbiguous = "pwGenerator:ambiguous";
public const string PushInitialPromptShown = "push:initialPromptShown";
public const string PushLastRegistrationDate = "push:lastRegistrationDate";
} }
} }

View file

@ -0,0 +1,35 @@
using System;
using System.ComponentModel;
namespace Bit.App.Models.Page
{
public class PasswordGeneratorPageModel : INotifyPropertyChanged
{
private string _password = " ";
private string _length;
public PasswordGeneratorPageModel() { }
public event PropertyChangedEventHandler PropertyChanged;
public string Password
{
get { return _password; }
set
{
_password = value;
PropertyChanged(this, new PropertyChangedEventArgs(nameof(Password)));
}
}
public string Length
{
get { return _length; }
set
{
_length = value;
PropertyChanged(this, new PropertyChangedEventArgs(nameof(Length)));
}
}
}
}

View file

@ -24,6 +24,7 @@ namespace Bit.App.Pages
public void Init() public void Init()
{ {
var generatorCell = new ToolsViewCell("Password Generator", "Automatically generate strong, unique passwords for your logins.", "refresh"); var generatorCell = new ToolsViewCell("Password Generator", "Automatically generate strong, unique passwords for your logins.", "refresh");
generatorCell.Tapped += GeneratorCell_Tapped;
var extensionCell = new ToolsViewCell("bitwarden App Extension", "Use bitwarden in Safari and other apps to auto-fill your logins.", "upload"); var extensionCell = new ToolsViewCell("bitwarden App Extension", "Use bitwarden in Safari and other apps to auto-fill your logins.", "upload");
var webCell = new ToolsViewCell("bitwarden Web Vault", "Manage your logins from any web browser with the bitwarden web vault.", "globe"); var webCell = new ToolsViewCell("bitwarden Web Vault", "Manage your logins from any web browser with the bitwarden web vault.", "globe");
webCell.Tapped += WebCell_Tapped; webCell.Tapped += WebCell_Tapped;
@ -57,6 +58,11 @@ namespace Bit.App.Pages
Content = table; Content = table;
} }
private void GeneratorCell_Tapped(object sender, EventArgs e)
{
Navigation.PushModalAsync(new ExtendedNavigationPage(new ToolsPasswordGeneratorPage()));
}
private void WebCell_Tapped(object sender, EventArgs e) private void WebCell_Tapped(object sender, EventArgs e)
{ {
Device.OpenUri(new Uri("https://vault.bitwarden.com")); Device.OpenUri(new Uri("https://vault.bitwarden.com"));

View file

@ -0,0 +1,210 @@
using System;
using Acr.UserDialogs;
using Bit.App.Abstractions;
using Bit.App.Controls;
using Bit.App.Models.Page;
using Bit.App.Resources;
using Plugin.Settings.Abstractions;
using Xamarin.Forms;
using XLabs.Ioc;
namespace Bit.App.Pages
{
public class ToolsPasswordGeneratorPage : ExtendedContentPage
{
private readonly IUserDialogs _userDialogs;
private readonly IPasswordGenerationService _passwordGenerationService;
private readonly ISettings _settings;
private readonly IClipboardService _clipboardService;
public ToolsPasswordGeneratorPage()
{
_userDialogs = Resolver.Resolve<IUserDialogs>();
_passwordGenerationService = Resolver.Resolve<IPasswordGenerationService>();
_settings = Resolver.Resolve<ISettings>();
_clipboardService = Resolver.Resolve<IClipboardService>();
Init();
}
public PasswordGeneratorPageModel Model { get; private set; } = new PasswordGeneratorPageModel();
public Label Password { get; private set; }
public SliderViewCell SliderCell { get; private set; }
public void Init()
{
Password = new Label
{
FontSize = Device.GetNamedSize(NamedSize.Large, typeof(Label)),
Margin = new Thickness(15, 40, 15, 0),
HorizontalTextAlignment = TextAlignment.Center,
FontFamily = "Courier",
LineBreakMode = LineBreakMode.TailTruncation
};
var tgr = new TapGestureRecognizer();
tgr.Tapped += Tgr_Tapped;
Password.GestureRecognizers.Add(tgr);
Password.SetBinding<PasswordGeneratorPageModel>(Label.TextProperty, m => m.Password);
SliderCell = new SliderViewCell(this, _passwordGenerationService, _settings);
var settingsCell = new ExtendedTextCell { Text = "More Settings", ShowDisclousure = true };
settingsCell.Tapped += SettingsCell_Tapped;
var buttonColor = Color.FromHex("3c8dbc");
var regenerateCell = new ExtendedTextCell { Text = "Regenerate Password", TextColor = buttonColor };
regenerateCell.Tapped += RegenerateCell_Tapped; ;
var copyCell = new ExtendedTextCell { Text = "Copy Password", TextColor = buttonColor };
copyCell.Tapped += CopyCell_Tapped;
var saveCell = new ExtendedTextCell { Text = "Save Password", TextColor = buttonColor };
saveCell.Tapped += SaveCell_Tapped;
var table = new ExtendedTableView
{
EnableScrolling = false,
Intent = TableIntent.Menu,
HasUnevenRows = true,
Root = new TableRoot
{
new TableSection
{
SliderCell,
settingsCell
},
new TableSection
{
regenerateCell,
copyCell
},
new TableSection
{
saveCell
}
}
};
if(Device.OS == TargetPlatform.iOS)
{
table.RowHeight = -1;
table.EstimatedRowHeight = 44;
ToolbarItems.Add(new DismissModalToolBarItem(this, "Cancel"));
}
var stackLayout = new StackLayout
{
Orientation = StackOrientation.Vertical,
Children = { Password, table }
};
var scrollView = new ScrollView
{
Content = stackLayout,
Orientation = ScrollOrientation.Vertical
};
Title = "Generate Password";
Content = scrollView;
BindingContext = Model;
}
private void Tgr_Tapped(object sender, EventArgs e)
{
CopyPassword();
}
protected override void OnAppearing()
{
Model.Password = _passwordGenerationService.GeneratePassword();
Model.Length = _settings.GetValueOrDefault(Constants.PasswordGeneratorLength, 10).ToString();
base.OnAppearing();
}
private void RegenerateCell_Tapped(object sender, EventArgs e)
{
Model.Password = _passwordGenerationService.GeneratePassword();
}
private void SaveCell_Tapped(object sender, EventArgs e)
{
}
private void CopyCell_Tapped(object sender, EventArgs e)
{
CopyPassword();
}
private void SettingsCell_Tapped(object sender, EventArgs e)
{
Navigation.PushAsync(new ToolsPasswordGeneratorSettingsPage());
}
private void CopyPassword()
{
_clipboardService.CopyToClipboard(Password.Text);
_userDialogs.SuccessToast(string.Format(AppResources.ValueHasBeenCopied, AppResources.Password));
}
public class SliderViewCell : ExtendedViewCell
{
private readonly ToolsPasswordGeneratorPage _page;
private readonly IPasswordGenerationService _passwordGenerationService;
private readonly ISettings _settings;
public Label Value { get; set; }
public Slider LengthSlider { get; set; }
public SliderViewCell(
ToolsPasswordGeneratorPage page,
IPasswordGenerationService passwordGenerationService,
ISettings settings)
{
_page = page;
_passwordGenerationService = passwordGenerationService;
_settings = settings;
var label = new Label
{
Text = "Length",
HorizontalOptions = LayoutOptions.Start,
VerticalOptions = LayoutOptions.CenterAndExpand
};
LengthSlider = new Slider(5, 64, _settings.GetValueOrDefault(Constants.PasswordGeneratorLength, 10))
{
HorizontalOptions = LayoutOptions.FillAndExpand,
VerticalOptions = LayoutOptions.CenterAndExpand
};
Value = new Label
{
HorizontalOptions = LayoutOptions.End,
VerticalOptions = LayoutOptions.CenterAndExpand,
Style = (Style)Application.Current.Resources["text-muted"]
};
Value.SetBinding<PasswordGeneratorPageModel>(Label.TextProperty, m => m.Length);
LengthSlider.ValueChanged += Slider_ValueChanged;
var stackLayout = new StackLayout
{
Orientation = StackOrientation.Horizontal,
Spacing = 15,
Children = { label, LengthSlider, Value },
Padding = new Thickness(15)
};
View = stackLayout;
}
private void Slider_ValueChanged(object sender, ValueChangedEventArgs e)
{
var length = Convert.ToInt32(LengthSlider.Value);
_settings.AddOrUpdateValue(Constants.PasswordGeneratorLength, length);
_page.Model.Length = length.ToString();
_page.Model.Password = _passwordGenerationService.GeneratePassword();
}
}
}
}

View file

@ -0,0 +1,192 @@
using System;
using System.Threading.Tasks;
using Acr.UserDialogs;
using Bit.App.Abstractions;
using Bit.App.Controls;
using Bit.App.Resources;
using Plugin.Connectivity.Abstractions;
using Plugin.Settings.Abstractions;
using Xamarin.Forms;
using XLabs.Ioc;
namespace Bit.App.Pages
{
public class ToolsPasswordGeneratorSettingsPage : ExtendedContentPage
{
private readonly IUserDialogs _userDialogs;
private readonly ISettings _settings;
public ToolsPasswordGeneratorSettingsPage()
{
_userDialogs = Resolver.Resolve<IUserDialogs>();
_settings = Resolver.Resolve<ISettings>();
Init();
}
public ExtendedSwitchCell UppercaseCell { get; set; }
public ExtendedSwitchCell LowercaseCell { get; set; }
public ExtendedSwitchCell SpecialCell { get; set; }
public ExtendedSwitchCell NumbersCell { get; set; }
public ExtendedSwitchCell AvoidAmbiguousCell { get; set; }
public EntryCell SpecialMinCell { get; set; }
public EntryCell NumbersMinCell { get; set; }
public void Init()
{
UppercaseCell = new ExtendedSwitchCell
{
Text = "A-Z",
On = _settings.GetValueOrDefault(Constants.PasswordGeneratorUppercase, true)
};
UppercaseCell.OnChanged += UppercaseCell_OnChanged;
LowercaseCell = new ExtendedSwitchCell
{
Text = "a-z",
On = _settings.GetValueOrDefault(Constants.PasswordGeneratorLowercase, true)
};
LowercaseCell.OnChanged += LowercaseCell_OnChanged;
SpecialCell = new ExtendedSwitchCell
{
Text = "!@#$%^&*",
On = _settings.GetValueOrDefault(Constants.PasswordGeneratorSpecial, true)
};
SpecialCell.OnChanged += SpecialCell_OnChanged;
NumbersCell = new ExtendedSwitchCell
{
Text = "0-9",
On = _settings.GetValueOrDefault(Constants.PasswordGeneratorNumbers, true)
};
NumbersCell.OnChanged += NumbersCell_OnChanged;
AvoidAmbiguousCell = new ExtendedSwitchCell
{
Text = "Avoid Ambiguous Characters",
On = !_settings.GetValueOrDefault(Constants.PasswordGeneratorAmbiguous, false)
};
AvoidAmbiguousCell.OnChanged += AvoidAmbiguousCell_OnChanged; ;
NumbersMinCell = new EntryCell
{
Label = "Minimum Numbers",
Text = _settings.GetValueOrDefault(Constants.PasswordGeneratorMinNumbers, 1).ToString(),
Keyboard = Keyboard.Numeric
};
SpecialMinCell = new EntryCell
{
Label = "Minimum Special",
Text = _settings.GetValueOrDefault(Constants.PasswordGeneratorMinSpecial, 1).ToString(),
Keyboard = Keyboard.Numeric
};
var table = new ExtendedTableView
{
EnableScrolling = true,
Intent = TableIntent.Menu,
HasUnevenRows = true,
EnableSelection = false,
Root = new TableRoot
{
new TableSection
{
UppercaseCell,
LowercaseCell,
NumbersCell,
SpecialCell
},
new TableSection
{
NumbersMinCell,
SpecialMinCell
},
new TableSection
{
AvoidAmbiguousCell
}
}
};
if(Device.OS == TargetPlatform.iOS)
{
table.RowHeight = -1;
table.EstimatedRowHeight = 44;
}
Title = "Settings";
Content = table;
}
protected override void OnDisappearing()
{
int minNumber;
if(int.TryParse(NumbersMinCell.Text, out minNumber))
{
_settings.AddOrUpdateValue(Constants.PasswordGeneratorMinNumbers, minNumber);
}
int minSpecial;
if(int.TryParse(SpecialMinCell.Text, out minSpecial))
{
_settings.AddOrUpdateValue(Constants.PasswordGeneratorMinSpecial, minSpecial);
}
}
private void AvoidAmbiguousCell_OnChanged(object sender, ToggledEventArgs e)
{
_settings.AddOrUpdateValue(Constants.PasswordGeneratorAmbiguous, !AvoidAmbiguousCell.On);
}
private void NumbersCell_OnChanged(object sender, ToggledEventArgs e)
{
_settings.AddOrUpdateValue(Constants.PasswordGeneratorNumbers, NumbersCell.On);
if(InvalidState())
{
_settings.AddOrUpdateValue(Constants.PasswordGeneratorLowercase, true);
LowercaseCell.On = true;
}
}
private void SpecialCell_OnChanged(object sender, ToggledEventArgs e)
{
_settings.AddOrUpdateValue(Constants.PasswordGeneratorSpecial, SpecialCell.On);
if(InvalidState())
{
_settings.AddOrUpdateValue(Constants.PasswordGeneratorLowercase, true);
LowercaseCell.On = true;
}
}
private void LowercaseCell_OnChanged(object sender, ToggledEventArgs e)
{
_settings.AddOrUpdateValue(Constants.PasswordGeneratorLowercase, LowercaseCell.On);
if(InvalidState())
{
_settings.AddOrUpdateValue(Constants.PasswordGeneratorUppercase, true);
UppercaseCell.On = true;
}
}
private void UppercaseCell_OnChanged(object sender, ToggledEventArgs e)
{
_settings.AddOrUpdateValue(Constants.PasswordGeneratorUppercase, UppercaseCell.On);
if(InvalidState())
{
_settings.AddOrUpdateValue(Constants.PasswordGeneratorLowercase, true);
LowercaseCell.On = true;
}
}
private bool InvalidState()
{
return !LowercaseCell.On && !UppercaseCell.On && !NumbersCell.On && !SpecialCell.On;
}
}
}

View file

@ -82,17 +82,17 @@ namespace Bit.App.Pages
if(_connectivity.IsConnected && Device.OS == TargetPlatform.iOS && !_favorites) if(_connectivity.IsConnected && Device.OS == TargetPlatform.iOS && !_favorites)
{ {
var pushPromptShow = _settings.GetValueOrDefault<bool>(Constants.PushPromptShown); var pushPromptShow = _settings.GetValueOrDefault<bool>(Constants.PushInitialPromptShown);
if(!pushPromptShow) if(!pushPromptShow)
{ {
_settings.AddOrUpdateValue(Constants.PushPromptShown, true); _settings.AddOrUpdateValue(Constants.PushInitialPromptShown, true);
await _userDialogs.AlertAsync(@"bitwarden keeps your vault automatically synced by using push notifications. await _userDialogs.AlertAsync(@"bitwarden keeps your vault automatically synced by using push notifications.
For the best possible experience, please select ""Ok"" on the following prompt when asked to enable push notifications.", For the best possible experience, please select ""Ok"" on the following prompt when asked to enable push notifications.",
"Enable Automatic Syncing", "Ok, got it!"); "Enable Automatic Syncing", "Ok, got it!");
} }
// Check push registration once per day // Check push registration once per day
var lastPushRegistration = _settings.GetValueOrDefault<DateTime?>(Constants.PushLastRegistration); var lastPushRegistration = _settings.GetValueOrDefault<DateTime?>(Constants.PushLastRegistrationDate);
if(!pushPromptShow || !lastPushRegistration.HasValue || (DateTime.UtcNow - lastPushRegistration) > TimeSpan.FromDays(1)) if(!pushPromptShow || !lastPushRegistration.HasValue || (DateTime.UtcNow - lastPushRegistration) > TimeSpan.FromDays(1))
{ {
_pushNotification.Register(); _pushNotification.Register();

View file

@ -0,0 +1,168 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Bit.App.Abstractions;
using Plugin.Settings.Abstractions;
namespace Bit.App.Services
{
public class PasswordGenerationService : IPasswordGenerationService
{
private readonly ISettings _settings;
private Random _random = new Random();
public PasswordGenerationService(ISettings settings)
{
_settings = settings;
}
public string GeneratePassword()
{
int minUppercase = 1,
minLowercase = 1,
minNumbers = _settings.GetValueOrDefault(Constants.PasswordGeneratorMinNumbers, 1),
minSpecial = _settings.GetValueOrDefault(Constants.PasswordGeneratorMinSpecial, 1),
length = _settings.GetValueOrDefault(Constants.PasswordGeneratorLength, 10);
bool uppercase = _settings.GetValueOrDefault(Constants.PasswordGeneratorUppercase, true),
lowercase = _settings.GetValueOrDefault(Constants.PasswordGeneratorLowercase, true),
numbers = _settings.GetValueOrDefault(Constants.PasswordGeneratorNumbers, true),
special = _settings.GetValueOrDefault(Constants.PasswordGeneratorSpecial, true),
ambiguous = _settings.GetValueOrDefault(Constants.PasswordGeneratorAmbiguous, false);
// Sanitize
if(uppercase && minUppercase < 0)
{
minUppercase = 1;
}
if(lowercase && minLowercase < 0)
{
minLowercase = 1;
}
if(numbers && minNumbers < 0)
{
minNumbers = 1;
}
if(special && minSpecial < 0)
{
minSpecial = 1;
}
if(length < 1)
{
length = 10;
}
var minLength = minUppercase + minLowercase + minNumbers + minSpecial;
if(length < minLength)
{
length = minLength;
}
var positionsBuilder = new StringBuilder();
if(lowercase && minLowercase > 0)
{
for(int i = 0; i < minLowercase; i++)
{
positionsBuilder.Append("l");
}
}
if(uppercase && minUppercase > 0)
{
for(int i = 0; i < minUppercase; i++)
{
positionsBuilder.Append("u");
}
}
if(numbers && minNumbers > 0)
{
for(int i = 0; i < minNumbers; i++)
{
positionsBuilder.Append("n");
}
}
if(special && minSpecial > 0)
{
for(int i = 0; i < minSpecial; i++)
{
positionsBuilder.Append("s");
}
}
while(positionsBuilder.Length < length)
{
positionsBuilder.Append("a");
}
// Shuffle
var positions = positionsBuilder.ToString().ToCharArray().OrderBy(a => _random.Next()).ToArray();
// Build out other character sets
var allCharSet = string.Empty;
var lowercaseCharSet = "abcdefghijkmnopqrstuvwxyz";
if(ambiguous)
{
lowercaseCharSet = string.Concat(lowercaseCharSet, "l");
}
if(lowercase)
{
allCharSet = string.Concat(allCharSet, lowercaseCharSet);
}
var uppercaseCharSet = "ABCDEFGHIJKLMNPQRSTUVWXYZ";
if(ambiguous)
{
uppercaseCharSet = string.Concat(uppercaseCharSet, "O");
}
if(uppercase)
{
allCharSet = string.Concat(allCharSet, uppercaseCharSet);
}
var numberCharSet = "23456789";
if(ambiguous)
{
numberCharSet = string.Concat(numberCharSet, "01");
}
if(numbers)
{
allCharSet = string.Concat(allCharSet, numberCharSet);
}
var specialCharSet = "!@#$%^&*";
if(special)
{
allCharSet = string.Concat(allCharSet, specialCharSet);
}
var password = new StringBuilder();
for(var i = 0; i < length; i++)
{
string positionChars = string.Empty;
switch(positions[i])
{
case 'l':
positionChars = lowercaseCharSet;
break;
case 'u':
positionChars = uppercaseCharSet;
break;
case 'n':
positionChars = numberCharSet;
break;
case 's':
positionChars = specialCharSet;
break;
case 'a':
positionChars = allCharSet;
break;
}
var randomCharIndex = _random.Next(0, positionChars.Length - 1);
password.Append(positionChars[randomCharIndex]);
}
return password.ToString();
}
}
}

View file

@ -76,7 +76,7 @@ namespace Bit.App.Services
if(response.Succeeded) if(response.Succeeded)
{ {
Debug.WriteLine("Registered device with server."); Debug.WriteLine("Registered device with server.");
_settings.AddOrUpdateValue(Constants.PushLastRegistration, DateTime.UtcNow); _settings.AddOrUpdateValue(Constants.PushLastRegistrationDate, DateTime.UtcNow);
} }
else else
{ {
@ -87,7 +87,7 @@ namespace Bit.App.Services
public void OnUnregistered(DeviceType deviceType) public void OnUnregistered(DeviceType deviceType)
{ {
Debug.WriteLine("Push Notification - Device Unnregistered"); Debug.WriteLine("Push Notification - Device Unnregistered");
_settings.Remove(Constants.PushLastRegistration); _settings.Remove(Constants.PushLastRegistrationDate);
} }
public void OnError(string message, DeviceType deviceType) public void OnError(string message, DeviceType deviceType)

View file

@ -94,6 +94,7 @@ namespace Bit.iOS.Extension
.RegisterType<IFolderService, FolderService>(new ContainerControlledLifetimeManager()) .RegisterType<IFolderService, FolderService>(new ContainerControlledLifetimeManager())
.RegisterType<ISiteService, SiteService>(new ContainerControlledLifetimeManager()) .RegisterType<ISiteService, SiteService>(new ContainerControlledLifetimeManager())
.RegisterType<ISyncService, SyncService>(new ContainerControlledLifetimeManager()) .RegisterType<ISyncService, SyncService>(new ContainerControlledLifetimeManager())
.RegisterType<IPasswordGenerationService, PasswordGenerationService>(new ContainerControlledLifetimeManager())
//.RegisterType<IClipboardService, ClipboardService>(new ContainerControlledLifetimeManager()) //.RegisterType<IClipboardService, ClipboardService>(new ContainerControlledLifetimeManager())
// Repositories // Repositories
.RegisterType<IFolderRepository, FolderRepository>(new ContainerControlledLifetimeManager()) .RegisterType<IFolderRepository, FolderRepository>(new ContainerControlledLifetimeManager())
@ -101,10 +102,10 @@ namespace Bit.iOS.Extension
.RegisterType<ISiteRepository, SiteRepository>(new ContainerControlledLifetimeManager()) .RegisterType<ISiteRepository, SiteRepository>(new ContainerControlledLifetimeManager())
.RegisterType<ISiteApiRepository, SiteApiRepository>(new ContainerControlledLifetimeManager()) .RegisterType<ISiteApiRepository, SiteApiRepository>(new ContainerControlledLifetimeManager())
.RegisterType<IAuthApiRepository, AuthApiRepository>(new ContainerControlledLifetimeManager()); .RegisterType<IAuthApiRepository, AuthApiRepository>(new ContainerControlledLifetimeManager());
// Other // Other
//.RegisterInstance(CrossConnectivity.Current, new ContainerControlledLifetimeManager()) //.RegisterInstance(CrossConnectivity.Current, new ContainerControlledLifetimeManager())
//.RegisterInstance(UserDialogs.Instance, new ContainerControlledLifetimeManager()) //.RegisterInstance(UserDialogs.Instance, new ContainerControlledLifetimeManager())
//.RegisterInstance(CrossFingerprint.Current, new ContainerControlledLifetimeManager()); //.RegisterInstance(CrossFingerprint.Current, new ContainerControlledLifetimeManager());
ISettings settings = new Settings("group.com.8bit.bitwarden"); ISettings settings = new Settings("group.com.8bit.bitwarden");
container.RegisterInstance(settings, new ContainerControlledLifetimeManager()); container.RegisterInstance(settings, new ContainerControlledLifetimeManager());

View file

@ -185,6 +185,7 @@ namespace Bit.iOS
.RegisterType<IClipboardService, ClipboardService>(new ContainerControlledLifetimeManager()) .RegisterType<IClipboardService, ClipboardService>(new ContainerControlledLifetimeManager())
.RegisterType<IPushNotificationListener, PushNotificationListener>(new ContainerControlledLifetimeManager()) .RegisterType<IPushNotificationListener, PushNotificationListener>(new ContainerControlledLifetimeManager())
.RegisterType<IAppIdService, AppIdService>(new ContainerControlledLifetimeManager()) .RegisterType<IAppIdService, AppIdService>(new ContainerControlledLifetimeManager())
.RegisterType<IPasswordGenerationService, PasswordGenerationService>(new ContainerControlledLifetimeManager())
// Repositories // Repositories
.RegisterType<IFolderRepository, FolderRepository>(new ContainerControlledLifetimeManager()) .RegisterType<IFolderRepository, FolderRepository>(new ContainerControlledLifetimeManager())
.RegisterType<IFolderApiRepository, FolderApiRepository>(new ContainerControlledLifetimeManager()) .RegisterType<IFolderApiRepository, FolderApiRepository>(new ContainerControlledLifetimeManager())

View file

@ -42,6 +42,14 @@
<HintPath>..\..\packages\NSubstitute.1.10.0.0\lib\net45\NSubstitute.dll</HintPath> <HintPath>..\..\packages\NSubstitute.1.10.0.0\lib\net45\NSubstitute.dll</HintPath>
<Private>True</Private> <Private>True</Private>
</Reference> </Reference>
<Reference Include="Plugin.Settings, Version=2.1.0.0, Culture=neutral, processorArchitecture=MSIL">
<HintPath>..\..\packages\Xam.Plugins.Settings.2.1.0\lib\net45\Plugin.Settings.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="Plugin.Settings.Abstractions, Version=2.1.0.0, Culture=neutral, processorArchitecture=MSIL">
<HintPath>..\..\packages\Xam.Plugins.Settings.2.1.0\lib\net45\Plugin.Settings.Abstractions.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="System" /> <Reference Include="System" />
<Reference Include="xunit.abstractions, Version=2.0.0.0, Culture=neutral, PublicKeyToken=8d05b1bb7a6fdb6c, processorArchitecture=MSIL"> <Reference Include="xunit.abstractions, Version=2.0.0.0, Culture=neutral, PublicKeyToken=8d05b1bb7a6fdb6c, processorArchitecture=MSIL">
<HintPath>..\..\packages\xunit.abstractions.2.0.0\lib\net35\xunit.abstractions.dll</HintPath> <HintPath>..\..\packages\xunit.abstractions.2.0.0\lib\net35\xunit.abstractions.dll</HintPath>
@ -69,6 +77,7 @@
<Otherwise /> <Otherwise />
</Choose> </Choose>
<ItemGroup> <ItemGroup>
<Compile Include="PasswordGeneratorServiceTests.cs" />
<Compile Include="CryptoServiceTests.cs" /> <Compile Include="CryptoServiceTests.cs" />
<Compile Include="Properties\AssemblyInfo.cs" /> <Compile Include="Properties\AssemblyInfo.cs" />
</ItemGroup> </ItemGroup>

View file

@ -0,0 +1,177 @@
using System;
using System.Linq;
using Bit.App.Services;
using NSubstitute;
using Plugin.Settings.Abstractions;
using Xunit;
namespace Bit.App.Test
{
public class PasswordGeneratorServiceTests
{
[Fact]
public void GeneratesCorrectLength()
{
var settings = SetupDefaultSettings();
settings.GetValueOrDefault(Constants.PasswordGeneratorLength, Arg.Any<int>()).Returns(25);
var service = new PasswordGenerationService(settings);
int i = 0;
while(i < 100)
{
var password = service.GeneratePassword();
Assert.True(password.Length == 25);
i++;
}
}
[Fact]
public void GeneratesCorrectMinNumbers()
{
var settings = SetupDefaultSettings();
settings.GetValueOrDefault(Constants.PasswordGeneratorMinNumbers, Arg.Any<int>()).Returns(25);
var service = new PasswordGenerationService(settings);
int i = 0;
while(i < 100)
{
var password = service.GeneratePassword();
Assert.True(password.Count(char.IsDigit) >= 25);
i++;
}
}
[Fact]
public void GeneratesCorrectMinSpecial()
{
var settings = SetupDefaultSettings();
settings.GetValueOrDefault(Constants.PasswordGeneratorMinSpecial, Arg.Any<int>()).Returns(25);
var service = new PasswordGenerationService(settings);
int i = 0;
while(i < 100)
{
var password = service.GeneratePassword();
Assert.True(password.Count(c => !char.IsLetterOrDigit(c)) >= 25);
i++;
}
}
[Fact]
public void GeneratesCorrectNoUppercase()
{
var settings = SetupDefaultSettings();
settings.GetValueOrDefault(Constants.PasswordGeneratorUppercase, Arg.Any<bool>()).Returns(false);
var service = new PasswordGenerationService(settings);
int i = 0;
while(i < 100)
{
var password = service.GeneratePassword();
Assert.True(password.Count(char.IsUpper) == 0);
i++;
}
}
[Fact]
public void GeneratesCorrectUppercase()
{
var settings = SetupDefaultSettings();
settings.GetValueOrDefault(Constants.PasswordGeneratorUppercase, Arg.Any<bool>()).Returns(true);
settings.GetValueOrDefault(Constants.PasswordGeneratorLowercase, Arg.Any<bool>()).Returns(false);
settings.GetValueOrDefault(Constants.PasswordGeneratorNumbers, Arg.Any<bool>()).Returns(false);
settings.GetValueOrDefault(Constants.PasswordGeneratorSpecial, Arg.Any<bool>()).Returns(false);
var service = new PasswordGenerationService(settings);
int i = 0;
while(i < 100)
{
var password = service.GeneratePassword();
Assert.True(password.Count(char.IsUpper) == 50);
i++;
}
}
[Fact]
public void GeneratesCorrectNoLowercase()
{
var settings = SetupDefaultSettings();
settings.GetValueOrDefault(Constants.PasswordGeneratorLowercase, Arg.Any<bool>()).Returns(false);
var service = new PasswordGenerationService(settings);
int i = 0;
while(i < 100)
{
var password = service.GeneratePassword();
Assert.True(password.Count(char.IsLower) == 0);
i++;
}
}
[Fact]
public void GeneratesCorrectLowercase()
{
var settings = SetupDefaultSettings();
settings.GetValueOrDefault(Constants.PasswordGeneratorUppercase, Arg.Any<bool>()).Returns(false);
settings.GetValueOrDefault(Constants.PasswordGeneratorLowercase, Arg.Any<bool>()).Returns(true);
settings.GetValueOrDefault(Constants.PasswordGeneratorNumbers, Arg.Any<bool>()).Returns(false);
settings.GetValueOrDefault(Constants.PasswordGeneratorSpecial, Arg.Any<bool>()).Returns(false);
var service = new PasswordGenerationService(settings);
int i = 0;
while(i < 100)
{
var password = service.GeneratePassword();
Assert.True(password.Count(char.IsLower) == 50);
i++;
}
}
[Fact]
public void GeneratesCorrectMixed()
{
var settings = SetupDefaultSettings();
var service = new PasswordGenerationService(settings);
int i = 0;
while(i < 100)
{
var password = service.GeneratePassword();
Assert.True(password.Count(char.IsLower) >= 1);
Assert.True(password.Count(char.IsDigit) >= 1);
Assert.True(password.Count(char.IsUpper) >= 1);
Assert.True(password.Count(c => !char.IsLetterOrDigit(c)) >= 1);
i++;
}
}
[Fact]
public void GeneratesCorrectNoAmbiguous()
{
var settings = SetupDefaultSettings();
settings.GetValueOrDefault(Constants.PasswordGeneratorAmbiguous, Arg.Any<bool>()).Returns(false);
var service = new PasswordGenerationService(settings);
int i = 0;
while(i < 100)
{
var password = service.GeneratePassword();
Assert.True(!password.Any(c => c == '1' || c == 'l' || c == '0' || c == 'O'));
i++;
}
}
private ISettings SetupDefaultSettings()
{
var settings = Substitute.For<ISettings>();
settings.GetValueOrDefault(Constants.PasswordGeneratorLength, Arg.Any<int>()).Returns(50);
settings.GetValueOrDefault(Constants.PasswordGeneratorMinNumbers, Arg.Any<int>()).Returns(1);
settings.GetValueOrDefault(Constants.PasswordGeneratorMinSpecial, Arg.Any<int>()).Returns(1);
settings.GetValueOrDefault(Constants.PasswordGeneratorUppercase, Arg.Any<bool>()).Returns(true);
settings.GetValueOrDefault(Constants.PasswordGeneratorLowercase, Arg.Any<bool>()).Returns(true);
settings.GetValueOrDefault(Constants.PasswordGeneratorSpecial, Arg.Any<bool>()).Returns(true);
settings.GetValueOrDefault(Constants.PasswordGeneratorNumbers, Arg.Any<bool>()).Returns(true);
settings.GetValueOrDefault(Constants.PasswordGeneratorAmbiguous, Arg.Any<bool>()).Returns(false);
return settings;
}
}
}

View file

@ -1,6 +1,7 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<packages> <packages>
<package id="NSubstitute" version="1.10.0.0" targetFramework="net46" /> <package id="NSubstitute" version="1.10.0.0" targetFramework="net46" />
<package id="Xam.Plugins.Settings" version="2.1.0" targetFramework="net46" />
<package id="xunit" version="2.1.0" targetFramework="net46" /> <package id="xunit" version="2.1.0" targetFramework="net46" />
<package id="xunit.abstractions" version="2.0.0" targetFramework="net46" /> <package id="xunit.abstractions" version="2.0.0" targetFramework="net46" />
<package id="xunit.assert" version="2.1.0" targetFramework="net46" /> <package id="xunit.assert" version="2.1.0" targetFramework="net46" />