sync and display custom fields for login

This commit is contained in:
Kyle Spearrin 2017-09-22 17:32:20 -04:00
parent cc12ae7712
commit e126cbf644
14 changed files with 200 additions and 38 deletions

View File

@ -87,6 +87,7 @@
<Compile Include="Controls\PinControl.cs" /> <Compile Include="Controls\PinControl.cs" />
<Compile Include="Controls\VaultAttachmentsViewCell.cs" /> <Compile Include="Controls\VaultAttachmentsViewCell.cs" />
<Compile Include="Controls\VaultListViewCell.cs" /> <Compile Include="Controls\VaultListViewCell.cs" />
<Compile Include="Enums\FieldType.cs" />
<Compile Include="Enums\TwoFactorProviderType.cs" /> <Compile Include="Enums\TwoFactorProviderType.cs" />
<Compile Include="Enums\EncryptionType.cs" /> <Compile Include="Enums\EncryptionType.cs" />
<Compile Include="Enums\OrganizationUserType.cs" /> <Compile Include="Enums\OrganizationUserType.cs" />
@ -98,7 +99,8 @@
<Compile Include="Abstractions\Services\ILocalizeService.cs" /> <Compile Include="Abstractions\Services\ILocalizeService.cs" />
<Compile Include="Models\Api\ApiError.cs" /> <Compile Include="Models\Api\ApiError.cs" />
<Compile Include="Models\Api\ApiResult.cs" /> <Compile Include="Models\Api\ApiResult.cs" />
<Compile Include="Models\Api\FolderDataModel.cs" /> <Compile Include="Models\Api\CipherDataModel.cs" />
<Compile Include="Models\Api\FieldDataModel.cs" />
<Compile Include="Models\Api\Request\DeviceTokenRequest.cs" /> <Compile Include="Models\Api\Request\DeviceTokenRequest.cs" />
<Compile Include="Models\Api\Request\FolderRequest.cs" /> <Compile Include="Models\Api\Request\FolderRequest.cs" />
<Compile Include="Models\Api\Request\DeviceRequest.cs" /> <Compile Include="Models\Api\Request\DeviceRequest.cs" />
@ -123,6 +125,7 @@
<Compile Include="Models\CipherString.cs" /> <Compile Include="Models\CipherString.cs" />
<Compile Include="Models\Data\AttachmentData.cs" /> <Compile Include="Models\Data\AttachmentData.cs" />
<Compile Include="Models\Attachment.cs" /> <Compile Include="Models\Attachment.cs" />
<Compile Include="Models\Field.cs" />
<Compile Include="Models\Page\VaultAttachmentsPageModel.cs" /> <Compile Include="Models\Page\VaultAttachmentsPageModel.cs" />
<Compile Include="Models\SymmetricCryptoKey.cs" /> <Compile Include="Models\SymmetricCryptoKey.cs" />
<Compile Include="Models\Data\SettingsData.cs" /> <Compile Include="Models\Data\SettingsData.cs" />

View File

@ -4,6 +4,8 @@
{ {
// Folder deprecated // Folder deprecated
//Folder = 0, //Folder = 0,
Login = 1 Login = 1,
SecureNote = 2,
Card = 3
} }
} }

View File

@ -0,0 +1,9 @@
namespace Bit.App.Enums
{
public enum FieldType : byte
{
Text = 0,
Hidden = 1,
Boolean = 2
}
}

View File

@ -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<FieldDataModel> Fields { get; set; }
}
}

View File

@ -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; }
}
}

View File

@ -1,7 +0,0 @@
namespace Bit.App.Models.Api
{
public class FolderDataModel
{
public string Name { get; set; }
}
}

View File

@ -1,12 +1,10 @@
namespace Bit.App.Models.Api namespace Bit.App.Models.Api
{ {
public class LoginDataModel public class LoginDataModel : CipherDataModel
{ {
public string Name { get; set; }
public string Uri { get; set; } public string Uri { get; set; }
public string Username { get; set; } public string Username { get; set; }
public string Password { get; set; } public string Password { get; set; }
public string Notes { get; set; }
public string Totp { get; set; } public string Totp { get; set; }
} }
} }

View File

