using System; using System.Collections.Generic; using System.Threading.Tasks; using Bit.App.Abstractions; using Bit.App.Pages; using Bit.App.Pages.Accounts; using Bit.Core.Abstractions; using Bit.Core.Enums; using Bit.Core.Utilities; using Xamarin.Forms; namespace Bit.App.Utilities { public interface IVerificationActionsFlowHelper { IVerificationActionsFlowHelper Configure(VerificationFlowAction action, IActionFlowParmeters parameters = null, string verificatioCodeMainActionText = null, bool isVerificationCodeMainActionStyleDanger = false); IActionFlowParmeters GetParameters(); Task ValidateAndExecuteAsync(); Task ExecuteAsync(IActionFlowParmeters parameters); } public interface IActionFlowParmeters { VerificationType VerificationType { get; set; } string Secret { get; set; } } public class DefaultActionFlowParameters : IActionFlowParmeters { public VerificationType VerificationType { get; set; } public string Secret { get; set; } } public interface IActionFlowExecutioner { Task Execute(IActionFlowParmeters parameters); } public enum VerificationFlowAction { ExportVault, DeleteAccount } /// /// Verifies and execute actions on the corresponding order. /// /// Use: From the caller /// - Configure it on /// - Call /// /// For every we need an implementation of /// and it to be configured in the inner dictionary. /// Also, inherit from if more custom parameters are needed for the executioner. /// public class VerificationActionsFlowHelper : IVerificationActionsFlowHelper { private readonly IKeyConnectorService _keyConnectorService; private readonly IPasswordRepromptService _passwordRepromptService; private readonly ICryptoService _cryptoService; private VerificationFlowAction? _action; private IActionFlowParmeters _parameters; private string _verificationCodeMainActionText; private bool _isVerificationCodeMainActionStyleDanger; private readonly Dictionary _actionExecutionerDictionary = new Dictionary(); public VerificationActionsFlowHelper(IKeyConnectorService keyConnectorService, IPasswordRepromptService passwordRepromptService, ICryptoService cryptoService) { _keyConnectorService = keyConnectorService; _passwordRepromptService = passwordRepromptService; _cryptoService = cryptoService; _actionExecutionerDictionary.Add(VerificationFlowAction.DeleteAccount, ServiceContainer.Resolve("deleteAccountActionFlowExecutioner")); } public IVerificationActionsFlowHelper Configure(VerificationFlowAction action, IActionFlowParmeters parameters = null, string verificationCodeMainActionText = null, bool isVerificationCodeMainActionStyleDanger = false) { _action = action; _parameters = parameters; _verificationCodeMainActionText = verificationCodeMainActionText; _isVerificationCodeMainActionStyleDanger = isVerificationCodeMainActionStyleDanger; return this; } public IActionFlowParmeters GetParameters() { if (_parameters is null) { _parameters = new DefaultActionFlowParameters(); } return _parameters; } public async Task ValidateAndExecuteAsync() { var verificationType = await _keyConnectorService.GetUsesKeyConnector() ? VerificationType.OTP : VerificationType.MasterPassword; switch (verificationType) { case VerificationType.MasterPassword: var (password, valid) = await _passwordRepromptService.ShowPasswordPromptAndGetItAsync(); if (!valid) { return; } var parameters = GetParameters(); parameters.Secret = await _cryptoService.HashPasswordAsync(password, null); parameters.VerificationType = VerificationType.MasterPassword; await ExecuteAsync(parameters); break; case VerificationType.OTP: await Application.Current.MainPage.Navigation.PushModalAsync(new NavigationPage( new VerificationCodePage(_verificationCodeMainActionText, _isVerificationCodeMainActionStyleDanger))); break; default: throw new NotImplementedException($"There is no implementation for {verificationType}"); } } /// /// Executes the action with the given parameters after we have gotten the verification secret /// public async Task ExecuteAsync(IActionFlowParmeters parameters) { if (!_action.HasValue) { // this should never happen throw new InvalidOperationException("A problem occurred while getting the action value after validation"); } if (!_actionExecutionerDictionary.TryGetValue(_action.Value, out var executioner)) { throw new InvalidOperationException($"There is no executioner for {_action}"); } await executioner.Execute(GetParameters()); } } }