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

439 lines
18 KiB
C#
Raw Normal View History

2019-05-07 18:25:21 +03:00
using Bit.App.Abstractions;
using Bit.App.Resources;
using Bit.Core.Abstractions;
2019-05-07 19:29:02 +03:00
using Bit.Core.Enums;
using Bit.Core.Exceptions;
2019-05-07 18:25:21 +03:00
using Bit.Core.Models.View;
using Bit.Core.Utilities;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Xamarin.Forms;
namespace Bit.App.Pages
{
public class AddEditPageViewModel : BaseViewModel
{
private readonly IDeviceActionService _deviceActionService;
private readonly ICipherService _cipherService;
private readonly IUserService _userService;
private readonly IPlatformUtilsService _platformUtilsService;
private readonly IAuditService _auditService;
2019-05-07 19:29:02 +03:00
private readonly IMessagingService _messagingService;
2019-05-07 18:25:21 +03:00
private CipherView _cipher;
private List<AddEditPageFieldViewModel> _fields;
private bool _showPassword;
private bool _showCardCode;
2019-05-08 21:37:12 +03:00
private int _typeSelectedIndex;
private int _cardBrandSelectedIndex;
private int _cardExpMonthSelectedIndex;
private int _identityTitleSelectedIndex;
private string[] _additionalCipherProperties = new string[]
{
nameof(IsLogin),
nameof(IsIdentity),
nameof(IsCard),
nameof(IsSecureNote),
nameof(ShowUris),
nameof(ShowAttachments),
nameof(ShowIdentityAddress),
};
2019-05-07 18:25:21 +03:00
public AddEditPageViewModel()
{
_deviceActionService = ServiceContainer.Resolve<IDeviceActionService>("deviceActionService");
_cipherService = ServiceContainer.Resolve<ICipherService>("cipherService");
_userService = ServiceContainer.Resolve<IUserService>("userService");
_platformUtilsService = ServiceContainer.Resolve<IPlatformUtilsService>("platformUtilsService");
_auditService = ServiceContainer.Resolve<IAuditService>("auditService");
2019-05-07 19:29:02 +03:00
_messagingService = ServiceContainer.Resolve<IMessagingService>("messagingService");
GeneratePasswordCommand = new Command(GeneratePassword);
2019-05-07 18:25:21 +03:00
TogglePasswordCommand = new Command(TogglePassword);
ToggleCardCodeCommand = new Command(ToggleCardCode);
CheckPasswordCommand = new Command(CheckPasswordAsync);
2019-05-08 06:22:55 +03:00
2019-05-08 21:37:12 +03:00
TypeOptions = new List<KeyValuePair<string, CipherType>>
{
new KeyValuePair<string, CipherType>(AppResources.TypeLogin, CipherType.Login),
new KeyValuePair<string, CipherType>(AppResources.TypeCard, CipherType.Card),
new KeyValuePair<string, CipherType>(AppResources.TypeIdentity, CipherType.Identity),
new KeyValuePair<string, CipherType>(AppResources.TypeSecureNote, CipherType.SecureNote),
};
2019-05-08 06:22:55 +03:00
CardBrandOptions = new List<KeyValuePair<string, string>>
{
new KeyValuePair<string, string>($"-- {AppResources.Select} --", null),
new KeyValuePair<string, string>("Visa", "Visa"),
new KeyValuePair<string, string>("Mastercard", "Mastercard"),
new KeyValuePair<string, string>("American Express", "Amex"),
new KeyValuePair<string, string>("Discover", "Discover"),
new KeyValuePair<string, string>("Diners Club", "Diners Club"),
new KeyValuePair<string, string>("JCB", "JCB"),
new KeyValuePair<string, string>("Maestro", "Maestro"),
new KeyValuePair<string, string>("UnionPay", "UnionPay"),
new KeyValuePair<string, string>(AppResources.Other, "Other")
};
CardExpMonthOptions = new List<KeyValuePair<string, string>>
{
new KeyValuePair<string, string>($"-- {AppResources.Select} --", null),
new KeyValuePair<string, string>($"01 - {AppResources.January}", "1"),
new KeyValuePair<string, string>($"02 - {AppResources.February}", "2"),
new KeyValuePair<string, string>($"03 - {AppResources.March}", "3"),
new KeyValuePair<string, string>($"04 - {AppResources.April}", "4"),
new KeyValuePair<string, string>($"05 - {AppResources.May}", "5"),
new KeyValuePair<string, string>($"06 - {AppResources.June}", "6"),
new KeyValuePair<string, string>($"07 - {AppResources.July}", "7"),
new KeyValuePair<string, string>($"08 - {AppResources.August}", "8"),
new KeyValuePair<string, string>($"09 - {AppResources.September}", "9"),
new KeyValuePair<string, string>($"10 - {AppResources.October}", "10"),
new KeyValuePair<string, string>($"11 - {AppResources.November}", "11"),
new KeyValuePair<string, string>($"12 - {AppResources.December}", "12")
};
IdentityTitleOptions = new List<KeyValuePair<string, string>>
{
new KeyValuePair<string, string>($"-- {AppResources.Select} --", null),
new KeyValuePair<string, string>(AppResources.Mr, AppResources.Mr),
new KeyValuePair<string, string>(AppResources.Mrs, AppResources.Mrs),
new KeyValuePair<string, string>(AppResources.Ms, AppResources.Ms),
new KeyValuePair<string, string>(AppResources.Dr, AppResources.Dr),
};
2019-05-07 18:25:21 +03:00
}
2019-05-07 19:29:02 +03:00
public Command GeneratePasswordCommand { get; set; }
2019-05-07 18:25:21 +03:00
public Command TogglePasswordCommand { get; set; }
public Command ToggleCardCodeCommand { get; set; }
public Command CheckPasswordCommand { get; set; }
public string CipherId { get; set; }
2019-05-07 19:29:02 +03:00
public string OrganizationId { get; set; }
public string FolderId { get; set; }
public CipherType? Type { get; set; }
public List<string> CollectionIds { get; set; }
2019-05-08 21:37:12 +03:00
public List<KeyValuePair<string, CipherType>> TypeOptions { get; set; }
2019-05-08 06:22:55 +03:00
public List<KeyValuePair<string, string>> CardBrandOptions { get; set; }
public List<KeyValuePair<string, string>> CardExpMonthOptions { get; set; }
public List<KeyValuePair<string, string>> IdentityTitleOptions { get; set; }
2019-05-08 21:37:12 +03:00
public int TypeSelectedIndex
{
get => _typeSelectedIndex;
set
{
SetProperty(ref _typeSelectedIndex, value);
TypeChanged();
}
}
public int CardBrandSelectedIndex
{
get => _cardBrandSelectedIndex;
set
{
SetProperty(ref _cardBrandSelectedIndex, value);
CardBrandChanged();
}
}
public int CardExpMonthSelectedIndex
{
get => _cardExpMonthSelectedIndex;
set
{
SetProperty(ref _cardExpMonthSelectedIndex, value);
CardExpMonthChanged();
}
}
public int IdentityTitleSelectedIndex
{
get => _identityTitleSelectedIndex;
set
{
SetProperty(ref _identityTitleSelectedIndex, value);
IdentityTitleChanged();
}
}
2019-05-07 18:25:21 +03:00
public CipherView Cipher
{
get => _cipher;
2019-05-08 21:37:12 +03:00
set => SetProperty(ref _cipher, value, additionalPropertyNames: _additionalCipherProperties);
2019-05-07 18:25:21 +03:00
}
public List<AddEditPageFieldViewModel> Fields
{
get => _fields;
set => SetProperty(ref _fields, value);
}
public bool ShowPassword
{
get => _showPassword;
set => SetProperty(ref _showPassword, value,
additionalPropertyNames: new string[]
{
nameof(ShowPasswordIcon)
});
}
public bool ShowCardCode
{
get => _showCardCode;
set => SetProperty(ref _showCardCode, value,
additionalPropertyNames: new string[]
{
nameof(ShowCardCodeIcon)
});
}
2019-05-07 19:29:02 +03:00
public bool EditMode => !string.IsNullOrWhiteSpace(CipherId);
public bool IsLogin => Cipher?.Type == CipherType.Login;
public bool IsIdentity => Cipher?.Type == CipherType.Identity;
public bool IsCard => Cipher?.Type == CipherType.Card;
public bool IsSecureNote => Cipher?.Type == CipherType.SecureNote;
2019-05-07 18:25:21 +03:00
public bool ShowUris => IsLogin && Cipher.Login.HasUris;
public bool ShowIdentityAddress => IsIdentity && (
!string.IsNullOrWhiteSpace(Cipher.Identity.Address1) ||
!string.IsNullOrWhiteSpace(Cipher.Identity.City) ||
!string.IsNullOrWhiteSpace(Cipher.Identity.Country));
2019-05-07 19:29:02 +03:00
public bool ShowAttachments => Cipher.HasAttachments;
2019-05-07 18:25:21 +03:00
public string ShowPasswordIcon => ShowPassword ? "" : "";
public string ShowCardCodeIcon => ShowCardCode ? "" : "";
2019-05-07 19:29:02 +03:00
public void Init()
2019-05-07 18:25:21 +03:00
{
2019-05-07 19:29:02 +03:00
PageTitle = EditMode ? AppResources.EditItem : AppResources.AddItem;
2019-05-07 18:25:21 +03:00
}
2019-05-07 19:29:02 +03:00
public async Task LoadAsync()
2019-05-07 18:25:21 +03:00
{
2019-05-07 19:29:02 +03:00
// TODO: load collections
2019-05-07 18:25:21 +03:00
2019-05-07 19:29:02 +03:00
if(EditMode)
2019-05-07 18:25:21 +03:00
{
2019-05-07 19:29:02 +03:00
var cipher = await _cipherService.GetAsync(CipherId);
Cipher = await cipher.DecryptAsync();
Fields = Cipher.Fields?.Select(f => new AddEditPageFieldViewModel(f)).ToList();
2019-05-08 21:37:12 +03:00
if(Cipher.Card != null)
{
CardBrandSelectedIndex = string.IsNullOrWhiteSpace(Cipher.Card.Brand) ? 0 :
CardBrandOptions.FindIndex(k => k.Value == Cipher.Card.Brand);
CardExpMonthSelectedIndex = string.IsNullOrWhiteSpace(Cipher.Card.ExpMonth) ? 0 :
CardExpMonthOptions.FindIndex(k => k.Value == Cipher.Card.ExpMonth);
}
if(Cipher.Identity != null)
{
IdentityTitleSelectedIndex = string.IsNullOrWhiteSpace(Cipher.Identity.Title) ? 0 :
IdentityTitleOptions.FindIndex(k => k.Value == Cipher.Identity.Title);
}
2019-05-07 18:25:21 +03:00
}
else
{
2019-05-07 19:29:02 +03:00
Cipher = new CipherView
{
OrganizationId = OrganizationId,
FolderId = FolderId,
Type = Type.GetValueOrDefault(CipherType.Login),
Login = new LoginView(),
Card = new CardView(),
Identity = new IdentityView(),
SecureNote = new SecureNoteView()
};
Cipher.Login.Uris = new List<LoginUriView>();
Cipher.SecureNote.Type = SecureNoteType.Generic;
2019-05-08 21:37:12 +03:00
TypeSelectedIndex = TypeOptions.FindIndex(k => k.Value == Cipher.Type);
CardBrandSelectedIndex = 0;
CardExpMonthSelectedIndex = 0;
IdentityTitleSelectedIndex = 0;
2019-05-07 19:29:02 +03:00
// TODO: org/collection stuff
2019-05-07 18:25:21 +03:00
}
}
2019-05-07 19:29:02 +03:00
public async Task<bool> SubmitAsync()
2019-05-07 18:25:21 +03:00
{
2019-05-07 19:29:02 +03:00
if(string.IsNullOrWhiteSpace(Cipher.Name))
2019-05-07 18:25:21 +03:00
{
2019-05-07 19:29:02 +03:00
await Page.DisplayAlert(AppResources.AnErrorHasOccurred,
string.Format(AppResources.ValidationFieldRequired, AppResources.Name),
AppResources.Ok);
return false;
2019-05-07 18:25:21 +03:00
}
2019-05-07 19:29:02 +03:00
if(!EditMode && Cipher.Type == CipherType.Login && (Cipher.Login.Uris?.Count ?? 0) == 1 &&
string.IsNullOrWhiteSpace(Cipher.Login.Uris.First().Uri))
2019-05-07 18:25:21 +03:00
{
2019-05-07 19:29:02 +03:00
Cipher.Login.Uris = null;
2019-05-07 18:25:21 +03:00
}
2019-05-07 19:29:02 +03:00
if(!EditMode && Cipher.OrganizationId != null)
2019-05-07 18:25:21 +03:00
{
2019-05-07 19:29:02 +03:00
// TODO: filter cipher collection ids
2019-05-07 18:25:21 +03:00
}
2019-05-07 19:29:02 +03:00
var cipher = await _cipherService.EncryptAsync(Cipher);
try
2019-05-07 18:25:21 +03:00
{
2019-05-07 19:29:02 +03:00
await _deviceActionService.ShowLoadingAsync(AppResources.Saving);
await _cipherService.SaveWithServerAsync(cipher);
Cipher.Id = cipher.Id;
await _deviceActionService.HideLoadingAsync();
_platformUtilsService.ShowToast("success", null,
EditMode ? AppResources.ItemUpdated : AppResources.NewItemCreated);
_messagingService.Send(EditMode ? "editedCipher" : "addedCipher");
await Page.Navigation.PopModalAsync();
return true;
2019-05-07 18:25:21 +03:00
}
2019-05-07 19:29:02 +03:00
catch(ApiException e)
2019-05-07 18:25:21 +03:00
{
2019-05-07 19:29:02 +03:00
await _deviceActionService.HideLoadingAsync();
await Page.DisplayAlert(AppResources.AnErrorHasOccurred, e.Error.GetSingleMessage(), AppResources.Ok);
2019-05-07 18:25:21 +03:00
}
2019-05-07 19:29:02 +03:00
return false;
2019-05-07 18:25:21 +03:00
}
2019-05-07 19:29:02 +03:00
public async Task<bool> DeleteAsync()
2019-05-07 18:25:21 +03:00
{
2019-05-07 19:29:02 +03:00
var confirmed = await _platformUtilsService.ShowDialogAsync(AppResources.DoYouReallyWantToDelete,
null, AppResources.Yes, AppResources.No);
if(!confirmed)
2019-05-07 18:25:21 +03:00
{
2019-05-07 19:29:02 +03:00
return false;
2019-05-07 18:25:21 +03:00
}
try
{
2019-05-07 19:29:02 +03:00
await _deviceActionService.ShowLoadingAsync(AppResources.Deleting);
await _cipherService.DeleteWithServerAsync(Cipher.Id);
2019-05-07 18:25:21 +03:00
await _deviceActionService.HideLoadingAsync();
2019-05-07 19:29:02 +03:00
_platformUtilsService.ShowToast("success", null, AppResources.ItemDeleted);
_messagingService.Send("deletedCipher");
return true;
2019-05-07 18:25:21 +03:00
}
2019-05-07 19:29:02 +03:00
catch(ApiException e)
2019-05-07 18:25:21 +03:00
{
await _deviceActionService.HideLoadingAsync();
2019-05-07 19:29:02 +03:00
await Page.DisplayAlert(AppResources.AnErrorHasOccurred, e.Error.GetSingleMessage(), AppResources.Ok);
2019-05-07 18:25:21 +03:00
}
2019-05-07 19:29:02 +03:00
return false;
2019-05-07 18:25:21 +03:00
}
2019-05-07 19:29:02 +03:00
public void GeneratePassword()
2019-05-07 18:25:21 +03:00
{
2019-05-08 06:22:55 +03:00
// TODO: push modal for generate page
2019-05-07 18:25:21 +03:00
}
2019-05-07 19:29:02 +03:00
public void TogglePassword()
2019-05-07 18:25:21 +03:00
{
2019-05-07 19:29:02 +03:00
ShowPassword = !ShowPassword;
2019-05-07 18:25:21 +03:00
}
2019-05-07 19:29:02 +03:00
public void ToggleCardCode()
2019-05-07 18:25:21 +03:00
{
2019-05-07 19:29:02 +03:00
ShowCardCode = !ShowCardCode;
2019-05-07 18:25:21 +03:00
}
2019-05-08 21:37:12 +03:00
private void TypeChanged()
{
if(Cipher != null && TypeSelectedIndex > -1)
{
Cipher.Type = TypeOptions[TypeSelectedIndex].Value;
TriggerCipherChanged();
}
}
private void CardBrandChanged()
{
if(Cipher?.Card != null && CardBrandSelectedIndex > -1)
{
Cipher.Card.Brand = CardBrandOptions[CardBrandSelectedIndex].Value;
}
}
private void CardExpMonthChanged()
{
if(Cipher?.Card != null && CardExpMonthSelectedIndex > -1)
{
Cipher.Card.ExpMonth = CardExpMonthOptions[CardExpMonthSelectedIndex].Value;
}
}
private void IdentityTitleChanged()
{
if(Cipher?.Identity != null && IdentityTitleSelectedIndex > -1)
{
Cipher.Identity.Title = IdentityTitleOptions[IdentityTitleSelectedIndex].Value;
}
}
private void TriggerCipherChanged()
{
TriggerPropertyChanged(nameof(Cipher), _additionalCipherProperties);
}
2019-05-07 19:29:02 +03:00
private async void CheckPasswordAsync()
2019-05-07 18:25:21 +03:00
{
2019-05-07 19:29:02 +03:00
if(!(Page as BaseContentPage).DoOnce())
2019-05-07 18:25:21 +03:00
{
2019-05-07 19:29:02 +03:00
return;
}
if(string.IsNullOrWhiteSpace(Cipher.Login?.Password))
{
return;
}
await _deviceActionService.ShowLoadingAsync(AppResources.CheckingPassword);
var matches = await _auditService.PasswordLeakedAsync(Cipher.Login.Password);
await _deviceActionService.HideLoadingAsync();
if(matches > 0)
{
await _platformUtilsService.ShowDialogAsync(string.Format(AppResources.PasswordExposed,
matches.ToString("N0")));
}
else
{
await _platformUtilsService.ShowDialogAsync(AppResources.PasswordSafe);
2019-05-07 18:25:21 +03:00
}
}
}
public class AddEditPageFieldViewModel : BaseViewModel
{
private FieldView _field;
private bool _showHiddenValue;
public AddEditPageFieldViewModel(FieldView field)
{
Field = field;
ToggleHiddenValueCommand = new Command(ToggleHiddenValue);
}
public FieldView Field
{
get => _field;
set => SetProperty(ref _field, value,
additionalPropertyNames: new string[]
{
nameof(ValueText),
nameof(IsBooleanType),
nameof(IsHiddenType),
nameof(IsTextType),
nameof(ShowCopyButton),
});
}
public bool ShowHiddenValue
{
get => _showHiddenValue;
set => SetProperty(ref _showHiddenValue, value,
additionalPropertyNames: new string[]
{
nameof(ShowHiddenValueIcon)
});
}
public Command ToggleHiddenValueCommand { get; set; }
public string ValueText => IsBooleanType ? (_field.Value == "true" ? "" : "") : _field.Value;
public string ShowHiddenValueIcon => _showHiddenValue ? "" : "";
public bool IsTextType => _field.Type == Core.Enums.FieldType.Text;
public bool IsBooleanType => _field.Type == Core.Enums.FieldType.Boolean;
public bool IsHiddenType => _field.Type == Core.Enums.FieldType.Hidden;
public bool ShowCopyButton => _field.Type != Core.Enums.FieldType.Boolean &&
!string.IsNullOrWhiteSpace(_field.Value);
public void ToggleHiddenValue()
{
ShowHiddenValue = !ShowHiddenValue;
}
}
}