bitwarden-mobile/src/App/Pages/Vault/ViewPageViewModel.cs

802 lines
30 KiB
C#
Raw Normal View History

2022-04-26 17:21:17 +02:00
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Bit.App.Abstractions;
2019-04-24 17:23:03 +02:00
using Bit.App.Resources;
2019-04-27 05:58:15 +02:00
using Bit.App.Utilities;
2022-04-26 17:21:17 +02:00
using Bit.Core;
2019-04-24 17:23:03 +02:00
using Bit.Core.Abstractions;
using Bit.Core.Enums;
2019-05-10 19:47:59 +02:00
using Bit.Core.Exceptions;
2019-04-24 17:23:03 +02:00
using Bit.Core.Models.View;
using Bit.Core.Utilities;
2019-04-26 06:26:09 +02:00
using Xamarin.Forms;
2019-04-24 17:23:03 +02:00
namespace Bit.App.Pages
{
public class ViewPageViewModel : BaseViewModel
{
private readonly IDeviceActionService _deviceActionService;
private readonly ICipherService _cipherService;
Account Switching (#1807) * Account Switching (#1720) * Account switching * WIP * wip * wip * updates to send test logic * fixed Send tests * fixes for theme handling on account switching and re-adding existing account * switch fixes * fixes * fixes * cleanup * vault timeout fixes * account list status enhancements * logout fixes and token handling improvements * merge latest (#1727) * remove duplicate dependency * fix for initial login token storage paradox (#1730) * Fix avatar color update toolbar item issue on iOS for account switching (#1735) * Updated account switching menu UI (#1733) * updated account switching menu UI * additional changes * add key suffix to constant * GetFirstLetters method tweaks * Fix crash on account switching when logging out when having more than user at a time (#1740) * single account migration to multi-account on app update (#1741) * Account Switching Tap to dismiss (#1743) * Added tap to dismiss on the Account switching overlay and improved a bit the code * Fix account switching overlay background transparent on the proper place * Fixed transparent background and the shadow on the account switching overlay * Fix iOS top space on Account switching list overlay after modal (#1746) * Fix top space added to Account switching list overlay after closing modal * Fix top space added to Account switching list overlay after closing modal on lock, login and home views just in case we add modals in the future there as well * Usability: dismiss account list on certain events (#1748) * dismiss account list on certain events * use new FireAndForget method for back button logic * Create and use Account Switching overlay control (#1753) * Added Account switching overlay control and its own ViewModel and refactored accordingly * Fix account switching Accounts list binding update * Implemented dismiss account switching overlay when changing tabs and when selecting the same tab. Also updated the deprecated listener on CustomTabbedRenderer on Android (#1755) * Overriden Equals on AvatarImageSource so it doesn't get set multiple times when it's the same image thus producing blinking on tab chaged (#1756) * Usability improvements for logout on vault timeout (#1781) * accountswitching fixes (#1784) * Fix for invalid PIN lock state when switching accounts (#1792) * fix for pin lock flow * named tuple values and updated async * clear send service cache on account switch (#1796) * Global theme and account removal (#1793) * Global theme and account removal * remove redundant call to hide account list overlay * cleanup and additional tweaks * add try/catch to remove account dialog flow Co-authored-by: Federico Maccaroni <fedemkr@gmail.com>
2022-02-23 18:40:17 +01:00
private readonly IStateService _stateService;
2019-04-26 06:26:09 +02:00
private readonly ITotpService _totpService;
private readonly IPlatformUtilsService _platformUtilsService;
2019-04-27 06:19:44 +02:00
private readonly IAuditService _auditService;
2019-05-10 19:47:59 +02:00
private readonly IMessagingService _messagingService;
2019-07-12 23:29:40 +02:00
private readonly IEventService _eventService;
private readonly IPasswordRepromptService _passwordRepromptService;
private readonly ILocalizeService _localizeService;
private readonly IClipboardService _clipboardService;
2019-04-24 17:23:03 +02:00
private CipherView _cipher;
2019-04-29 19:56:36 +02:00
private List<ViewPageFieldViewModel> _fields;
2019-04-24 17:23:03 +02:00
private bool _canAccessPremium;
2019-04-27 05:37:21 +02:00
private bool _showPassword;
private bool _showCardNumber;
2019-04-27 05:37:21 +02:00
private bool _showCardCode;
2019-04-26 06:26:09 +02:00
private string _totpCode;
private string _totpCodeFormatted;
private string _totpSec;
private bool _totpLow;
private DateTime? _totpInterval = null;
2019-07-12 23:29:40 +02:00
private string _previousCipherId;
private byte[] _attachmentData;
private string _attachmentFilename;
private bool _passwordReprompted;
2019-04-24 17:23:03 +02:00
public ViewPageViewModel()
{
_deviceActionService = ServiceContainer.Resolve<IDeviceActionService>("deviceActionService");
_cipherService = ServiceContainer.Resolve<ICipherService>("cipherService");
Account Switching (#1807) * Account Switching (#1720) * Account switching * WIP * wip * wip * updates to send test logic * fixed Send tests * fixes for theme handling on account switching and re-adding existing account * switch fixes * fixes * fixes * cleanup * vault timeout fixes * account list status enhancements * logout fixes and token handling improvements * merge latest (#1727) * remove duplicate dependency * fix for initial login token storage paradox (#1730) * Fix avatar color update toolbar item issue on iOS for account switching (#1735) * Updated account switching menu UI (#1733) * updated account switching menu UI * additional changes * add key suffix to constant * GetFirstLetters method tweaks * Fix crash on account switching when logging out when having more than user at a time (#1740) * single account migration to multi-account on app update (#1741) * Account Switching Tap to dismiss (#1743) * Added tap to dismiss on the Account switching overlay and improved a bit the code * Fix account switching overlay background transparent on the proper place * Fixed transparent background and the shadow on the account switching overlay * Fix iOS top space on Account switching list overlay after modal (#1746) * Fix top space added to Account switching list overlay after closing modal * Fix top space added to Account switching list overlay after closing modal on lock, login and home views just in case we add modals in the future there as well * Usability: dismiss account list on certain events (#1748) * dismiss account list on certain events * use new FireAndForget method for back button logic * Create and use Account Switching overlay control (#1753) * Added Account switching overlay control and its own ViewModel and refactored accordingly * Fix account switching Accounts list binding update * Implemented dismiss account switching overlay when changing tabs and when selecting the same tab. Also updated the deprecated listener on CustomTabbedRenderer on Android (#1755) * Overriden Equals on AvatarImageSource so it doesn't get set multiple times when it's the same image thus producing blinking on tab chaged (#1756) * Usability improvements for logout on vault timeout (#1781) * accountswitching fixes (#1784) * Fix for invalid PIN lock state when switching accounts (#1792) * fix for pin lock flow * named tuple values and updated async * clear send service cache on account switch (#1796) * Global theme and account removal (#1793) * Global theme and account removal * remove redundant call to hide account list overlay * cleanup and additional tweaks * add try/catch to remove account dialog flow Co-authored-by: Federico Maccaroni <fedemkr@gmail.com>
2022-02-23 18:40:17 +01:00
_stateService = ServiceContainer.Resolve<IStateService>("stateService");
2019-04-26 06:26:09 +02:00
_totpService = ServiceContainer.Resolve<ITotpService>("totpService");
_platformUtilsService = ServiceContainer.Resolve<IPlatformUtilsService>("platformUtilsService");
2019-04-27 06:19:44 +02:00
_auditService = ServiceContainer.Resolve<IAuditService>("auditService");
2019-05-10 19:47:59 +02:00
_messagingService = ServiceContainer.Resolve<IMessagingService>("messagingService");
2019-07-12 23:29:40 +02:00
_eventService = ServiceContainer.Resolve<IEventService>("eventService");
_passwordRepromptService = ServiceContainer.Resolve<IPasswordRepromptService>("passwordRepromptService");
_localizeService = ServiceContainer.Resolve<ILocalizeService>("localizeService");
_clipboardService = ServiceContainer.Resolve<IClipboardService>("clipboardService");
2019-04-26 22:58:20 +02:00
CopyCommand = new Command<string>((id) => CopyAsync(id, null));
2019-04-27 06:19:44 +02:00
CopyUriCommand = new Command<LoginUriView>(CopyUri);
2019-04-29 19:51:05 +02:00
CopyFieldCommand = new Command<FieldView>(CopyField);
2019-04-27 06:19:44 +02:00
LaunchUriCommand = new Command<LoginUriView>(LaunchUri);
2019-04-27 05:37:21 +02:00
TogglePasswordCommand = new Command(TogglePassword);
ToggleCardNumberCommand = new Command(ToggleCardNumber);
2019-04-27 05:37:21 +02:00
ToggleCardCodeCommand = new Command(ToggleCardCode);
2019-04-27 06:19:44 +02:00
CheckPasswordCommand = new Command(CheckPasswordAsync);
2019-04-29 20:35:44 +02:00
DownloadAttachmentCommand = new Command<AttachmentView>(DownloadAttachmentAsync);
2019-04-24 17:23:03 +02:00
PageTitle = AppResources.ViewItem;
}
2019-04-26 06:26:09 +02:00
public Command CopyCommand { get; set; }
2019-04-26 22:58:20 +02:00
public Command CopyUriCommand { get; set; }
2019-04-29 19:51:05 +02:00
public Command CopyFieldCommand { get; set; }
2019-04-26 22:58:20 +02:00
public Command LaunchUriCommand { get; set; }
2019-04-27 05:37:21 +02:00
public Command TogglePasswordCommand { get; set; }
public Command ToggleCardNumberCommand { get; set; }
2019-04-27 05:37:21 +02:00
public Command ToggleCardCodeCommand { get; set; }
2019-04-27 06:19:44 +02:00
public Command CheckPasswordCommand { get; set; }
2019-04-29 20:35:44 +02:00
public Command DownloadAttachmentCommand { get; set; }
2019-04-26 06:26:09 +02:00
public string CipherId { get; set; }
2019-04-24 17:23:03 +02:00
public CipherView Cipher
{
get => _cipher;
2019-04-26 06:26:09 +02:00
set => SetProperty(ref _cipher, value,
additionalPropertyNames: new string[]
{
nameof(IsLogin),
nameof(IsIdentity),
nameof(IsCard),
2019-04-26 22:58:20 +02:00
nameof(IsSecureNote),
nameof(ShowUris),
nameof(ShowAttachments),
2019-04-27 05:37:21 +02:00
nameof(ShowTotp),
2019-04-27 05:58:15 +02:00
nameof(ColoredPassword),
2019-04-30 15:50:35 +02:00
nameof(UpdatedText),
nameof(PasswordUpdatedText),
nameof(PasswordHistoryText),
2019-04-29 16:20:29 +02:00
nameof(ShowIdentityAddress),
nameof(IsDeleted),
nameof(CanEdit),
2019-04-26 06:26:09 +02:00
});
2019-04-24 17:23:03 +02:00
}
2019-04-29 19:56:36 +02:00
public List<ViewPageFieldViewModel> Fields
2019-04-27 03:53:39 +02:00
{
get => _fields;
set => SetProperty(ref _fields, value);
}
2019-04-24 17:23:03 +02:00
public bool CanAccessPremium
{
get => _canAccessPremium;
set => SetProperty(ref _canAccessPremium, value);
}
2019-04-27 05:37:21 +02:00
public bool ShowPassword
{
get => _showPassword;
set => SetProperty(ref _showPassword, value,
additionalPropertyNames: new string[]
{
nameof(ShowPasswordIcon)
});
}
public bool ShowCardNumber
{
get => _showCardNumber;
set => SetProperty(ref _showCardNumber, value,
additionalPropertyNames: new string[]
{
nameof(ShowCardNumberIcon)
});
}
2019-04-27 05:37:21 +02:00
public bool ShowCardCode
{
get => _showCardCode;
set => SetProperty(ref _showCardCode, value,
additionalPropertyNames: new string[]
{
nameof(ShowCardCodeIcon)
});
}
2019-04-29 20:35:44 +02:00
public bool IsLogin => Cipher?.Type == Core.Enums.CipherType.Login;
public bool IsIdentity => Cipher?.Type == Core.Enums.CipherType.Identity;
public bool IsCard => Cipher?.Type == Core.Enums.CipherType.Card;
public bool IsSecureNote => Cipher?.Type == Core.Enums.CipherType.SecureNote;
public FormattedString ColoredPassword => PasswordFormatter.FormatPassword(Cipher.Login.Password);
2019-04-30 15:50:35 +02:00
public FormattedString UpdatedText
{
get
{
var fs = new FormattedString();
fs.Spans.Add(new Span
{
Text = string.Format("{0}:", AppResources.DateUpdated),
FontAttributes = FontAttributes.Bold
});
fs.Spans.Add(new Span
{
Text = string.Format(" {0} {1}",
_localizeService.GetLocaleShortDate(Cipher.RevisionDate.ToLocalTime()),
_localizeService.GetLocaleShortTime(Cipher.RevisionDate.ToLocalTime()))
2019-04-30 15:50:35 +02:00
});
return fs;
}
}
public FormattedString PasswordUpdatedText
{
get
{
var fs = new FormattedString();
fs.Spans.Add(new Span
{
Text = string.Format("{0}:", AppResources.DatePasswordUpdated),
FontAttributes = FontAttributes.Bold
});
fs.Spans.Add(new Span
{
Text = string.Format(" {0} {1}",
_localizeService.GetLocaleShortDate(Cipher.PasswordRevisionDisplayDate?.ToLocalTime()),
_localizeService.GetLocaleShortTime(Cipher.PasswordRevisionDisplayDate?.ToLocalTime()))
2019-04-30 15:50:35 +02:00
});
return fs;
}
}
public FormattedString PasswordHistoryText
{
get
{
var fs = new FormattedString();
fs.Spans.Add(new Span
{
Text = string.Format("{0}:", AppResources.PasswordHistory),
FontAttributes = FontAttributes.Bold
});
fs.Spans.Add(new Span
{
Text = string.Format(" {0}", Cipher.PasswordHistory.Count.ToString()),
[Auto Logout] Final review of feature (#932) * Initial commit of LockService name refactor (#831) * [Auto-Logout] Update Service layer logic (#835) * Initial commit of service logic update * Added default value for action * Updated ToggleTokensAsync conditional * Removed unused variables, updated action conditional * Initial commit: lockOption/lock refactor app layer (#840) * [Auto-Logout] Settings Refactor - Application Layer Part 2 (#844) * Initial commit of app layer part 2 * Updated biometrics position * Reverted resource name refactor * LockOptions refactor revert * Updated method casing :: Removed VaultTimeout prefix for timeouts * Fixed dupe string resource (#854) * Updated dependency to use VaultTimeoutService (#896) * [Auto Logout] Xamarin Forms in AutoFill flow (iOS) (#902) * fix typo in PINRequireMasterPasswordRestart (#900) * initial commit for xf usage in autofill * Fixed databinding for hint button * Updated Two Factor page launch - removed unused imports * First pass at broadcast/messenger implentation for autofill * setting theme in extension using theme manager * extension app resources * App resources from main app * fix ref to twoFactorPage * apply resources to page * load empty app for sytling in extension * move ios renderers to ios core * static ref to resources and GetResourceColor helper * fix method ref * move application.current.resources refs to helper * switch login page alerts to device action dialogs * run on main thread * showDialog with device action service * abstract action sheet to device action service * add support for yubikey * add yubikey iimages to extension * support close button action * add support to action extension * remove empty lines Co-authored-by: Jonas Kittner <54631600+theendlessriver13@users.noreply.github.com> Co-authored-by: Kyle Spearrin <kyle.spearrin@gmail.com> * [Auto Logout] Update lock option to be default value (#929) * Initial commit - make lock action default * Removed extra whitespace Co-authored-by: Jonas Kittner <54631600+theendlessriver13@users.noreply.github.com> Co-authored-by: Kyle Spearrin <kyle.spearrin@gmail.com> Co-authored-by: Kyle Spearrin <kspearrin@users.noreply.github.com>
2020-05-29 18:26:36 +02:00
TextColor = ThemeManager.GetResourceColor("PrimaryColor")
2019-04-30 15:50:35 +02:00
});
return fs;
}
}
2019-04-29 20:35:44 +02:00
public bool ShowUris => IsLogin && Cipher.Login.HasUris;
2019-04-29 16:20:29 +02:00
public bool ShowIdentityAddress => IsIdentity && (
2019-04-29 20:35:44 +02:00
!string.IsNullOrWhiteSpace(Cipher.Identity.Address1) ||
!string.IsNullOrWhiteSpace(Cipher.Identity.City) ||
!string.IsNullOrWhiteSpace(Cipher.Identity.Country));
public bool ShowAttachments => Cipher.HasAttachments && (CanAccessPremium || Cipher.OrganizationId != null);
public bool ShowTotp => IsLogin && !string.IsNullOrWhiteSpace(Cipher.Login.Totp) &&
2019-04-26 22:58:20 +02:00
!string.IsNullOrWhiteSpace(TotpCodeFormatted);
2022-01-21 10:31:03 +01:00
public string ShowPasswordIcon => ShowPassword ? BitwardenIcons.EyeSlash : BitwardenIcons.Eye;
public string ShowCardNumberIcon => ShowCardNumber ? BitwardenIcons.EyeSlash : BitwardenIcons.Eye;
public string ShowCardCodeIcon => ShowCardCode ? BitwardenIcons.EyeSlash : BitwardenIcons.Eye;
2019-04-26 06:26:09 +02:00
public string TotpCodeFormatted
{
get => _totpCodeFormatted;
2019-04-26 22:58:20 +02:00
set => SetProperty(ref _totpCodeFormatted, value,
additionalPropertyNames: new string[]
{
nameof(ShowTotp)
});
2019-04-26 06:26:09 +02:00
}
public string TotpSec
{
get => _totpSec;
set => SetProperty(ref _totpSec, value);
}
public bool TotpLow
{
get => _totpLow;
set
{
SetProperty(ref _totpLow, value);
2022-04-26 17:21:17 +02:00
Page.Resources["textTotp"] = ThemeManager.Resources()[value ? "text-danger" : "text-default"];
}
2019-04-26 06:26:09 +02:00
}
public bool IsDeleted => Cipher.IsDeleted;
public bool CanEdit => !Cipher.IsDeleted;
2019-04-24 17:23:03 +02:00
2019-06-05 23:25:12 +02:00
public async Task<bool> LoadAsync(Action finishedLoadingAction = null)
2019-04-24 17:23:03 +02:00
{
2019-04-26 06:26:09 +02:00
CleanUp();
2019-04-24 17:23:03 +02:00
var cipher = await _cipherService.GetAsync(CipherId);
if (cipher == null)
2019-05-30 14:40:10 +02:00
{
2019-06-05 23:25:12 +02:00
finishedLoadingAction?.Invoke();
2019-05-30 14:40:10 +02:00
return false;
}
2019-04-24 17:23:03 +02:00
Cipher = await cipher.DecryptAsync();
Account Switching (#1807) * Account Switching (#1720) * Account switching * WIP * wip * wip * updates to send test logic * fixed Send tests * fixes for theme handling on account switching and re-adding existing account * switch fixes * fixes * fixes * cleanup * vault timeout fixes * account list status enhancements * logout fixes and token handling improvements * merge latest (#1727) * remove duplicate dependency * fix for initial login token storage paradox (#1730) * Fix avatar color update toolbar item issue on iOS for account switching (#1735) * Updated account switching menu UI (#1733) * updated account switching menu UI * additional changes * add key suffix to constant * GetFirstLetters method tweaks * Fix crash on account switching when logging out when having more than user at a time (#1740) * single account migration to multi-account on app update (#1741) * Account Switching Tap to dismiss (#1743) * Added tap to dismiss on the Account switching overlay and improved a bit the code * Fix account switching overlay background transparent on the proper place * Fixed transparent background and the shadow on the account switching overlay * Fix iOS top space on Account switching list overlay after modal (#1746) * Fix top space added to Account switching list overlay after closing modal * Fix top space added to Account switching list overlay after closing modal on lock, login and home views just in case we add modals in the future there as well * Usability: dismiss account list on certain events (#1748) * dismiss account list on certain events * use new FireAndForget method for back button logic * Create and use Account Switching overlay control (#1753) * Added Account switching overlay control and its own ViewModel and refactored accordingly * Fix account switching Accounts list binding update * Implemented dismiss account switching overlay when changing tabs and when selecting the same tab. Also updated the deprecated listener on CustomTabbedRenderer on Android (#1755) * Overriden Equals on AvatarImageSource so it doesn't get set multiple times when it's the same image thus producing blinking on tab chaged (#1756) * Usability improvements for logout on vault timeout (#1781) * accountswitching fixes (#1784) * Fix for invalid PIN lock state when switching accounts (#1792) * fix for pin lock flow * named tuple values and updated async * clear send service cache on account switch (#1796) * Global theme and account removal (#1793) * Global theme and account removal * remove redundant call to hide account list overlay * cleanup and additional tweaks * add try/catch to remove account dialog flow Co-authored-by: Federico Maccaroni <fedemkr@gmail.com>
2022-02-23 18:40:17 +01:00
CanAccessPremium = await _stateService.CanAccessPremiumAsync();
Fields = Cipher.Fields?.Select(f => new ViewPageFieldViewModel(this, Cipher, f)).ToList();
2019-04-24 17:23:03 +02:00
if (Cipher.Type == Core.Enums.CipherType.Login && !string.IsNullOrWhiteSpace(Cipher.Login.Totp) &&
2019-04-26 06:26:09 +02:00
(Cipher.OrganizationUseTotp || CanAccessPremium))
{
await TotpUpdateCodeAsync();
var interval = _totpService.GetTimeInterval(Cipher.Login.Totp);
await TotpTickAsync(interval);
_totpInterval = DateTime.UtcNow;
Device.StartTimer(new TimeSpan(0, 0, 1), () =>
{
if (_totpInterval == null)
2019-04-26 06:26:09 +02:00
{
return false;
}
var task = TotpTickAsync(interval);
return true;
});
}
if (_previousCipherId != CipherId)
2019-07-12 23:29:40 +02:00
{
var task = _eventService.CollectAsync(Core.Enums.EventType.Cipher_ClientViewed, CipherId);
}
_previousCipherId = CipherId;
2019-06-05 23:25:12 +02:00
finishedLoadingAction?.Invoke();
2019-05-30 14:40:10 +02:00
return true;
2019-04-26 06:26:09 +02:00
}
public void CleanUp()
{
_totpInterval = null;
}
public async void TogglePassword()
2019-04-27 05:37:21 +02:00
{
2022-04-26 17:21:17 +02:00
if (!await PromptPasswordAsync())
{
return;
}
2019-04-27 05:37:21 +02:00
ShowPassword = !ShowPassword;
if (ShowPassword)
2019-07-12 23:29:40 +02:00
{
var task = _eventService.CollectAsync(Core.Enums.EventType.Cipher_ClientToggledPasswordVisible, CipherId);
}
2019-04-27 05:37:21 +02:00
}
public async void ToggleCardNumber()
{
if (!await PromptPasswordAsync())
{
return;
}
ShowCardNumber = !ShowCardNumber;
if (ShowCardNumber)
{
var task = _eventService.CollectAsync(
Core.Enums.EventType.Cipher_ClientToggledCardNumberVisible, CipherId);
}
}
public async void ToggleCardCode()
2019-04-27 05:37:21 +02:00
{
if (!await PromptPasswordAsync())
{
return;
}
2019-04-27 05:37:21 +02:00
ShowCardCode = !ShowCardCode;
if (ShowCardCode)
2019-07-12 23:29:40 +02:00
{
var task = _eventService.CollectAsync(
Core.Enums.EventType.Cipher_ClientToggledCardCodeVisible, CipherId);
}
2019-04-27 05:37:21 +02:00
}
2019-05-10 19:47:59 +02:00
public async Task<bool> DeleteAsync()
{
if (Xamarin.Essentials.Connectivity.NetworkAccess == Xamarin.Essentials.NetworkAccess.None)
{
await _platformUtilsService.ShowDialogAsync(AppResources.InternetConnectionRequiredMessage,
AppResources.InternetConnectionRequiredTitle);
return false;
}
var confirmed = await _platformUtilsService.ShowDialogAsync(
Cipher.IsDeleted ? AppResources.DoYouReallyWantToPermanentlyDeleteCipher : AppResources.DoYouReallyWantToSoftDeleteCipher,
2019-05-30 14:35:50 +02:00
null, AppResources.Yes, AppResources.Cancel);
if (!confirmed)
2019-05-10 19:47:59 +02:00
{
return false;
}
try
{
await _deviceActionService.ShowLoadingAsync(Cipher.IsDeleted ? AppResources.Deleting : AppResources.SoftDeleting);
if (Cipher.IsDeleted)
{
await _cipherService.DeleteWithServerAsync(Cipher.Id);
}
else
{
await _cipherService.SoftDeleteWithServerAsync(Cipher.Id);
}
await _deviceActionService.HideLoadingAsync();
_platformUtilsService.ShowToast("success", null,
Cipher.IsDeleted ? AppResources.ItemDeleted : AppResources.ItemSoftDeleted);
_messagingService.Send(Cipher.IsDeleted ? "deletedCipher" : "softDeletedCipher", Cipher);
return true;
}
catch (ApiException e)
{
await _deviceActionService.HideLoadingAsync();
if (e?.Error != null)
{
await _platformUtilsService.ShowDialogAsync(e.Error.GetSingleMessage(),
AppResources.AnErrorHasOccurred);
}
}
return false;
}
public async Task<bool> RestoreAsync()
{
if (!IsDeleted)
{
return false;
}
if (Xamarin.Essentials.Connectivity.NetworkAccess == Xamarin.Essentials.NetworkAccess.None)
{
await _platformUtilsService.ShowDialogAsync(AppResources.InternetConnectionRequiredMessage,
AppResources.InternetConnectionRequiredTitle);
return false;
}
var confirmed = await _platformUtilsService.ShowDialogAsync(AppResources.DoYouReallyWantToRestoreCipher,
null, AppResources.Yes, AppResources.Cancel);
if (!confirmed)
{
return false;
}
try
{
await _deviceActionService.ShowLoadingAsync(AppResources.Restoring);
await _cipherService.RestoreWithServerAsync(Cipher.Id);
2019-05-10 19:47:59 +02:00
await _deviceActionService.HideLoadingAsync();
_platformUtilsService.ShowToast("success", null, AppResources.ItemRestored);
_messagingService.Send("restoredCipher", Cipher);
2019-05-10 19:47:59 +02:00
return true;
}
catch (ApiException e)
2019-05-10 19:47:59 +02:00
{
await _deviceActionService.HideLoadingAsync();
if (e?.Error != null)
2019-10-22 22:37:40 +02:00
{
await _platformUtilsService.ShowDialogAsync(e.Error.GetSingleMessage(),
AppResources.AnErrorHasOccurred);
}
2019-05-10 19:47:59 +02:00
}
return false;
}
2019-04-26 06:26:09 +02:00
private async Task TotpUpdateCodeAsync()
{
if (Cipher == null || Cipher.Type != Core.Enums.CipherType.Login || Cipher.Login.Totp == null)
2019-04-26 06:26:09 +02:00
{
_totpInterval = null;
return;
}
_totpCode = await _totpService.GetCodeAsync(Cipher.Login.Totp);
if (_totpCode != null)
2019-04-26 06:26:09 +02:00
{
if (_totpCode.Length > 4)
2019-04-26 06:26:09 +02:00
{
var half = (int)Math.Floor(_totpCode.Length / 2M);
TotpCodeFormatted = string.Format("{0} {1}", _totpCode.Substring(0, half),
_totpCode.Substring(half));
}
else
{
TotpCodeFormatted = _totpCode;
}
}
else
{
TotpCodeFormatted = null;
_totpInterval = null;
}
}
private async Task TotpTickAsync(int intervalSeconds)
{
var epoc = CoreHelpers.EpocUtcNow() / 1000;
var mod = epoc % intervalSeconds;
var totpSec = intervalSeconds - mod;
TotpSec = totpSec.ToString();
TotpLow = totpSec < 7;
if (mod == 0)
2019-04-26 06:26:09 +02:00
{
await TotpUpdateCodeAsync();
}
}
2019-04-27 06:19:44 +02:00
private async void CheckPasswordAsync()
{
if (!(Page as BaseContentPage).DoOnce())
2019-05-07 05:22:48 +02:00
{
return;
}
if (string.IsNullOrWhiteSpace(Cipher.Login?.Password))
2019-04-27 06:19:44 +02:00
{
return;
}
if (Xamarin.Essentials.Connectivity.NetworkAccess == Xamarin.Essentials.NetworkAccess.None)
{
await _platformUtilsService.ShowDialogAsync(AppResources.InternetConnectionRequiredMessage,
AppResources.InternetConnectionRequiredTitle);
return;
}
2019-04-27 06:19:44 +02:00
await _deviceActionService.ShowLoadingAsync(AppResources.CheckingPassword);
var matches = await _auditService.PasswordLeakedAsync(Cipher.Login.Password);
await _deviceActionService.HideLoadingAsync();
if (matches > 0)
2019-04-27 06:19:44 +02:00
{
2019-05-01 16:33:48 +02:00
await _platformUtilsService.ShowDialogAsync(string.Format(AppResources.PasswordExposed,
matches.ToString("N0")));
2019-04-27 06:19:44 +02:00
}
else
{
await _platformUtilsService.ShowDialogAsync(AppResources.PasswordSafe);
}
}
2019-04-29 20:35:44 +02:00
private async void DownloadAttachmentAsync(AttachmentView attachment)
{
if (!(Page as BaseContentPage).DoOnce())
2019-05-07 05:22:48 +02:00
{
return;
}
if (Xamarin.Essentials.Connectivity.NetworkAccess == Xamarin.Essentials.NetworkAccess.None)
{
await _platformUtilsService.ShowDialogAsync(AppResources.InternetConnectionRequiredMessage,
AppResources.InternetConnectionRequiredTitle);
return;
}
if (Cipher.OrganizationId == null && !CanAccessPremium)
2019-04-29 20:35:44 +02:00
{
await _platformUtilsService.ShowDialogAsync(AppResources.PremiumRequired);
return;
}
if (attachment.FileSize >= 10485760) // 10 MB
{
var confirmed = await _platformUtilsService.ShowDialogAsync(
string.Format(AppResources.AttachmentLargeWarning, attachment.SizeName), null,
AppResources.Yes, AppResources.No);
if (!confirmed)
{
return;
}
2019-04-29 20:35:44 +02:00
}
var canOpenFile = true;
if (!_deviceActionService.CanOpenFile(attachment.FileName))
{
if (Device.RuntimePlatform == Device.iOS)
{
// iOS is currently hardcoded to always return CanOpenFile == true, but should it ever return false
// for any reason we want to be sure to catch it here.
await _platformUtilsService.ShowDialogAsync(AppResources.UnableToOpenFile);
return;
}
canOpenFile = false;
}
2021-06-11 15:20:21 +02:00
if (!await PromptPasswordAsync())
{
return;
}
2019-04-29 20:35:44 +02:00
await _deviceActionService.ShowLoadingAsync(AppResources.Downloading);
try
{
var data = await _cipherService.DownloadAndDecryptAttachmentAsync(Cipher.Id, attachment, Cipher.OrganizationId);
await _deviceActionService.HideLoadingAsync();
if (data == null)
{
await _platformUtilsService.ShowDialogAsync(AppResources.UnableToDownloadFile);
return;
}
if (Device.RuntimePlatform == Device.Android)
{
if (canOpenFile)
{
// We can open this attachment directly, so give the user the option to open or save
PromptOpenOrSave(data, attachment);
}
else
{
// We can't open this attachment so go directly to save
SaveAttachment(data, attachment);
}
}
else
{
OpenAttachment(data, attachment);
}
}
catch
{
await _deviceActionService.HideLoadingAsync();
}
2019-04-29 20:35:44 +02:00
}
public async void PromptOpenOrSave(byte[] data, AttachmentView attachment)
{
var selection = await Page.DisplayActionSheet(attachment.FileName, AppResources.Cancel, null,
AppResources.Open, AppResources.Save);
if (selection == AppResources.Open)
{
OpenAttachment(data, attachment);
}
else if (selection == AppResources.Save)
{
SaveAttachment(data, attachment);
}
}
public async void OpenAttachment(byte[] data, AttachmentView attachment)
{
if (!_deviceActionService.OpenFile(data, attachment.Id, attachment.FileName))
{
await _platformUtilsService.ShowDialogAsync(AppResources.UnableToOpenFile);
return;
}
}
public async void SaveAttachment(byte[] data, AttachmentView attachment)
{
_attachmentData = data;
_attachmentFilename = attachment.FileName;
if (!_deviceActionService.SaveFile(_attachmentData, null, _attachmentFilename, null))
{
ClearAttachmentData();
await _platformUtilsService.ShowDialogAsync(AppResources.UnableToSaveAttachment);
}
}
public async void SaveFileSelected(string contentUri, string filename)
{
if (_deviceActionService.SaveFile(_attachmentData, null, filename ?? _attachmentFilename, contentUri))
{
ClearAttachmentData();
_platformUtilsService.ShowToast("success", null, AppResources.SaveAttachmentSuccess);
return;
}
ClearAttachmentData();
await _platformUtilsService.ShowDialogAsync(AppResources.UnableToSaveAttachment);
}
private void ClearAttachmentData()
{
_attachmentData = null;
_attachmentFilename = null;
}
2022-04-26 17:21:17 +02:00
2019-04-26 22:58:20 +02:00
private async void CopyAsync(string id, string text = null)
2019-04-26 06:26:09 +02:00
{
if (_passwordRepromptService.ProtectedFields.Contains(id) && !await PromptPasswordAsync())
{
return;
}
2019-04-26 06:26:09 +02:00
string name = null;
if (id == "LoginUsername")
2019-04-26 06:26:09 +02:00
{
text = Cipher.Login.Username;
name = AppResources.Username;
}
else if (id == "LoginPassword")
2019-04-26 06:26:09 +02:00
{
text = Cipher.Login.Password;
name = AppResources.Password;
}
else if (id == "LoginTotp")
2019-04-26 06:26:09 +02:00
{
text = _totpCode;
name = AppResources.VerificationCodeTotp;
}
else if (id == "LoginUri")
2019-04-26 22:58:20 +02:00
{
name = AppResources.URI;
}
else if (id == "FieldValue" || id == "H_FieldValue")
2019-04-29 19:51:05 +02:00
{
name = AppResources.Value;
}
else if (id == "CardNumber")
2019-04-29 16:20:29 +02:00
{
text = Cipher.Card.Number;
name = AppResources.Number;
}
else if (id == "CardCode")
2019-04-29 16:20:29 +02:00
{
text = Cipher.Card.Code;
name = AppResources.SecurityCode;
}
2019-04-26 06:26:09 +02:00
if (text != null)
2019-04-26 06:26:09 +02:00
{
await _clipboardService.CopyTextAsync(text);
if (!string.IsNullOrWhiteSpace(name))
2019-04-26 06:26:09 +02:00
{
_platformUtilsService.ShowToast("info", null, string.Format(AppResources.ValueHasBeenCopied, name));
}
if (id == "LoginPassword")
2019-07-12 23:29:40 +02:00
{
await _eventService.CollectAsync(Core.Enums.EventType.Cipher_ClientCopiedPassword, CipherId);
}
else if (id == "CardCode")
2019-07-12 23:29:40 +02:00
{
await _eventService.CollectAsync(Core.Enums.EventType.Cipher_ClientCopiedCardCode, CipherId);
}
else if (id == "H_FieldValue")
2019-07-12 23:29:40 +02:00
{
await _eventService.CollectAsync(Core.Enums.EventType.Cipher_ClientCopiedHiddenField, CipherId);
}
2019-04-26 06:26:09 +02:00
}
2019-04-24 17:23:03 +02:00
}
2019-04-26 22:58:20 +02:00
2019-04-27 06:19:44 +02:00
private void CopyUri(LoginUriView uri)
2019-04-26 22:58:20 +02:00
{
CopyAsync("LoginUri", uri.Uri);
}
2019-04-29 19:51:05 +02:00
private void CopyField(FieldView field)
{
2019-07-12 23:29:40 +02:00
CopyAsync(field.Type == Core.Enums.FieldType.Hidden ? "H_FieldValue" : "FieldValue", field.Value);
2019-04-29 19:51:05 +02:00
}
2019-04-27 06:19:44 +02:00
private void LaunchUri(LoginUriView uri)
2019-04-26 22:58:20 +02:00
{
if (uri.CanLaunch && (Page as BaseContentPage).DoOnce())
2019-04-26 22:58:20 +02:00
{
_platformUtilsService.LaunchUri(uri.LaunchUri);
}
}
internal async Task<bool> PromptPasswordAsync()
{
if (Cipher.Reprompt == CipherRepromptType.None || _passwordReprompted)
{
return true;
}
return _passwordReprompted = await _passwordRepromptService.ShowPasswordPromptAsync();
}
2019-04-24 17:23:03 +02:00
}
2019-04-29 19:56:36 +02:00
public class ViewPageFieldViewModel : ExtendedViewModel
2019-04-29 19:56:36 +02:00
{
private II18nService _i18nService;
private ViewPageViewModel _vm;
2019-04-29 19:56:36 +02:00
private FieldView _field;
2019-07-12 23:29:40 +02:00
private CipherView _cipher;
2019-04-29 19:56:36 +02:00
private bool _showHiddenValue;
public ViewPageFieldViewModel(ViewPageViewModel vm, CipherView cipher, FieldView field)
2019-04-29 19:56:36 +02:00
{
_i18nService = ServiceContainer.Resolve<II18nService>("i18nService");
_vm = vm;
2019-07-12 23:29:40 +02:00
_cipher = cipher;
2019-04-29 19:56:36 +02:00
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 string ValueText
{
get
{
if (IsBooleanType)
{
return _field.Value == "true" ? "" : "";
}
else if (IsLinkedType)
{
var i18nKey = _cipher.LinkedFieldI18nKey(Field.LinkedId.GetValueOrDefault());
return " " + _i18nService.T(i18nKey);
}
else
{
return _field.Value;
}
}
}
public FormattedString ColoredHiddenValue => PasswordFormatter.FormatPassword(_field.Value);
2019-04-29 19:56:36 +02:00
public Command ToggleHiddenValueCommand { get; set; }
2022-01-21 10:31:03 +01:00
public string ShowHiddenValueIcon => _showHiddenValue ? BitwardenIcons.EyeSlash : BitwardenIcons.Eye;
2019-04-29 19:56:36 +02:00
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 IsLinkedType => _field.Type == Core.Enums.FieldType.Linked;
public bool ShowViewHidden => IsHiddenType && _cipher.ViewPassword;
2019-04-29 19:56:36 +02:00
public bool ShowCopyButton => _field.Type != Core.Enums.FieldType.Boolean &&
!string.IsNullOrWhiteSpace(_field.Value) &&
!(IsHiddenType && !_cipher.ViewPassword) &&
_field.Type != FieldType.Linked;
2019-04-29 19:56:36 +02:00
public async void ToggleHiddenValue()
2019-04-29 19:56:36 +02:00
{
if (!await _vm.PromptPasswordAsync())
{
return;
}
2019-04-29 19:56:36 +02:00
ShowHiddenValue = !ShowHiddenValue;
if (ShowHiddenValue)
2019-07-12 23:29:40 +02:00
{
var eventService = ServiceContainer.Resolve<IEventService>("eventService");
var task = eventService.CollectAsync(
Core.Enums.EventType.Cipher_ClientToggledHiddenFieldVisible, _cipher.Id);
}
2019-04-29 19:56:36 +02:00
}
}
2019-04-24 17:23:03 +02:00
}