@ -2,6 +2,8 @@
using SQLite; using SQLite;
using Bit.App.Abstractions; using Bit.App.Abstractions;
using Bit.App.Models.Api; using Bit.App.Models.Api;
using Newtonsoft.Json;
using System.Linq;
namespace Bit.App.Models.Data namespace Bit.App.Models.Data
{ {
@ -11,23 +13,6 @@ namespace Bit.App.Models.Data
public LoginData() 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) public LoginData(CipherResponse cipher, string userId)
{ {
if(cipher.Type != Enums.CipherType.Login) if(cipher.Type != Enums.CipherType.Login)
@ -51,6 +36,15 @@ namespace Bit.App.Models.Data
Edit = cipher.Edit; Edit = cipher.Edit;
OrganizationUseTotp = cipher.OrganizationUseTotp; OrganizationUseTotp = cipher.OrganizationUseTotp;
RevisionDateTime = cipher.RevisionDate; RevisionDateTime = cipher.RevisionDate;
if(data.Fields != null && data.Fields.Any())
{
try
{
Fields = JsonConvert.SerializeObject(data.Fields);
}
catch(JsonSerializationException) { }
}
} }
[PrimaryKey] [PrimaryKey]
@ -65,6 +59,7 @@ namespace Bit.App.Models.Data
public string Password { get; set; } public string Password { get; set; }
public string Notes { get; set; } public string Notes { get; set; }
public string Totp { get; set; } public string Totp { get; set; }
public string Fields { get; set; }
public bool Favorite { get; set; } public bool Favorite { get; set; }
public bool Edit { get; set; } public bool Edit { get; set; }
public bool OrganizationUseTotp { get; set; } public bool OrganizationUseTotp { get; set; }

19
src/App/Models/Field.cs Normal file
View File

@ -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; }
}
}

View File

@ -1,5 +1,6 @@
using Bit.App.Models.Api; using Bit.App.Models.Api;
using Bit.App.Models.Data; using Bit.App.Models.Data;
using Newtonsoft.Json;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
@ -26,6 +27,16 @@ namespace Bit.App.Models
Edit = data.Edit; Edit = data.Edit;
OrganizationUseTotp = data.OrganizationUseTotp; OrganizationUseTotp = data.OrganizationUseTotp;
Attachments = attachments?.Select(a => new Attachment(a)); Attachments = attachments?.Select(a => new Attachment(a));
if(!string.IsNullOrWhiteSpace(data.Fields))
{
try
{
var fieldModels = JsonConvert.DeserializeObject<IEnumerable<FieldDataModel>>(data.Fields);
Fields = fieldModels?.Select(f => new Field(f));
}
catch(JsonSerializationException) { }
}
} }
public string Id { get; set; } public string Id { get; set; }
@ -38,14 +49,10 @@ namespace Bit.App.Models
public CipherString Password { get; set; } public CipherString Password { get; set; }
public CipherString Notes { get; set; } public CipherString Notes { get; set; }
public CipherString Totp { get; set; } public CipherString Totp { get; set; }
public IEnumerable<Field> Fields { get; set; }
public bool Favorite { get; set; } public bool Favorite { get; set; }
public bool Edit { get; set; } public bool Edit { get; set; }
public bool OrganizationUseTotp { get; set; } public bool OrganizationUseTotp { get; set; }
public IEnumerable<Attachment> Attachments { get; set; } public IEnumerable<Attachment> Attachments { get; set; }
public LoginData ToLoginData(string userId)
{
return new LoginData(this, userId);
}
} }
} }

View File

@ -3,6 +3,7 @@ using System.ComponentModel;
using Bit.App.Resources; using Bit.App.Resources;
using Xamarin.Forms; using Xamarin.Forms;
using System.Collections.Generic; using System.Collections.Generic;
using Bit.App.Enums;
namespace Bit.App.Models.Page namespace Bit.App.Models.Page
{ {
@ -17,6 +18,7 @@ namespace Bit.App.Models.Page
private int _totpSec = 30; private int _totpSec = 30;
private bool _revealPassword; private bool _revealPassword;
private List<Attachment> _attachments; private List<Attachment> _attachments;
private List<Field> _fields;
public VaultViewLoginPageModel() { } public VaultViewLoginPageModel() { }
@ -144,12 +146,10 @@ namespace Bit.App.Models.Page
_revealPassword = value; _revealPassword = value;
PropertyChanged(this, new PropertyChangedEventArgs(nameof(RevealPassword))); PropertyChanged(this, new PropertyChangedEventArgs(nameof(RevealPassword)));
PropertyChanged(this, new PropertyChangedEventArgs(nameof(MaskedPassword))); PropertyChanged(this, new PropertyChangedEventArgs(nameof(MaskedPassword)));
PropertyChanged(this, new PropertyChangedEventArgs(nameof(ShowHideText)));
PropertyChanged(this, new PropertyChangedEventArgs(nameof(ShowHideImage))); PropertyChanged(this, new PropertyChangedEventArgs(nameof(ShowHideImage)));
} }
} }
public string MaskedPassword => RevealPassword ? Password : Password == null ? null : new string('●', Password.Length); 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 ImageSource ShowHideImage => RevealPassword ? ImageSource.FromFile("eye_slash") : ImageSource.FromFile("eye");
public string TotpCode public string TotpCode
@ -189,6 +189,18 @@ namespace Bit.App.Models.Page
} }
public bool ShowAttachments => (Attachments?.Count ?? 0) > 0; public bool ShowAttachments => (Attachments?.Count ?? 0) > 0;
public List<Field> 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) public void Update(Login login)
{ {
Name = login.Name?.Decrypt(login.OrganizationId); Name = login.Name?.Decrypt(login.OrganizationId);
@ -217,6 +229,25 @@ namespace Bit.App.Models.Page
{ {
login.Attachments = null; login.Attachments = null;
} }
if(login.Fields != null)
{
var fields = new List<Field>();
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 public class Attachment
@ -227,5 +258,26 @@ namespace Bit.App.Models.Page
public long Size { get; set; } public long Size { get; set; }
public string Url { 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; }
}
} }
} }

