diff --git a/src/App/App.csproj b/src/App/App.csproj
index 131c2d56f..c90ae7fee 100644
--- a/src/App/App.csproj
+++ b/src/App/App.csproj
@@ -87,6 +87,7 @@
+
@@ -98,7 +99,8 @@
-
+
+
@@ -123,6 +125,7 @@
+
diff --git a/src/App/Enums/CipherType.cs b/src/App/Enums/CipherType.cs
index 4cef54436..41950bc79 100644
--- a/src/App/Enums/CipherType.cs
+++ b/src/App/Enums/CipherType.cs
@@ -4,6 +4,8 @@
{
// Folder deprecated
//Folder = 0,
- Login = 1
+ Login = 1,
+ SecureNote = 2,
+ Card = 3
}
}
diff --git a/src/App/Enums/FieldType.cs b/src/App/Enums/FieldType.cs
new file mode 100644
index 000000000..50d9251ee
--- /dev/null
+++ b/src/App/Enums/FieldType.cs
@@ -0,0 +1,9 @@
+namespace Bit.App.Enums
+{
+ public enum FieldType : byte
+ {
+ Text = 0,
+ Hidden = 1,
+ Boolean = 2
+ }
+}
diff --git a/src/App/Models/Api/CipherDataModel.cs b/src/App/Models/Api/CipherDataModel.cs
new file mode 100644
index 000000000..81dbf8d6c
--- /dev/null
+++ b/src/App/Models/Api/CipherDataModel.cs
@@ -0,0 +1,11 @@
+using System.Collections.Generic;
+
+namespace Bit.App.Models.Api
+{
+ public abstract class CipherDataModel
+ {
+ public string Name { get; set; }
+ public string Notes { get; set; }
+ public IEnumerable Fields { get; set; }
+ }
+}
diff --git a/src/App/Models/Api/FieldDataModel.cs b/src/App/Models/Api/FieldDataModel.cs
new file mode 100644
index 000000000..1ee190a6c
--- /dev/null
+++ b/src/App/Models/Api/FieldDataModel.cs
@@ -0,0 +1,11 @@
+using Bit.App.Enums;
+
+namespace Bit.App.Models.Api
+{
+ public class FieldDataModel
+ {
+ public FieldType Type { get; set; }
+ public string Name { get; set; }
+ public string Value { get; set; }
+ }
+}
diff --git a/src/App/Models/Api/FolderDataModel.cs b/src/App/Models/Api/FolderDataModel.cs
deleted file mode 100644
index a5d7d7b6e..000000000
--- a/src/App/Models/Api/FolderDataModel.cs
+++ /dev/null
@@ -1,7 +0,0 @@
-namespace Bit.App.Models.Api
-{
- public class FolderDataModel
- {
- public string Name { get; set; }
- }
-}
diff --git a/src/App/Models/Api/LoginDataModel.cs b/src/App/Models/Api/LoginDataModel.cs
index 2791257b1..637822256 100644
--- a/src/App/Models/Api/LoginDataModel.cs
+++ b/src/App/Models/Api/LoginDataModel.cs
@@ -1,12 +1,10 @@
namespace Bit.App.Models.Api
{
- public class LoginDataModel
+ public class LoginDataModel : CipherDataModel
{
- public string Name { get; set; }
public string Uri { get; set; }
public string Username { get; set; }
public string Password { get; set; }
- public string Notes { get; set; }
public string Totp { get; set; }
}
}
diff --git a/src/App/Models/Data/LoginData.cs b/src/App/Models/Data/LoginData.cs
index 0896e588a..9bd763000 100644
--- a/src/App/Models/Data/LoginData.cs
+++ b/src/App/Models/Data/LoginData.cs
@@ -2,6 +2,8 @@
using SQLite;
using Bit.App.Abstractions;
using Bit.App.Models.Api;
+using Newtonsoft.Json;
+using System.Linq;
namespace Bit.App.Models.Data
{
@@ -11,23 +13,6 @@ namespace Bit.App.Models.Data
public LoginData()
{ }
- public LoginData(Login login, string userId)
- {
- Id = login.Id;
- FolderId = login.FolderId;
- UserId = userId;
- OrganizationId = login.OrganizationId;
- Name = login.Name?.EncryptedString;
- Uri = login.Uri?.EncryptedString;
- Username = login.Username?.EncryptedString;
- Password = login.Password?.EncryptedString;
- Notes = login.Notes?.EncryptedString;
- Totp = login?.Notes?.EncryptedString;
- Favorite = login.Favorite;
- Edit = login.Edit;
- OrganizationUseTotp = login.OrganizationUseTotp;
- }
-
public LoginData(CipherResponse cipher, string userId)
{
if(cipher.Type != Enums.CipherType.Login)
@@ -51,6 +36,15 @@ namespace Bit.App.Models.Data
Edit = cipher.Edit;
OrganizationUseTotp = cipher.OrganizationUseTotp;
RevisionDateTime = cipher.RevisionDate;
+
+ if(data.Fields != null && data.Fields.Any())
+ {
+ try
+ {
+ Fields = JsonConvert.SerializeObject(data.Fields);
+ }
+ catch(JsonSerializationException) { }
+ }
}
[PrimaryKey]
@@ -65,6 +59,7 @@ namespace Bit.App.Models.Data
public string Password { get; set; }
public string Notes { get; set; }
public string Totp { get; set; }
+ public string Fields { get; set; }
public bool Favorite { get; set; }
public bool Edit { get; set; }
public bool OrganizationUseTotp { get; set; }
diff --git a/src/App/Models/Field.cs b/src/App/Models/Field.cs
new file mode 100644
index 000000000..c0df3167e
--- /dev/null
+++ b/src/App/Models/Field.cs
@@ -0,0 +1,19 @@
+using Bit.App.Enums;
+using Bit.App.Models.Api;
+
+namespace Bit.App.Models
+{
+ public class Field
+ {
+ public Field(FieldDataModel model)
+ {
+ Type = model.Type;
+ Name = new CipherString(model.Name);
+ Value = new CipherString(model.Value);
+ }
+
+ public FieldType Type { get; set; }
+ public CipherString Name { get; set; }
+ public CipherString Value { get; set; }
+ }
+}
diff --git a/src/App/Models/Login.cs b/src/App/Models/Login.cs
index de624a105..690e17616 100644
--- a/src/App/Models/Login.cs
+++ b/src/App/Models/Login.cs
@@ -1,5 +1,6 @@
using Bit.App.Models.Api;
using Bit.App.Models.Data;
+using Newtonsoft.Json;
using System.Collections.Generic;
using System.Linq;
@@ -26,6 +27,16 @@ namespace Bit.App.Models
Edit = data.Edit;
OrganizationUseTotp = data.OrganizationUseTotp;
Attachments = attachments?.Select(a => new Attachment(a));
+
+ if(!string.IsNullOrWhiteSpace(data.Fields))
+ {
+ try
+ {
+ var fieldModels = JsonConvert.DeserializeObject>(data.Fields);
+ Fields = fieldModels?.Select(f => new Field(f));
+ }
+ catch(JsonSerializationException) { }
+ }
}
public string Id { get; set; }
@@ -38,14 +49,10 @@ namespace Bit.App.Models
public CipherString Password { get; set; }
public CipherString Notes { get; set; }
public CipherString Totp { get; set; }
+ public IEnumerable Fields { get; set; }
public bool Favorite { get; set; }
public bool Edit { get; set; }
public bool OrganizationUseTotp { get; set; }
public IEnumerable Attachments { get; set; }
-
- public LoginData ToLoginData(string userId)
- {
- return new LoginData(this, userId);
- }
}
}
diff --git a/src/App/Models/Page/VaultViewLoginPageModel.cs b/src/App/Models/Page/VaultViewLoginPageModel.cs
index 848bde56f..bc3261568 100644
--- a/src/App/Models/Page/VaultViewLoginPageModel.cs
+++ b/src/App/Models/Page/VaultViewLoginPageModel.cs
@@ -3,6 +3,7 @@ using System.ComponentModel;
using Bit.App.Resources;
using Xamarin.Forms;
using System.Collections.Generic;
+using Bit.App.Enums;
namespace Bit.App.Models.Page
{
@@ -17,6 +18,7 @@ namespace Bit.App.Models.Page
private int _totpSec = 30;
private bool _revealPassword;
private List _attachments;
+ private List _fields;
public VaultViewLoginPageModel() { }
@@ -144,12 +146,10 @@ namespace Bit.App.Models.Page
_revealPassword = value;
PropertyChanged(this, new PropertyChangedEventArgs(nameof(RevealPassword)));
PropertyChanged(this, new PropertyChangedEventArgs(nameof(MaskedPassword)));
- PropertyChanged(this, new PropertyChangedEventArgs(nameof(ShowHideText)));
PropertyChanged(this, new PropertyChangedEventArgs(nameof(ShowHideImage)));
}
}
public string MaskedPassword => RevealPassword ? Password : Password == null ? null : new string('●', Password.Length);
- public string ShowHideText => RevealPassword ? AppResources.Hide : AppResources.Show;
public ImageSource ShowHideImage => RevealPassword ? ImageSource.FromFile("eye_slash") : ImageSource.FromFile("eye");
public string TotpCode
@@ -189,6 +189,18 @@ namespace Bit.App.Models.Page
}
public bool ShowAttachments => (Attachments?.Count ?? 0) > 0;
+ public List Fields
+ {
+ get { return _fields; }
+ set
+ {
+ _fields = value;
+ PropertyChanged(this, new PropertyChangedEventArgs(nameof(Fields)));
+ PropertyChanged(this, new PropertyChangedEventArgs(nameof(ShowFields)));
+ }
+ }
+ public bool ShowFields => (Fields?.Count ?? 0) > 0;
+
public void Update(Login login)
{
Name = login.Name?.Decrypt(login.OrganizationId);
@@ -217,6 +229,25 @@ namespace Bit.App.Models.Page
{
login.Attachments = null;
}
+
+ if(login.Fields != null)
+ {
+ var fields = new List();
+ foreach(var field in login.Fields)
+ {
+ fields.Add(new Field
+ {
+ Name = field.Name?.Decrypt(login.OrganizationId),
+ Value = field.Value?.Decrypt(login.OrganizationId),
+ Type = field.Type
+ });
+ }
+ Fields = fields;
+ }
+ else
+ {
+ login.Fields = null;
+ }
}
public class Attachment
@@ -227,5 +258,26 @@ namespace Bit.App.Models.Page
public long Size { get; set; }
public string Url { get; set; }
}
+
+ public class Field
+ {
+ private string _maskedValue;
+
+ public string Name { get; set; }
+ public string Value { get; set; }
+ public string MaskedValue
+ {
+ get
+ {
+ if(_maskedValue == null && Value != null)
+ {
+ _maskedValue = new string('●', Value.Length);
+ }
+
+ return _maskedValue;
+ }
+ }
+ public FieldType Type { get; set; }
+ }
}
}
diff --git a/src/App/Pages/Vault/VaultViewLoginPage.cs b/src/App/Pages/Vault/VaultViewLoginPage.cs
index 263c68f6e..8384c70d1 100644
--- a/src/App/Pages/Vault/VaultViewLoginPage.cs
+++ b/src/App/Pages/Vault/VaultViewLoginPage.cs
@@ -11,6 +11,7 @@ using Bit.App.Utilities;
using System.Collections.Generic;
using Bit.App.Models;
using System.Linq;
+using Bit.App.Enums;
namespace Bit.App.Pages
{
@@ -39,12 +40,14 @@ namespace Bit.App.Pages
private TableSection LoginInformationSection { get; set; }
private TableSection NotesSection { get; set; }
private TableSection AttachmentsSection { get; set; }
+ private TableSection FieldsSection { get; set; }
public LabeledValueCell UsernameCell { get; set; }
public LabeledValueCell PasswordCell { get; set; }
public LabeledValueCell UriCell { get; set; }
public LabeledValueCell NotesCell { get; set; }
public LabeledValueCell TotpCodeCell { get; set; }
private EditLoginToolBarItem EditItem { get; set; }
+ public List FieldsCells { get; set; }
public List AttachmentCells { get; set; }
private void Init()
@@ -255,6 +258,53 @@ namespace Bit.App.Pages
Table.Root.Add(AttachmentsSection);
}
+ if(Table.Root.Contains(FieldsSection))
+ {
+ Table.Root.Remove(FieldsSection);
+ }
+ if(Model.ShowFields)
+ {
+ FieldsSection = new TableSection(AppResources.CustomFields);
+ foreach(var field in Model.Fields)
+ {
+ LabeledValueCell fieldCell;
+ ExtendedButton copyButton = null;
+ switch(field.Type)
+ {
+ case FieldType.Text:
+ fieldCell = new LabeledValueCell(field.Name, field.Value, AppResources.Copy);
+ copyButton = fieldCell.Button1;
+ break;
+ case FieldType.Hidden:
+ fieldCell = new LabeledValueCell(field.Name, field.MaskedValue, string.Empty, AppResources.Copy);
+ copyButton = fieldCell.Button2;
+ fieldCell.Value.FontFamily =
+ Helpers.OnPlatform(iOS: "Menlo-Regular", Android: "monospace", WinPhone: "Courier");
+ if(Device.RuntimePlatform == Device.iOS)
+ {
+ fieldCell.Button1.Margin = new Thickness(10, 0);
+ }
+ // TODO: show/hide image toggle
+ break;
+ case FieldType.Boolean:
+ fieldCell = new LabeledValueCell(field.Name, field.Value == "true" ? "✓" : "-");
+ break;
+ default:
+ continue;
+ }
+
+ fieldCell.Value.LineBreakMode = LineBreakMode.WordWrap;
+ if(copyButton != null)
+ {
+ copyButton.Command = new Command(() => Copy(field.Value, field.Name));
+ copyButton.WidthRequest = 59;
+ }
+
+ FieldsSection.Add(fieldCell);
+ }
+ Table.Root.Add(FieldsSection);
+ }
+
base.OnAppearing();
}
@@ -322,8 +372,8 @@ namespace Bit.App.Pages
private void Copy(string copyText, string alertLabel)
{
- _deviceActionService.CopyToClipboard(copyText);
- _userDialogs.Toast(string.Format(AppResources.ValueHasBeenCopied, alertLabel));
+ _deviceActionService.CopyToClipboard(copyText);
+ _userDialogs.Toast(string.Format(AppResources.ValueHasBeenCopied, alertLabel));
}
private void TotpTick(string totpKey)
diff --git a/src/App/Resources/AppResources.Designer.cs b/src/App/Resources/AppResources.Designer.cs
index 69b152f38..8bec4193f 100644
--- a/src/App/Resources/AppResources.Designer.cs
+++ b/src/App/Resources/AppResources.Designer.cs
@@ -718,6 +718,15 @@ namespace Bit.App.Resources {
}
}
+ ///
+ /// Looks up a localized string similar to Custom Fields.
+ ///
+ public static string CustomFields {
+ get {
+ return ResourceManager.GetString("CustomFields", resourceCulture);
+ }
+ }
+
///
/// Looks up a localized string similar to Delete.
///
diff --git a/src/App/Resources/AppResources.resx b/src/App/Resources/AppResources.resx
index bbd62447d..2eb540bae 100644
--- a/src/App/Resources/AppResources.resx
+++ b/src/App/Resources/AppResources.resx
@@ -1030,4 +1030,7 @@
Tap this notification to view logins from your vault.
+
+ Custom Fields
+
\ No newline at end of file