From 421f7e8799ce53545deb632bcb0332b11cb2c707 Mon Sep 17 00:00:00 2001 From: Andreas Schneider Date: Mon, 10 Dec 2018 15:30:11 +0100 Subject: [PATCH] Colorized passwords (#424) * Added utility to format passwords using spans * Use the password formatter to render the cipher password * Colorize the password in the password generator --- .../Models/Page/PasswordGeneratorPageModel.cs | 8 ++ .../Models/Page/VaultViewCipherPageModel.cs | 9 +- .../Pages/Tools/ToolsPasswordGeneratorPage.cs | 2 +- src/App/Pages/Vault/VaultViewCipherPage.cs | 4 +- src/App/Utilities/PasswordFormatter.cs | 82 +++++++++++++++++++ 5 files changed, 98 insertions(+), 7 deletions(-) create mode 100644 src/App/Utilities/PasswordFormatter.cs diff --git a/src/App/Models/Page/PasswordGeneratorPageModel.cs b/src/App/Models/Page/PasswordGeneratorPageModel.cs index 45437e24c..0b148a2dc 100644 --- a/src/App/Models/Page/PasswordGeneratorPageModel.cs +++ b/src/App/Models/Page/PasswordGeneratorPageModel.cs @@ -1,5 +1,7 @@ using System; using System.ComponentModel; +using Bit.App.Utilities; +using Xamarin.Forms; namespace Bit.App.Models.Page { @@ -19,9 +21,15 @@ namespace Bit.App.Models.Page { _password = value; PropertyChanged(this, new PropertyChangedEventArgs(nameof(Password))); + PropertyChanged(this, new PropertyChangedEventArgs(nameof(FormattedPassword))); } } + public FormattedString FormattedPassword + { + get { return PasswordFormatter.FormatPassword(_password); } + } + public string Length { get { return _length; } diff --git a/src/App/Models/Page/VaultViewCipherPageModel.cs b/src/App/Models/Page/VaultViewCipherPageModel.cs index 5845a438e..301bb6e31 100644 --- a/src/App/Models/Page/VaultViewCipherPageModel.cs +++ b/src/App/Models/Page/VaultViewCipherPageModel.cs @@ -4,6 +4,7 @@ using Xamarin.Forms; using System.Collections.Generic; using Bit.App.Enums; using Bit.App.Resources; +using Bit.App.Utilities; namespace Bit.App.Models.Page { @@ -122,7 +123,7 @@ namespace Bit.App.Models.Page { _loginPassword = value; PropertyChanged(this, new PropertyChangedEventArgs(nameof(LoginPassword))); - PropertyChanged(this, new PropertyChangedEventArgs(nameof(MaskedLoginPassword))); + PropertyChanged(this, new PropertyChangedEventArgs(nameof(FormattedLoginPassword))); PropertyChanged(this, new PropertyChangedEventArgs(nameof(ShowLoginPassword))); } } @@ -134,12 +135,12 @@ namespace Bit.App.Models.Page { _loginRevealPassword = value; PropertyChanged(this, new PropertyChangedEventArgs(nameof(RevealLoginPassword))); - PropertyChanged(this, new PropertyChangedEventArgs(nameof(MaskedLoginPassword))); + PropertyChanged(this, new PropertyChangedEventArgs(nameof(FormattedLoginPassword))); PropertyChanged(this, new PropertyChangedEventArgs(nameof(LoginShowHideImage))); } } - public string MaskedLoginPassword => RevealLoginPassword ? - LoginPassword : LoginPassword == null ? null : MaskedPasswordString; + public FormattedString FormattedLoginPassword => RevealLoginPassword ? + PasswordFormatter.FormatPassword(LoginPassword) : LoginPassword == null ? null : MaskedPasswordString; public ImageSource LoginShowHideImage => RevealLoginPassword ? ImageSource.FromFile("eye_slash.png") : ImageSource.FromFile("eye.png"); diff --git a/src/App/Pages/Tools/ToolsPasswordGeneratorPage.cs b/src/App/Pages/Tools/ToolsPasswordGeneratorPage.cs index fb5fc6564..cdef285a1 100644 --- a/src/App/Pages/Tools/ToolsPasswordGeneratorPage.cs +++ b/src/App/Pages/Tools/ToolsPasswordGeneratorPage.cs @@ -67,7 +67,7 @@ namespace Bit.App.Pages Tgr = new TapGestureRecognizer(); Password.GestureRecognizers.Add(Tgr); - Password.SetBinding(Label.TextProperty, nameof(PasswordGeneratorPageModel.Password)); + Password.SetBinding(Label.FormattedTextProperty, nameof(PasswordGeneratorPageModel.FormattedPassword)); SliderCell = new SliderViewCell(this, _passwordGenerationService, _settings); diff --git a/src/App/Pages/Vault/VaultViewCipherPage.cs b/src/App/Pages/Vault/VaultViewCipherPage.cs index cd1bf19cd..eb8fd3769 100644 --- a/src/App/Pages/Vault/VaultViewCipherPage.cs +++ b/src/App/Pages/Vault/VaultViewCipherPage.cs @@ -129,8 +129,8 @@ namespace Bit.App.Pages // Password LoginPasswordCell = new LabeledValueCell(AppResources.Password, button1Image: string.Empty, button2Image: "clipboard.png"); - LoginPasswordCell.Value.SetBinding(Label.TextProperty, - nameof(VaultViewCipherPageModel.MaskedLoginPassword)); + LoginPasswordCell.Value.SetBinding(Label.FormattedTextProperty, + nameof(VaultViewCipherPageModel.FormattedLoginPassword)); LoginPasswordCell.Button1.SetBinding(Button.ImageProperty, nameof(VaultViewCipherPageModel.LoginShowHideImage)); LoginPasswordCell.Button1.Command = diff --git a/src/App/Utilities/PasswordFormatter.cs b/src/App/Utilities/PasswordFormatter.cs new file mode 100644 index 000000000..58d6f249a --- /dev/null +++ b/src/App/Utilities/PasswordFormatter.cs @@ -0,0 +1,82 @@ +using System; +using Xamarin.Forms; + +namespace Bit.App.Utilities +{ + /** + * Helper class to format a password with numeric encoding to separate + * normal text from numbers and special characters. + */ + class PasswordFormatter + { + /** + * This enum is used for the statemachine when building the colorized + * password string. + */ + private enum CharType + { + None, + Normal, + Number, + Special + } + + public static FormattedString FormatPassword(String password) + { + var result = new FormattedString(); + + // Start off with an empty span to prevent possible NPEs. Due to the way the statemachine + // works, this will actually always be replaced by a new span anyway. + var currentSpan = new Span(); + // Start with an otherwise uncovered case so we will definitely enter the "something changed" + // state. + var currentType = CharType.None; + + foreach (var c in password) + { + // First, identify what the current char is. + CharType charType; + if (char.IsLetter(c)) + { + charType = CharType.Normal; + } + else if (char.IsDigit(c)) + { + charType = CharType.Number; + } + else + { + charType = CharType.Special; + } + + // If the char type changed, build a new span to append the text to. + if (charType != currentType) + { + currentSpan = new Span(); + result.Spans.Add(currentSpan); + currentType = charType; + + // Switch the color if it is not a normal text. Otherwise leave the + // default value. + switch (currentType) + { + case CharType.Number: + { + currentSpan.TextColor = Color.DodgerBlue; + break; + } + case CharType.Special: + { + currentSpan.TextColor = Color.Firebrick; + break; + } + } + } + + currentSpan.Text += c; + } + + return result; + } + } +}