View File

@ -11,6 +11,7 @@ using Bit.App.Utilities;
using System.Collections.Generic; using System.Collections.Generic;
using Bit.App.Models; using Bit.App.Models;
using System.Linq; using System.Linq;
using Bit.App.Enums;
namespace Bit.App.Pages namespace Bit.App.Pages
{ {
@ -39,12 +40,14 @@ namespace Bit.App.Pages
private TableSection LoginInformationSection { get; set; } private TableSection LoginInformationSection { get; set; }
private TableSection NotesSection { get; set; } private TableSection NotesSection { get; set; }
private TableSection AttachmentsSection { get; set; } private TableSection AttachmentsSection { get; set; }
private TableSection FieldsSection { get; set; }
public LabeledValueCell UsernameCell { get; set; } public LabeledValueCell UsernameCell { get; set; }
public LabeledValueCell PasswordCell { get; set; } public LabeledValueCell PasswordCell { get; set; }
public LabeledValueCell UriCell { get; set; } public LabeledValueCell UriCell { get; set; }
public LabeledValueCell NotesCell { get; set; } public LabeledValueCell NotesCell { get; set; }
public LabeledValueCell TotpCodeCell { get; set; } public LabeledValueCell TotpCodeCell { get; set; }
private EditLoginToolBarItem EditItem { get; set; } private EditLoginToolBarItem EditItem { get; set; }
public List<LabeledValueCell> FieldsCells { get; set; }
public List<AttachmentViewCell> AttachmentCells { get; set; } public List<AttachmentViewCell> AttachmentCells { get; set; }
private void Init() private void Init()
@ -255,6 +258,53 @@ namespace Bit.App.Pages
Table.Root.Add(AttachmentsSection); 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(); base.OnAppearing();
} }
@ -322,8 +372,8 @@ namespace Bit.App.Pages
private void Copy(string copyText, string alertLabel) private void Copy(string copyText, string alertLabel)
{ {
_deviceActionService.CopyToClipboard(copyText); _deviceActionService.CopyToClipboard(copyText);
_userDialogs.Toast(string.Format(AppResources.ValueHasBeenCopied, alertLabel)); _userDialogs.Toast(string.Format(AppResources.ValueHasBeenCopied, alertLabel));
} }
private void TotpTick(string totpKey) private void TotpTick(string totpKey)

View File

@ -718,6 +718,15 @@ namespace Bit.App.Resources {
} }
} }
/// <summary>
/// Looks up a localized string similar to Custom Fields.
/// </summary>
public static string CustomFields {
get {
return ResourceManager.GetString("CustomFields", resourceCulture);
}
}
/// <summary> /// <summary>
/// Looks up a localized string similar to Delete. /// Looks up a localized string similar to Delete.
/// </summary> /// </summary>

View File

@ -1030,4 +1030,7 @@
<data name="BitwardenAutofillServiceNotificationContentOld" xml:space="preserve"> <data name="BitwardenAutofillServiceNotificationContentOld" xml:space="preserve">
<value>Tap this notification to view logins from your vault.</value> <value>Tap this notification to view logins from your vault.</value>
</data> </data>
<data name="CustomFields" xml:space="preserve">
<value>Custom Fields</value>
</data>
</root> </root>