bitwarden-android/src/App/Pages/Vault/VaultViewCipherPage.cs

468 lines
17 KiB
C#
Raw Normal View History

2016-05-02 09:52:09 +03:00
using System;
2016-05-07 05:29:03 +03:00
using Acr.UserDialogs;
using Bit.App.Abstractions;
using Bit.App.Controls;
using Bit.App.Models.Page;
using Bit.App.Resources;
2016-05-02 09:52:09 +03:00
using Xamarin.Forms;
2016-05-07 05:29:03 +03:00
using XLabs.Ioc;
using System.Threading.Tasks;
2017-05-30 21:13:53 +03:00
using Bit.App.Utilities;
2017-07-13 17:51:45 +03:00
using System.Collections.Generic;
2017-07-13 19:08:48 +03:00
using Bit.App.Models;
2017-07-22 22:38:08 +03:00
using System.Linq;
using Bit.App.Enums;
2016-05-02 09:52:09 +03:00
2016-05-03 00:50:16 +03:00
namespace Bit.App.Pages
2016-05-02 09:52:09 +03:00
{
public class VaultViewCipherPage : ExtendedContentPage
2016-05-02 09:52:09 +03:00
{
2017-10-19 23:08:29 +03:00
private readonly string _cipherId;
private readonly ICipherService _cipherService;
2016-05-07 05:29:03 +03:00
private readonly IUserDialogs _userDialogs;
private readonly IDeviceActionService _deviceActionService;
2017-07-13 21:44:02 +03:00
private readonly ITokenService _tokenService;
private bool _pageDisappeared = true;
2016-05-02 09:52:09 +03:00
2017-10-19 23:08:29 +03:00
public VaultViewCipherPage(string cipherId)
2016-05-02 09:52:09 +03:00
{
2017-10-19 23:08:29 +03:00
_cipherId = cipherId;
_cipherService = Resolver.Resolve<ICipherService>();
2016-05-07 05:29:03 +03:00
_userDialogs = Resolver.Resolve<IUserDialogs>();
_deviceActionService = Resolver.Resolve<IDeviceActionService>();
2017-07-13 21:44:02 +03:00
_tokenService = Resolver.Resolve<ITokenService>();
2016-05-07 05:29:03 +03:00
Init();
}
2017-10-19 23:08:29 +03:00
private VaultViewCipherPageModel Model { get; set; } = new VaultViewCipherPageModel();
private ExtendedTableView Table { get; set; }
2017-10-19 23:08:29 +03:00
private TableSection ItemInformationSection { 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; }
2017-04-20 23:07:34 +03:00
public LabeledValueCell NotesCell { get; set; }
2017-07-13 21:44:02 +03:00
public LabeledValueCell TotpCodeCell { get; set; }
2017-10-19 23:08:29 +03:00
private EditCipherToolBarItem EditItem { get; set; }
public List<LabeledValueCell> FieldsCells { get; set; }
2017-07-13 17:51:45 +03:00
public List<AttachmentViewCell> AttachmentCells { get; set; }
private void Init()
2016-05-07 05:29:03 +03:00
{
2017-10-19 23:08:29 +03:00
EditItem = new EditCipherToolBarItem(this, _cipherId);
ToolbarItems.Add(EditItem);
if(Device.RuntimePlatform == Device.iOS || Device.RuntimePlatform == Device.Windows)
{
ToolbarItems.Add(new DismissModalToolBarItem(this));
}
2016-05-07 05:29:03 +03:00
2016-06-18 01:14:24 +03:00
// Name
var nameCell = new LabeledValueCell(AppResources.Name);
2017-10-19 23:08:29 +03:00
nameCell.Value.SetBinding(Label.TextProperty, nameof(VaultViewCipherPageModel.Name));
// Username
UsernameCell = new LabeledValueCell(AppResources.Username, button1Image: "clipboard.png");
2017-10-19 23:08:29 +03:00
UsernameCell.Value.SetBinding(Label.TextProperty, nameof(VaultViewCipherPageModel.Username));
UsernameCell.Button1.Command = new Command(() => Copy(Model.Username, AppResources.Username));
UsernameCell.Value.LineBreakMode = LineBreakMode.WordWrap;
2016-05-07 05:29:03 +03:00
// Password
PasswordCell = new LabeledValueCell(AppResources.Password, button1Image: string.Empty,
button2Image: "clipboard.png");
2017-10-19 23:08:29 +03:00
PasswordCell.Value.SetBinding(Label.TextProperty, nameof(VaultViewCipherPageModel.MaskedPassword));
PasswordCell.Button1.SetBinding(Button.ImageProperty, nameof(VaultViewCipherPageModel.ShowHideImage));
2017-05-30 21:13:53 +03:00
if(Device.RuntimePlatform == Device.iOS)
2016-06-18 01:14:24 +03:00
{
PasswordCell.Button1.Margin = new Thickness(10, 0);
}
PasswordCell.Button1.Command = new Command(() => Model.RevealPassword = !Model.RevealPassword);
PasswordCell.Button2.Command = new Command(() => Copy(Model.Password, AppResources.Password));
PasswordCell.Value.FontFamily = Helpers.OnPlatform(iOS: "Menlo-Regular", Android: "monospace", WinPhone: "Courier");
PasswordCell.Value.LineBreakMode = LineBreakMode.WordWrap;
2016-05-07 05:29:03 +03:00
// URI
UriCell = new LabeledValueCell(AppResources.Website, button1Image: "launch.png");
2017-10-19 23:08:29 +03:00
UriCell.Value.SetBinding(Label.TextProperty, nameof(VaultViewCipherPageModel.UriHost));
UriCell.Button1.SetBinding(IsVisibleProperty, nameof(VaultViewCipherPageModel.ShowLaunch));
2017-06-08 23:22:11 +03:00
UriCell.Button1.Command = new Command(() =>
{
if(Device.RuntimePlatform == Device.Android && Model.Uri.StartsWith("androidapp://"))
{
MessagingCenter.Send(Application.Current, "LaunchApp", Model.Uri);
}
else if(Model.Uri.StartsWith("http://") || Model.Uri.StartsWith("https://"))
{
Device.OpenUri(new Uri(Model.Uri));
}
});
2017-07-13 21:44:02 +03:00
// Totp
TotpCodeCell = new LabeledValueCell(AppResources.VerificationCodeTotp, button1Image: "clipboard.png", subText: "--");
2017-10-19 23:08:29 +03:00
TotpCodeCell.Value.SetBinding(Label.TextProperty, nameof(VaultViewCipherPageModel.TotpCodeFormatted));
TotpCodeCell.Value.SetBinding(Label.TextColorProperty, nameof(VaultViewCipherPageModel.TotpColor));
2017-07-13 21:44:02 +03:00
TotpCodeCell.Button1.Command = new Command(() => Copy(Model.TotpCode, AppResources.VerificationCodeTotp));
2017-10-19 23:08:29 +03:00
TotpCodeCell.Sub.SetBinding(Label.TextProperty, nameof(VaultViewCipherPageModel.TotpSecond));
TotpCodeCell.Sub.SetBinding(Label.TextColorProperty, nameof(VaultViewCipherPageModel.TotpColor));
TotpCodeCell.Value.FontFamily = Helpers.OnPlatform(iOS: "Menlo-Regular", Android: "monospace", WinPhone: "Courier");
2017-07-13 21:44:02 +03:00
// Notes
2017-04-20 23:07:34 +03:00
NotesCell = new LabeledValueCell();
2017-10-19 23:08:29 +03:00
NotesCell.Value.SetBinding(Label.TextProperty, nameof(VaultViewCipherPageModel.Notes));
2017-04-20 23:07:34 +03:00
NotesCell.Value.LineBreakMode = LineBreakMode.WordWrap;
2017-10-19 23:08:29 +03:00
ItemInformationSection = new TableSection(AppResources.ItemInformation)
{
nameCell
};
NotesSection = new TableSection(AppResources.Notes)
{
2017-04-20 23:07:34 +03:00
NotesCell
};
Table = new ExtendedTableView
2016-05-07 05:29:03 +03:00
{
Intent = TableIntent.Settings,
2016-05-24 06:48:34 +03:00
EnableScrolling = true,
HasUnevenRows = true,
2017-07-13 17:51:45 +03:00
EnableSelection = true,
Root = new TableRoot
{
2017-10-19 23:08:29 +03:00
ItemInformationSection
}
};
2017-05-30 21:13:53 +03:00
if(Device.RuntimePlatform == Device.iOS)
{
Table.RowHeight = -1;
Table.EstimatedRowHeight = 70;
}
2016-05-07 05:29:03 +03:00
Title = AppResources.ViewItem;
2016-05-24 06:48:34 +03:00
Content = Table;
BindingContext = Model;
2016-05-07 05:29:03 +03:00
}
protected async override void OnAppearing()
2016-05-07 05:29:03 +03:00
{
_pageDisappeared = false;
2017-04-20 23:07:34 +03:00
NotesCell.Tapped += NotesCell_Tapped;
EditItem.InitEvents();
2017-10-19 23:08:29 +03:00
var cipher = await _cipherService.GetByIdAsync(_cipherId);
if(cipher == null)
2016-05-07 05:29:03 +03:00
{
await Navigation.PopForDeviceAsync();
return;
2016-05-07 05:29:03 +03:00
}
Model.Update(cipher);
2016-05-12 00:30:09 +03:00
2017-10-19 23:08:29 +03:00
if(ItemInformationSection.Contains(UriCell))
{
2017-10-19 23:08:29 +03:00
ItemInformationSection.Remove(UriCell);
}
2017-07-22 22:38:08 +03:00
if(Model.ShowUri)
{
2017-10-19 23:08:29 +03:00
ItemInformationSection.Add(UriCell);
}
2017-10-19 23:08:29 +03:00
if(ItemInformationSection.Contains(UsernameCell))
{
2017-10-19 23:08:29 +03:00
ItemInformationSection.Remove(UsernameCell);
}
2017-07-22 22:38:08 +03:00
if(Model.ShowUsername)
{
2017-10-19 23:08:29 +03:00
ItemInformationSection.Add(UsernameCell);
}
2017-10-19 23:08:29 +03:00
if(ItemInformationSection.Contains(PasswordCell))
{
2017-10-19 23:08:29 +03:00
ItemInformationSection.Remove(PasswordCell);
}
2017-07-22 22:38:08 +03:00
if(Model.ShowPassword)
{
2017-10-19 23:08:29 +03:00
ItemInformationSection.Add(PasswordCell);
}
2017-07-22 22:38:08 +03:00
if(Table.Root.Contains(NotesSection))
{
Table.Root.Remove(NotesSection);
}
2017-07-22 22:38:08 +03:00
if(Model.ShowNotes)
{
Table.Root.Add(NotesSection);
}
2017-07-22 22:38:08 +03:00
// Totp
2017-10-19 23:08:29 +03:00
if(ItemInformationSection.Contains(TotpCodeCell))
{
2017-10-19 23:08:29 +03:00
ItemInformationSection.Remove(TotpCodeCell);
}
if(cipher.Login?.Totp != null && (_tokenService.TokenPremium || cipher.OrganizationUseTotp))
2017-07-13 21:44:02 +03:00
{
var totpKey = cipher.Login?.Totp.Decrypt(cipher.OrganizationId);
2017-07-22 22:38:08 +03:00
if(!string.IsNullOrWhiteSpace(totpKey))
2017-07-13 21:44:02 +03:00
{
Model.TotpCode = Crypto.Totp(totpKey);
2017-07-22 22:38:08 +03:00
if(!string.IsNullOrWhiteSpace(Model.TotpCode))
2017-07-13 21:44:02 +03:00
{
TotpTick(totpKey);
Device.StartTimer(new TimeSpan(0, 0, 1), () =>
{
if(_pageDisappeared)
{
return false;
}
2017-07-13 21:44:02 +03:00
TotpTick(totpKey);
return true;
});
2017-10-19 23:08:29 +03:00
ItemInformationSection.Add(TotpCodeCell);
2017-07-13 21:44:02 +03:00
}
}
}
2017-07-22 22:38:08 +03:00
CleanupAttachmentCells();
if(Table.Root.Contains(AttachmentsSection))
2017-07-13 21:44:02 +03:00
{
2017-07-22 22:38:08 +03:00
Table.Root.Remove(AttachmentsSection);
}
2017-07-24 17:34:22 +03:00
if(Model.ShowAttachments && _tokenService.TokenPremium)
2017-07-22 22:38:08 +03:00
{
AttachmentsSection = new TableSection(AppResources.Attachments);
AttachmentCells = new List<AttachmentViewCell>();
foreach(var attachment in Model.Attachments.OrderBy(s => s.Name))
{
var attachmentCell = new AttachmentViewCell(attachment, async () =>
{
await OpenAttachmentAsync(cipher, attachment);
2017-07-22 22:38:08 +03:00
});
AttachmentCells.Add(attachmentCell);
AttachmentsSection.Add(attachmentCell);
attachmentCell.InitEvents();
}
Table.Root.Add(AttachmentsSection);
2017-07-13 21:44:02 +03:00
}
if(Table.Root.Contains(FieldsSection))
{
Table.Root.Remove(FieldsSection);
}
if(Model.ShowFields)
{
FieldsSection = new TableSection(AppResources.CustomFields);
foreach(var field in Model.Fields)
{
2017-09-25 22:05:36 +03:00
FieldViewCell fieldCell;
switch(field.Type)
{
case FieldType.Text:
2017-09-25 22:05:36 +03:00
fieldCell = new FieldViewCell(this, field, null);
break;
case FieldType.Hidden:
2017-09-25 22:05:36 +03:00
fieldCell = new FieldViewCell(this, field, null, null);
break;
case FieldType.Boolean:
2017-09-25 22:05:36 +03:00
fieldCell = new FieldViewCell(this, field);
break;
default:
continue;
}
FieldsSection.Add(fieldCell);
}
Table.Root.Add(FieldsSection);
}
base.OnAppearing();
2016-05-07 05:29:03 +03:00
}
protected override void OnDisappearing()
{
_pageDisappeared = true;
2017-04-20 23:07:34 +03:00
NotesCell.Tapped -= NotesCell_Tapped;
EditItem.Dispose();
2017-07-13 17:51:45 +03:00
CleanupAttachmentCells();
}
private void CleanupAttachmentCells()
{
if(AttachmentCells != null)
{
foreach(var cell in AttachmentCells)
{
cell.Dispose();
}
}
}
2017-10-19 23:08:29 +03:00
private async Task OpenAttachmentAsync(Cipher cipher, VaultViewCipherPageModel.Attachment attachment)
{
2017-10-19 23:08:29 +03:00
if(!_tokenService.TokenPremium && !cipher.OrganizationUseTotp)
{
_userDialogs.Alert(AppResources.PremiumRequired);
return;
}
// 10 MB warning
if(attachment.Size >= 10485760 && !(await _userDialogs.ConfirmAsync(
2017-07-13 16:01:00 +03:00
string.Format(AppResources.AttachmentLargeWarning, attachment.SizeName), null,
AppResources.Yes, AppResources.No)))
{
return;
}
if(!_deviceActionService.CanOpenFile(attachment.Name))
{
await _userDialogs.AlertAsync(AppResources.UnableToOpenFile, null, AppResources.Ok);
return;
}
_userDialogs.ShowLoading(AppResources.Downloading, MaskType.Black);
2017-10-19 23:08:29 +03:00
var data = await _cipherService.DownloadAndDecryptAttachmentAsync(attachment.Url, cipher.OrganizationId);
_userDialogs.HideLoading();
if(data == null)
{
await _userDialogs.AlertAsync(AppResources.UnableToDownloadFile, null, AppResources.Ok);
return;
}
2017-07-13 16:01:00 +03:00
if(!_deviceActionService.OpenFile(data, attachment.Id, attachment.Name))
{
await _userDialogs.AlertAsync(AppResources.UnableToOpenFile, null, AppResources.Ok);
return;
}
}
2017-04-20 23:07:34 +03:00
private void NotesCell_Tapped(object sender, EventArgs e)
{
Copy(Model.Notes, AppResources.Notes);
}
private void Copy(string copyText, string alertLabel)
2016-05-07 05:29:03 +03:00
{
_deviceActionService.CopyToClipboard(copyText);
_userDialogs.Toast(string.Format(AppResources.ValueHasBeenCopied, alertLabel));
2016-05-07 05:29:03 +03:00
}
2017-07-13 21:44:02 +03:00
private void TotpTick(string totpKey)
{
var now = Helpers.EpocUtcNow() / 1000;
var mod = now % 30;
Model.TotpSecond = (int)(30 - mod);
if(mod == 0)
{
Model.TotpCode = Crypto.Totp(totpKey);
}
}
2017-10-19 23:08:29 +03:00
private class EditCipherToolBarItem : ExtendedToolbarItem
2016-05-07 05:29:03 +03:00
{
private readonly VaultViewCipherPage _page;
2017-10-19 23:08:29 +03:00
private readonly string _cipherId;
2016-05-07 05:29:03 +03:00
2017-10-19 23:08:29 +03:00
public EditCipherToolBarItem(VaultViewCipherPage page, string cipherId)
2016-05-07 05:29:03 +03:00
{
_page = page;
2017-10-19 23:08:29 +03:00
_cipherId = cipherId;
Text = AppResources.Edit;
ClickAction = async () => await ClickedItem();
2016-05-07 05:29:03 +03:00
}
2016-05-02 09:52:09 +03:00
private async Task ClickedItem()
2016-05-07 05:29:03 +03:00
{
2017-10-19 23:08:29 +03:00
var page = new VaultEditCipherPage(_cipherId);
await _page.Navigation.PushForDeviceAsync(page);
2016-05-07 05:29:03 +03:00
}
2016-05-02 09:52:09 +03:00
}
2017-07-13 17:51:45 +03:00
public class AttachmentViewCell : LabeledRightDetailCell, IDisposable
{
private readonly Action _tapped;
2017-10-19 23:08:29 +03:00
public AttachmentViewCell(VaultViewCipherPageModel.Attachment attachment, Action tappedAction)
{
_tapped = tappedAction;
Label.Text = attachment.Name;
2017-07-13 16:01:00 +03:00
Detail.Text = attachment.SizeName;
Icon.Source = "download.png";
BackgroundColor = Color.White;
2017-07-14 01:08:16 +03:00
Detail.MinimumWidthRequest = 100;
2017-07-13 17:51:45 +03:00
}
public void InitEvents()
{
Tapped += AttachmentViewCell_Tapped;
}
2017-07-13 17:51:45 +03:00
public void Dispose()
{
Tapped -= AttachmentViewCell_Tapped;
}
private void AttachmentViewCell_Tapped(object sender, EventArgs e)
{
_tapped?.Invoke();
}
}
2017-09-25 22:05:36 +03:00
public class FieldViewCell : LabeledValueCell
{
2017-10-19 23:08:29 +03:00
public FieldViewCell(VaultViewCipherPage page, VaultViewCipherPageModel.Field field)
2017-09-25 22:05:36 +03:00
: base(field.Name, field.Value == "true" ? "✓" : "-")
{
Init(page, field, null);
}
2017-10-19 23:08:29 +03:00
public FieldViewCell(VaultViewCipherPage page, VaultViewCipherPageModel.Field field, bool? a)
: base(field.Name, field.Value, "clipboard.png")
2017-09-25 22:05:36 +03:00
{
Init(page, field, Button1);
}
2017-10-19 23:08:29 +03:00
public FieldViewCell(VaultViewCipherPage page, VaultViewCipherPageModel.Field field, bool? a, bool? b)
: base(field.Name, field.MaskedValue, string.Empty, "clipboard.png")
2017-09-25 22:05:36 +03:00
{
Value.FontFamily = Helpers.OnPlatform(iOS: "Menlo-Regular",
Android: "monospace", WinPhone: "Courier");
if(Device.RuntimePlatform == Device.iOS)
{
Button1.Margin = new Thickness(10, 0);
}
2017-09-25 22:05:36 +03:00
Button1.Image = "eye";
Button1.Command = new Command(() =>
{
field.Revealed = !field.Revealed;
if(field.Revealed)
{
Button1.Image = "eye_slash.png";
2017-09-25 22:05:36 +03:00
Value.Text = field.Value;
}
else
{
Button1.Image = "eye.png";
2017-09-25 22:05:36 +03:00
Value.Text = field.MaskedValue;
}
});
Init(page, field, Button2);
}
2017-10-19 23:08:29 +03:00
private void Init(VaultViewCipherPage page, VaultViewCipherPageModel.Field field, ExtendedButton copyButton)
2017-09-25 22:05:36 +03:00
{
Value.LineBreakMode = LineBreakMode.WordWrap;
if(copyButton != null)
{
copyButton.Command = new Command(() => page.Copy(field.Value, field.Name));
}
}
}
2016-05-02 09:52:09 +03:00
}
}