diff --git a/src/App/Pages/Generator/GeneratorPage.xaml b/src/App/Pages/Generator/GeneratorPage.xaml
index 52731c790..b179e34da 100644
--- a/src/App/Pages/Generator/GeneratorPage.xaml
+++ b/src/App/Pages/Generator/GeneratorPage.xaml
@@ -100,6 +100,27 @@
IsTextPredictionEnabled="False"
StyleClass="box-value" />
+
+
+
+
+
+
+
+
+
diff --git a/src/App/Pages/Generator/GeneratorPageViewModel.cs b/src/App/Pages/Generator/GeneratorPageViewModel.cs
index f224af933..4aeddd712 100644
--- a/src/App/Pages/Generator/GeneratorPageViewModel.cs
+++ b/src/App/Pages/Generator/GeneratorPageViewModel.cs
@@ -3,9 +3,7 @@ using Bit.App.Utilities;
using Bit.Core.Abstractions;
using Bit.Core.Models.Domain;
using Bit.Core.Utilities;
-using System;
using System.Collections.Generic;
-using System.Threading;
using System.Threading.Tasks;
using Xamarin.Forms;
@@ -29,6 +27,8 @@ namespace Bit.App.Pages
private int _length = 5;
private int _numWords = 3;
private string _wordSeparator;
+ private bool _capitalize;
+ private bool _includeNumber;
private int _typeSelectedIndex;
private bool _doneIniting;
@@ -196,6 +196,32 @@ namespace Bit.App.Pages
}
}
+ public bool Capitalize
+ {
+ get => _capitalize;
+ set
+ {
+ if(SetProperty(ref _capitalize, value))
+ {
+ _options.Capitalize = value;
+ var task = SaveOptionsAsync();
+ }
+ }
+ }
+
+ public bool IncludeNumber
+ {
+ get => _includeNumber;
+ set
+ {
+ if(SetProperty(ref _includeNumber, value))
+ {
+ _options.Number = value;
+ var task = SaveOptionsAsync();
+ }
+ }
+ }
+
public int TypeSelectedIndex
{
get => _typeSelectedIndex;
@@ -273,6 +299,8 @@ namespace Bit.App.Pages
Uppercase = _options.Uppercase.GetValueOrDefault();
Lowercase = _options.Lowercase.GetValueOrDefault();
Length = _options.Length.GetValueOrDefault(5);
+ Capitalize = _options.Capitalize.GetValueOrDefault();
+ IncludeNumber = _options.IncludeNumber.GetValueOrDefault();
}
private void SetOptions()
@@ -288,6 +316,8 @@ namespace Bit.App.Pages
_options.Uppercase = Uppercase;
_options.Lowercase = Lowercase;
_options.Length = Length;
+ _options.Capitalize = Capitalize;
+ _options.IncludeNumber = IncludeNumber;
}
}
}
diff --git a/src/App/Resources/AppResources.Designer.cs b/src/App/Resources/AppResources.Designer.cs
index 13b71ec61..cb9b6e53e 100644
--- a/src/App/Resources/AppResources.Designer.cs
+++ b/src/App/Resources/AppResources.Designer.cs
@@ -807,6 +807,15 @@ namespace Bit.App.Resources {
}
}
+ ///
+ /// Looks up a localized string similar to Capitalize.
+ ///
+ public static string Capitalize {
+ get {
+ return ResourceManager.GetString("Capitalize", resourceCulture);
+ }
+ }
+
///
/// Looks up a localized string similar to Cardholder Name.
///
@@ -1932,6 +1941,15 @@ namespace Bit.App.Resources {
}
}
+ ///
+ /// Looks up a localized string similar to Include Number.
+ ///
+ public static string IncludeNumber {
+ get {
+ return ResourceManager.GetString("IncludeNumber", resourceCulture);
+ }
+ }
+
///
/// Looks up a localized string similar to Please connect to the internet before continuing..
///
diff --git a/src/App/Resources/AppResources.resx b/src/App/Resources/AppResources.resx
index e98250ab3..9fbc08285 100644
--- a/src/App/Resources/AppResources.resx
+++ b/src/App/Resources/AppResources.resx
@@ -1559,4 +1559,11 @@
Your theme changes will apply when the app is restarted.
+
+ Capitalize
+ ex. Uppercase the first character of a word.
+
+
+ Include Number
+
\ No newline at end of file
diff --git a/src/Core/Models/Domain/PasswordGenerationOptions.cs b/src/Core/Models/Domain/PasswordGenerationOptions.cs
index 605ff0040..1a4687e1c 100644
--- a/src/Core/Models/Domain/PasswordGenerationOptions.cs
+++ b/src/Core/Models/Domain/PasswordGenerationOptions.cs
@@ -21,6 +21,8 @@
Type = "password";
NumWords = 3;
WordSeparator = "-";
+ Capitalize = false;
+ IncludeNumber = false;
}
}
@@ -37,6 +39,8 @@
public string Type { get; set; }
public int? NumWords { get; set; }
public string WordSeparator { get; set; }
+ public bool? Capitalize { get; set; }
+ public bool? IncludeNumber { get; set; }
public void Merge(PasswordGenerationOptions defaults)
{
@@ -53,6 +57,8 @@
Type = Type ?? defaults.Type;
NumWords = NumWords ?? defaults.NumWords;
WordSeparator = WordSeparator ?? defaults.WordSeparator;
+ Capitalize = Capitalize ?? defaults.Capitalize;
+ IncludeNumber = IncludeNumber ?? defaults.IncludeNumber;
}
}
}
diff --git a/src/Core/Services/PasswordGenerationService.cs b/src/Core/Services/PasswordGenerationService.cs
index f379e4f66..7321be26e 100644
--- a/src/Core/Services/PasswordGenerationService.cs
+++ b/src/Core/Services/PasswordGenerationService.cs
@@ -192,12 +192,31 @@ namespace Bit.Core.Services
{
options.WordSeparator = " ";
}
+ if(options.Capitalize == null)
+ {
+ options.Capitalize = false;
+ }
+ if(options.IncludeNumber == null)
+ {
+ options.IncludeNumber = false;
+ }
var listLength = EEFLongWordList.Instance.List.Count - 1;
var wordList = new List();
for(int i = 0; i < options.NumWords.GetValueOrDefault(); i++)
{
var wordIndex = await _cryptoService.RandomNumberAsync(0, listLength);
- wordList.Add(EEFLongWordList.Instance.List[wordIndex]);
+ if(options.Capitalize.GetValueOrDefault())
+ {
+ wordList.Add(Capitalize(EEFLongWordList.Instance.List[wordIndex]));
+ }
+ else
+ {
+ wordList.Add(EEFLongWordList.Instance.List[wordIndex]);
+ }
+ }
+ if(options.IncludeNumber.GetValueOrDefault())
+ {
+ await AppendRandomNumberToRandomWordAsync(wordList);
}
return string.Join(options.WordSeparator, wordList);
}
@@ -400,5 +419,21 @@ namespace Bit.Core.Services
}
return history.Last().Password == password;
}
+
+ private string Capitalize(string str)
+ {
+ return str.First().ToString().ToUpper() + str.Substring(1);
+ }
+
+ private async Task AppendRandomNumberToRandomWordAsync(List wordList)
+ {
+ if(wordList == null || wordList.Count <= 0)
+ {
+ return;
+ }
+ var index = await _cryptoService.RandomNumberAsync(0, wordList.Count - 1);
+ var num = await _cryptoService.RandomNumberAsync(0, 9);
+ wordList[index] = wordList[index] + num;
+ }
}
}