From 01d9ccc1101b69985fbc9b99fbbe9aabe097cb4b Mon Sep 17 00:00:00 2001 From: Kyle Spearrin Date: Mon, 30 Jul 2018 13:15:53 -0400 Subject: [PATCH] add password history and updated dates --- src/App/Models/Api/LoginType.cs | 2 + src/App/Models/Api/Request/CipherRequest.cs | 1 + .../Api/Request/PasswordHistoryRequest.cs | 8 ++++ src/App/Models/Api/Response/CipherResponse.cs | 1 + .../Api/Response/PasswordHistoryResponse.cs | 8 ++++ src/App/Models/Cipher.cs | 17 ++++++++ src/App/Models/Data/CipherData.cs | 11 +++++ .../Models/Data/CipherData/CipherDataModel.cs | 2 + .../Models/Data/CipherData/LoginDataModel.cs | 2 + .../CipherData/PasswordHistoryDataModel.cs | 18 +++++++++ src/App/Models/Login.cs | 2 + .../Models/Page/VaultViewCipherPageModel.cs | 40 ++++++++++++++++++- src/App/Models/PasswordHistory.cs | 18 +++++++++ src/App/Pages/Vault/VaultViewCipherPage.cs | 33 +++++++++++++++ src/App/Resources/AppResources.Designer.cs | 18 +++++++++ src/App/Resources/AppResources.resx | 10 ++++- 16 files changed, 188 insertions(+), 3 deletions(-) create mode 100644 src/App/Models/Api/Request/PasswordHistoryRequest.cs create mode 100644 src/App/Models/Api/Response/PasswordHistoryResponse.cs create mode 100644 src/App/Models/Data/CipherData/PasswordHistoryDataModel.cs create mode 100644 src/App/Models/PasswordHistory.cs diff --git a/src/App/Models/Api/LoginType.cs b/src/App/Models/Api/LoginType.cs index 48c3eaf06..10e3d3158 100644 --- a/src/App/Models/Api/LoginType.cs +++ b/src/App/Models/Api/LoginType.cs @@ -13,12 +13,14 @@ namespace Bit.App.Models.Api Uris = cipher.Login.Uris?.Select(u => new LoginUriType(u)); Username = cipher.Login.Username?.EncryptedString; Password = cipher.Login.Password?.EncryptedString; + Totp = cipher.Login.Totp?.EncryptedString; } public IEnumerable Uris { get; set; } public string Username { get; set; } public string Password { get; set; } + public System.DateTime? PasswordRevisionDate { get; set; } public string Totp { get; set; } } } diff --git a/src/App/Models/Api/Request/CipherRequest.cs b/src/App/Models/Api/Request/CipherRequest.cs index 3e3353157..67b3b38e2 100644 --- a/src/App/Models/Api/Request/CipherRequest.cs +++ b/src/App/Models/Api/Request/CipherRequest.cs @@ -46,6 +46,7 @@ namespace Bit.App.Models.Api public string Name { get; set; } public string Notes { get; set; } public IEnumerable Fields { get; set; } + public IEnumerable PasswordHistory { get; set; } public LoginType Login { get; set; } public CardType Card { get; set; } diff --git a/src/App/Models/Api/Request/PasswordHistoryRequest.cs b/src/App/Models/Api/Request/PasswordHistoryRequest.cs new file mode 100644 index 000000000..7fb05faee --- /dev/null +++ b/src/App/Models/Api/Request/PasswordHistoryRequest.cs @@ -0,0 +1,8 @@ +namespace Bit.App.Models.Api +{ + public class PasswordHistoryRequest + { + public string Password { get; set; } + public System.DateTime LastUsedDate { get; set; } + } +} diff --git a/src/App/Models/Api/Response/CipherResponse.cs b/src/App/Models/Api/Response/CipherResponse.cs index f9bf5a76c..f1bd0191d 100644 --- a/src/App/Models/Api/Response/CipherResponse.cs +++ b/src/App/Models/Api/Response/CipherResponse.cs @@ -22,6 +22,7 @@ namespace Bit.App.Models.Api public SecureNoteType SecureNote { get; set; } public IEnumerable Fields { get; set; } public IEnumerable Attachments { get; set; } + public IEnumerable PasswordHistory { get; set; } public IEnumerable CollectionIds { get; set; } public DateTime RevisionDate { get; set; } } diff --git a/src/App/Models/Api/Response/PasswordHistoryResponse.cs b/src/App/Models/Api/Response/PasswordHistoryResponse.cs new file mode 100644 index 000000000..3491d586b --- /dev/null +++ b/src/App/Models/Api/Response/PasswordHistoryResponse.cs @@ -0,0 +1,8 @@ +namespace Bit.App.Models.Api +{ + public class PasswordHistoryResponse + { + public string Password { get; set; } + public System.DateTime LastUsedDate { get; set; } + } +} diff --git a/src/App/Models/Cipher.cs b/src/App/Models/Cipher.cs index d36f92276..4191d1d4e 100644 --- a/src/App/Models/Cipher.cs +++ b/src/App/Models/Cipher.cs @@ -24,6 +24,7 @@ namespace Bit.App.Models Edit = data.Edit; OrganizationUseTotp = data.OrganizationUseTotp; Attachments = attachments?.Select(a => new Attachment(a)); + RevisionDate = data.RevisionDateTime; switch(Type) { @@ -52,6 +53,17 @@ namespace Bit.App.Models } catch(JsonSerializationException) { } } + + if(!string.IsNullOrWhiteSpace(data.PasswordHistory)) + { + try + { + var phModels = JsonConvert.DeserializeObject>( + data.PasswordHistory); + PasswordHistory = phModels?.Select(f => new PasswordHistory(f)); + } + catch(JsonSerializationException) { } + } } public string Id { get; set; } @@ -62,14 +74,19 @@ namespace Bit.App.Models public CipherString Name { get; set; } public CipherString Notes { get; set; } public IEnumerable Fields { get; set; } + public IEnumerable PasswordHistory { get; set; } public bool Favorite { get; set; } public bool Edit { get; set; } public bool OrganizationUseTotp { get; set; } public IEnumerable Attachments { get; set; } + public System.DateTime RevisionDate { get; set; } public Login Login { get; set; } public Identity Identity { get; set; } public Card Card { get; set; } public SecureNote SecureNote { get; set; } + + public System.DateTime? PasswordRevisionDisplayDate => + Login?.Password == null ? (System.DateTime?)null : Login.PasswordRevisionDate ?? RevisionDate; } } diff --git a/src/App/Models/Data/CipherData.cs b/src/App/Models/Data/CipherData.cs index eece1b303..25f1df712 100644 --- a/src/App/Models/Data/CipherData.cs +++ b/src/App/Models/Data/CipherData.cs @@ -64,6 +64,16 @@ namespace Bit.App.Models.Data } catch(JsonSerializationException) { } } + + if(cipher.PasswordHistory != null && cipher.PasswordHistory.Any()) + { + try + { + PasswordHistory = JsonConvert.SerializeObject( + cipher.PasswordHistory.Select(h => new PasswordHistoryDataModel(h))); + } + catch(JsonSerializationException) { } + } } [PrimaryKey] @@ -75,6 +85,7 @@ namespace Bit.App.Models.Data public string Name { get; set; } public string Notes { get; set; } public string Fields { get; set; } + public string PasswordHistory { get; set; } public string Login { get; set; } public string Card { get; set; } public string Identity { get; set; } diff --git a/src/App/Models/Data/CipherData/CipherDataModel.cs b/src/App/Models/Data/CipherData/CipherDataModel.cs index 1e5c80379..2e200574b 100644 --- a/src/App/Models/Data/CipherData/CipherDataModel.cs +++ b/src/App/Models/Data/CipherData/CipherDataModel.cs @@ -13,10 +13,12 @@ namespace Bit.App.Models.Data Name = cipher.Name; Notes = cipher.Notes; Fields = cipher.Fields?.Select(f => new FieldDataModel(f)); + PasswordHistory = cipher.PasswordHistory?.Select(h => new PasswordHistoryDataModel(h)); } public string Name { get; set; } public string Notes { get; set; } public IEnumerable Fields { get; set; } + public IEnumerable PasswordHistory { get; set; } } } diff --git a/src/App/Models/Data/CipherData/LoginDataModel.cs b/src/App/Models/Data/CipherData/LoginDataModel.cs index d1078f060..435096066 100644 --- a/src/App/Models/Data/CipherData/LoginDataModel.cs +++ b/src/App/Models/Data/CipherData/LoginDataModel.cs @@ -22,6 +22,7 @@ namespace Bit.App.Models.Data Uris = response.Login.Uris?.Where(u => u != null).Select(u => new LoginUriDataModel(u)); Username = response.Login.Username; Password = response.Login.Password; + PasswordRevisionDate = response.Login.PasswordRevisionDate; Totp = response.Login.Totp; } @@ -33,6 +34,7 @@ namespace Bit.App.Models.Data public IEnumerable Uris { get; set; } public string Username { get; set; } public string Password { get; set; } + public DateTime? PasswordRevisionDate { get; set; } public string Totp { get; set; } } } diff --git a/src/App/Models/Data/CipherData/PasswordHistoryDataModel.cs b/src/App/Models/Data/CipherData/PasswordHistoryDataModel.cs new file mode 100644 index 000000000..0ac08bd0d --- /dev/null +++ b/src/App/Models/Data/CipherData/PasswordHistoryDataModel.cs @@ -0,0 +1,18 @@ +using Bit.App.Models.Api; + +namespace Bit.App.Models.Data +{ + public class PasswordHistoryDataModel + { + public PasswordHistoryDataModel() { } + + public PasswordHistoryDataModel(PasswordHistoryResponse h) + { + Password = h.Password; + LastUsedDate = h.LastUsedDate; + } + + public string Password { get; set; } + public System.DateTime LastUsedDate { get; set; } + } +} diff --git a/src/App/Models/Login.cs b/src/App/Models/Login.cs index 4f84d344c..f3f164345 100644 --- a/src/App/Models/Login.cs +++ b/src/App/Models/Login.cs @@ -28,6 +28,7 @@ namespace Bit.App.Models Username = deserializedData.Username != null ? new CipherString(deserializedData.Username) : null; Password = deserializedData.Password != null ? new CipherString(deserializedData.Password) : null; + PasswordRevisionDate = deserializedData.PasswordRevisionDate; Totp = deserializedData.Totp != null ? new CipherString(deserializedData.Totp) : null; Uris = deserializedData.Uris?.Select(u => new LoginUri(u)); } @@ -35,6 +36,7 @@ namespace Bit.App.Models public IEnumerable Uris { get; set; } public CipherString Username { get; set; } public CipherString Password { get; set; } + public DateTime? PasswordRevisionDate { get; set; } public CipherString Totp { get; set; } } } diff --git a/src/App/Models/Page/VaultViewCipherPageModel.cs b/src/App/Models/Page/VaultViewCipherPageModel.cs index 0fec0ad78..6cf4f2776 100644 --- a/src/App/Models/Page/VaultViewCipherPageModel.cs +++ b/src/App/Models/Page/VaultViewCipherPageModel.cs @@ -11,7 +11,7 @@ namespace Bit.App.Models.Page { private const string MaskedPasswordString = "••••••••"; - private string _name, _notes; + private string _name, _notes, _reivisonDate, _passwordReivisonDate; private List _attachments; private List _fields; private List _loginUris; @@ -44,6 +44,28 @@ namespace Bit.App.Models.Page } } + public string RevisionDate + { + get => _reivisonDate; + set + { + _reivisonDate = value; + PropertyChanged(this, new PropertyChangedEventArgs(nameof(RevisionDate))); + } + } + + public string PasswordRevisionDate + { + get => _passwordReivisonDate; + set + { + _passwordReivisonDate = value; + PropertyChanged(this, new PropertyChangedEventArgs(nameof(PasswordRevisionDate))); + PropertyChanged(this, new PropertyChangedEventArgs(nameof(ShowPasswordRevisionDate))); + } + } + public bool ShowPasswordRevisionDate => !string.IsNullOrWhiteSpace(PasswordRevisionDate); + public string Notes { get => _notes; @@ -523,6 +545,20 @@ namespace Bit.App.Models.Page { Name = cipher.Name?.Decrypt(cipher.OrganizationId); Notes = cipher.Notes?.Decrypt(cipher.OrganizationId); + var revisionDate = DateTime.SpecifyKind(cipher.RevisionDate, DateTimeKind.Utc).ToLocalTime(); + RevisionDate = revisionDate.ToShortDateString() + " " + revisionDate.ToShortTimeString(); + + if(cipher.PasswordRevisionDisplayDate.HasValue) + { + var passwordRevisionDate = DateTime.SpecifyKind( + cipher.PasswordRevisionDisplayDate.Value, DateTimeKind.Utc).ToLocalTime(); + PasswordRevisionDate = passwordRevisionDate.ToShortDateString() + " " + + passwordRevisionDate.ToShortTimeString(); + } + else + { + PasswordRevisionDate = null; + } if(cipher.Attachments != null) { @@ -693,7 +729,7 @@ namespace Bit.App.Models.Page } } public string Label => IsWebsite ? AppResources.Website : AppResources.URI; - public bool IsWebsite => Value == null ? false : + public bool IsWebsite => Value == null ? false : Value.StartsWith("http://") || Value.StartsWith("https://"); public bool IsApp => Value == null ? false : Value.StartsWith(Constants.AndroidAppProtocol); } diff --git a/src/App/Models/PasswordHistory.cs b/src/App/Models/PasswordHistory.cs new file mode 100644 index 000000000..7a3b6f9cc --- /dev/null +++ b/src/App/Models/PasswordHistory.cs @@ -0,0 +1,18 @@ +using Bit.App.Models.Data; + +namespace Bit.App.Models +{ + public class PasswordHistory + { + public PasswordHistory() { } + + public PasswordHistory(PasswordHistoryDataModel model) + { + Password = model.Password != null ? new CipherString(model.Password) : null; + LastUsedDate = model.LastUsedDate; + } + + public CipherString Password { get; set; } + public System.DateTime LastUsedDate { get; set; } + } +} diff --git a/src/App/Pages/Vault/VaultViewCipherPage.cs b/src/App/Pages/Vault/VaultViewCipherPage.cs index ab9f0787f..15e8fb8bf 100644 --- a/src/App/Pages/Vault/VaultViewCipherPage.cs +++ b/src/App/Pages/Vault/VaultViewCipherPage.cs @@ -43,6 +43,7 @@ namespace Bit.App.Pages private TableSection NotesSection { get; set; } private TableSection AttachmentsSection { get; set; } private TableSection FieldsSection { get; set; } + public TableSection OtherSection { get; set; } public LabeledValueCell NotesCell { get; set; } private EditCipherToolBarItem EditItem { get; set; } public List FieldsCells { get; set; } @@ -51,6 +52,7 @@ namespace Bit.App.Pages // Login public LabeledValueCell LoginUsernameCell { get; set; } public LabeledValueCell LoginPasswordCell { get; set; } + public LabeledValueCell LoginPasswordRevisionDateCell { get; set; } public LabeledValueCell LoginTotpCodeCell { get; set; } // Card @@ -111,6 +113,10 @@ namespace Bit.App.Pages NotesCell.Value.SetBinding(Label.TextProperty, nameof(VaultViewCipherPageModel.Notes)); NotesCell.Value.LineBreakMode = LineBreakMode.WordWrap; + var revisionDateCell = new LabeledValueCell(AppResources.DateUpdated); + revisionDateCell.Value.SetBinding(Label.TextProperty, nameof(VaultViewCipherPageModel.RevisionDate)); + revisionDateCell.Value.LineBreakMode = LineBreakMode.WordWrap; + switch(_type) { case CipherType.Login: @@ -152,6 +158,12 @@ namespace Bit.App.Pages nameof(VaultViewCipherPageModel.LoginTotpColor)); LoginTotpCodeCell.Value.FontFamily = Helpers.OnPlatform(iOS: "Menlo-Regular", Android: "monospace", Windows: "Courier"); + + // Password Revision Date + LoginPasswordRevisionDateCell = new LabeledValueCell(AppResources.DatePasswordUpdated); + LoginPasswordRevisionDateCell.Value.SetBinding(Label.TextProperty, + nameof(VaultViewCipherPageModel.PasswordRevisionDate)); + LoginPasswordRevisionDateCell.Value.LineBreakMode = LineBreakMode.WordWrap; break; case CipherType.Card: CardNameCell = new LabeledValueCell(AppResources.CardholderName); @@ -235,6 +247,11 @@ namespace Bit.App.Pages NotesCell }; + OtherSection = new TableSection(AppResources.Other) + { + revisionDateCell + }; + Table = new ExtendedTableView { Intent = TableIntent.Settings, @@ -363,10 +380,26 @@ namespace Bit.App.Pages Table.Root.Add(AttachmentsSection); } + // Other + if(Table.Root.Contains(OtherSection)) + { + Table.Root.Remove(OtherSection); + } + Table.Root.Add(OtherSection); + // Various types switch(cipher.Type) { case CipherType.Login: + if(OtherSection.Contains(LoginPasswordRevisionDateCell)) + { + OtherSection.Remove(LoginPasswordRevisionDateCell); + } + if(Model.ShowPasswordRevisionDate) + { + OtherSection.Add(LoginPasswordRevisionDateCell); + } + AddSectionCell(LoginUsernameCell, Model.ShowLoginUsername); AddSectionCell(LoginPasswordCell, Model.ShowLoginPassword); diff --git a/src/App/Resources/AppResources.Designer.cs b/src/App/Resources/AppResources.Designer.cs index 3783bf0ed..42b0e164a 100644 --- a/src/App/Resources/AppResources.Designer.cs +++ b/src/App/Resources/AppResources.Designer.cs @@ -942,6 +942,24 @@ namespace Bit.App.Resources { } } + /// + /// Looks up a localized string similar to Password Updated. + /// + public static string DatePasswordUpdated { + get { + return ResourceManager.GetString("DatePasswordUpdated", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Updated. + /// + public static string DateUpdated { + get { + return ResourceManager.GetString("DateUpdated", resourceCulture); + } + } + /// /// Looks up a localized string similar to December. /// diff --git a/src/App/Resources/AppResources.resx b/src/App/Resources/AppResources.resx index 70edde364..916264ae4 100644 --- a/src/App/Resources/AppResources.resx +++ b/src/App/Resources/AppResources.resx @@ -887,7 +887,7 @@ YubiKey Security Key - "YubiKey NEO" is the product name and should not be translated. + "YubiKey" is the product name and should not be translated. Add New Attachment @@ -1305,4 +1305,12 @@ The accessibility service may be helpful to use when apps do not support the standard auto-fill service. + + Password Updated + ex. Date this password was updated + + + Updated + ex. Date this item was updated + \ No newline at end of file