Bitwarden-app-android-iphon.../src/iOS.ShareExtension/LoadingViewController.cs

Ignoring revisions in .git-blame-ignore-revs. Click here to bypass and see the normal blame view.

466 lines
16 KiB
C#
Raw Normal View History

using System;
using System.Diagnostics;
using System.Linq;
using System.Threading.Tasks;
using AuthenticationServices;
using Bit.App.Abstractions;
using Bit.App.Models;
using Bit.App.Pages;
using Bit.App.Utilities;
using Bit.App.Utilities.AccountManagement;
using Bit.Core.Abstractions;
using Bit.Core.Enums;
using Bit.Core.Services;
using Bit.Core.Utilities;
using Bit.iOS.Core.Controllers;
using Bit.iOS.Core.Utilities;
using Bit.iOS.Core.Views;
using Bit.iOS.ShareExtension.Models;
using CoreNFC;
using Foundation;
using MobileCoreServices;
using UIKit;
using Xamarin.Forms;
namespace Bit.iOS.ShareExtension
{
public partial class LoadingViewController : ExtendedUIViewController, IAccountsManagerHost
{
const string STORYBOARD_NAME = "MainInterface";
private Context _context = new Context();
private NFCNdefReaderSession _nfcSession = null;
private Core.NFCReaderDelegate _nfcDelegate = null;
private IAccountsManager _accountsManager;
readonly LazyResolve<IStateService> _stateService = new LazyResolve<IStateService>("stateService");
readonly LazyResolve<IVaultTimeoutService> _vaultTimeoutService = new LazyResolve<IVaultTimeoutService>("vaultTimeoutService");
Lazy<UIStoryboard> _storyboard = new Lazy<UIStoryboard>(() => UIStoryboard.FromName(STORYBOARD_NAME, null));
private readonly Lazy<AppOptions> _appOptions = new Lazy<AppOptions>(() => new AppOptions { IosExtension = true });
private App.App _app = null;
private UIViewController _currentModalController;
private bool _presentingOnNavigationPage;
private ExtensionNavigationController ExtNavigationController
{
get
{
NavigationController.PresentationController.Delegate =
new CustomPresentationControllerDelegate(CompleteRequest);
return NavigationController as ExtensionNavigationController;
}
}
public LoadingViewController(IntPtr handle)
: base(handle)
{ }
public override void ViewDidLoad()
{
iOSCoreHelpers.InitApp(this, Bit.Core.Constants.iOSShareExtensionClearCiphersCacheKey,
_nfcSession, out _nfcDelegate, out _accountsManager);
base.ViewDidLoad();
Logo.Image = new UIImage(ThemeHelpers.LightTheme ? "logo.png" : "logo_white.png");
View.BackgroundColor = ThemeHelpers.SplashBackgroundColor;
_context.ExtensionContext = ExtensionContext;
_context.ProviderType = GetProviderTypeFromExtensionInputItems();
}
/// <summary>
/// Gets the provider <see cref="UTType"/> given the input items
/// </summary>
private string GetProviderTypeFromExtensionInputItems()
{
foreach (var item in ExtensionContext.InputItems)
{
foreach (var itemProvider in item.Attachments)
{
if (itemProvider.HasItemConformingTo(UTType.PlainText))
{
return UTType.PlainText;
}
if (itemProvider.HasItemConformingTo(UTType.Data))
{
return UTType.Data;
}
}
}
return null;
}
public override async void ViewDidAppear(bool animated)
{
base.ViewDidAppear(animated);
try
{
if (!await IsAuthed())
{
await _accountsManager.NavigateOnAccountChangeAsync(false);
return;
}
else if (await IsLocked())
{
NavigateToLockViewController();
}
else
{
ContinueOnAsync().FireAndForget();
}
}
catch (Exception ex)
{
LoggerHelper.LogEvenIfCantBeResolved(ex);
}
}
void NavigateToLockViewController()
{
var viewController = _storyboard.Value.InstantiateViewController("lockVC") as LockPasswordViewController;
viewController.LoadingController = this;
viewController.ModalPresentationStyle = UIModalPresentationStyle.FullScreen;
if (_presentingOnNavigationPage)
{
_presentingOnNavigationPage = false;
DismissViewController(true, () => ExtNavigationController.PushViewController(viewController, true));
}
else
{
ExtNavigationController.PushViewController(viewController, true);
}
}
public void DismissLockAndContinue()
{
Debug.WriteLine("BW Log, Dismissing lock controller.");
ClearBeforeNavigating();
DismissViewController(false, () => ContinueOnAsync().FireAndForget());
}
private void DismissAndLaunch(Action pageToLaunch)
{
ClearBeforeNavigating();
DismissViewController(false, pageToLaunch);
}
void ClearBeforeNavigating()
{
_currentModalController?.Dispose();
_currentModalController = null;
if (_storyboard.IsValueCreated)
{
_storyboard.Value.Dispose();
_storyboard = null;
_storyboard = new Lazy<UIStoryboard>(() => UIStoryboard.FromName(STORYBOARD_NAME, null));
}
}
private async Task ContinueOnAsync()
{
Tuple<SendType, string, byte[], string> createSend = null;
if (_context.ProviderType == UTType.Data)
{
var (filename, fileBytes) = await LoadDataBytesAsync();
createSend = new Tuple<SendType, string, byte[], string>(SendType.File, filename, fileBytes, null);
}
else if (_context.ProviderType == UTType.PlainText)
{
createSend = new Tuple<SendType, string, byte[], string>(SendType.Text, null, null, LoadText());
}
var appOptions = new AppOptions
{
IosExtension = true,
CreateSend = createSend,
CopyInsteadOfShareAfterSaving = true
};
var sendPage = new SendAddOnlyPage(appOptions)
{
OnClose = () => CompleteRequest(),
AfterSubmit = () => CompleteRequest()
};
SetupAppAndApplyResources(sendPage);
NavigateToPage(sendPage);
}
private void NavigateToPage(ContentPage page)
{
var navigationPage = new NavigationPage(page);
_currentModalController = navigationPage.CreateViewController();
_currentModalController.ModalPresentationStyle = UIModalPresentationStyle.FullScreen;
_presentingOnNavigationPage = true;
PresentViewController(_currentModalController, true, null);
}
private async Task<(string, byte[])> LoadDataBytesAsync()
{
var itemProvider = ExtensionContext?.InputItems.FirstOrDefault()?.Attachments?.FirstOrDefault();
if (itemProvider is null || !itemProvider.HasItemConformingTo(UTType.Data))
return default;
var item = await itemProvider.LoadItemAsync(UTType.Data, null);
if (item is NSUrl urlItem)
{
var filename = urlItem?.AbsoluteUrl?.LastPathComponent;
var data = NSData.FromUrl(urlItem);
var stream = NSInputStream.FromData(data);
var bytes = new byte[data.Length];
try
{
stream.Open();
stream.Read(bytes, data.Length);
}
finally
{
stream?.Close();
}
return (filename, bytes);
}
return default;
}
private string LoadText()
{
return ExtensionContext?.InputItems
.FirstOrDefault()
?.AttributedContentText?.Value;
}
public void CompleteRequest()
{
NSRunLoop.Main.BeginInvokeOnMainThread(() =>
{
ServiceContainer.Reset();
ExtensionContext?.CompleteRequest(new NSExtensionItem[0], null);
});
}
private Task<bool> IsLocked()
{
return _vaultTimeoutService.Value.IsLockedAsync();
}
private Task<bool> IsAuthed()
{
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
return _stateService.Value.IsAuthenticatedAsync();
}
private void LogoutIfAuthed()
{
NSRunLoop.Main.BeginInvokeOnMainThread(async () =>
{
if (await IsAuthed())
{
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
await AppHelpers.LogOutAsync(await _stateService.Value.GetActiveUserIdAsync());
if (UIDevice.CurrentDevice.CheckSystemVersion(12, 0))
{
await ASCredentialIdentityStore.SharedStore?.RemoveAllCredentialIdentitiesAsync();
}
}
});
}
private App.App SetupAppAndApplyResources(ContentPage page)
{
if (_app is null)
{
var app = new App.App(_appOptions.Value);
ThemeManager.SetTheme(app.Resources);
}
ThemeManager.ApplyResourcesTo(page);
return _app;
}
private void LaunchHomePage()
{
var homePage = new HomePage();
SetupAppAndApplyResources(homePage);
if (homePage.BindingContext is HomeViewModel vm)
{
vm.StartLoginAction = () => DismissAndLaunch(() => LaunchLoginFlow());
vm.StartRegisterAction = () => DismissAndLaunch(() => LaunchRegisterFlow());
vm.StartSsoLoginAction = () => DismissAndLaunch(() => LaunchLoginSsoFlow());
vm.StartEnvironmentAction = () => DismissAndLaunch(() => LaunchEnvironmentFlow());
vm.CloseAction = () => CompleteRequest();
}
NavigateToPage(homePage);
LogoutIfAuthed();
}
private void LaunchEnvironmentFlow()
{
var environmentPage = new EnvironmentPage();
SetupAppAndApplyResources(environmentPage);
ThemeManager.ApplyResourcesTo(environmentPage);
if (environmentPage.BindingContext is EnvironmentPageViewModel vm)
{
vm.SubmitSuccessAction = () => DismissAndLaunch(() => LaunchHomePage());
vm.CloseAction = () => DismissAndLaunch(() => LaunchHomePage());
}
NavigateToPage(environmentPage);
}
private void LaunchRegisterFlow()
{
var registerPage = new RegisterPage(null);
SetupAppAndApplyResources(registerPage);
if (registerPage.BindingContext is RegisterPageViewModel vm)
{
vm.RegistrationSuccess = () => DismissAndLaunch(() => LaunchLoginFlow(vm.Email));
vm.CloseAction = () => DismissAndLaunch(() => LaunchHomePage());
}
NavigateToPage(registerPage);
}
private void LaunchLoginFlow(string email = null)
{
var loginPage = new LoginPage(email, _appOptions.Value);
SetupAppAndApplyResources(loginPage);
if (loginPage.BindingContext is LoginPageViewModel vm)
{
vm.StartTwoFactorAction = () => DismissAndLaunch(() => LaunchTwoFactorFlow(false));
vm.UpdateTempPasswordAction = () => DismissAndLaunch(() => LaunchUpdateTempPasswordFlow());
vm.LogInSuccessAction = () =>
{
DismissLockAndContinue();
};
vm.CloseAction = () => CompleteRequest();
}
NavigateToPage(loginPage);
LogoutIfAuthed();
}
private void LaunchLoginSsoFlow()
{
var loginPage = new LoginSsoPage();
SetupAppAndApplyResources(loginPage);
if (loginPage.BindingContext is LoginSsoPageViewModel vm)
{
vm.StartTwoFactorAction = () => DismissAndLaunch(() => LaunchTwoFactorFlow(true));
vm.StartSetPasswordAction = () => DismissAndLaunch(() => LaunchSetPasswordFlow());
vm.UpdateTempPasswordAction = () => DismissAndLaunch(() => LaunchUpdateTempPasswordFlow());
vm.SsoAuthSuccessAction = () => DismissLockAndContinue();
vm.CloseAction = () => DismissAndLaunch(() => LaunchHomePage());
}
NavigateToPage(loginPage);
LogoutIfAuthed();
}
private void LaunchTwoFactorFlow(bool authingWithSso)
{
var twoFactorPage = new TwoFactorPage();
SetupAppAndApplyResources(twoFactorPage);
if (twoFactorPage.BindingContext is TwoFactorPageViewModel vm)
{
vm.TwoFactorAuthSuccessAction = () => DismissLockAndContinue();
vm.StartSetPasswordAction = () => DismissAndLaunch(() => LaunchSetPasswordFlow());
if (authingWithSso)
{
vm.CloseAction = () => DismissAndLaunch(() => LaunchLoginSsoFlow());
}
else
{
vm.CloseAction = () => DismissAndLaunch(() => LaunchLoginFlow());
}
vm.UpdateTempPasswordAction = () => DismissAndLaunch(() => LaunchUpdateTempPasswordFlow());
}
NavigateToPage(twoFactorPage);
}
private void LaunchSetPasswordFlow()
{
var setPasswordPage = new SetPasswordPage();
SetupAppAndApplyResources(setPasswordPage);
if (setPasswordPage.BindingContext is SetPasswordPageViewModel vm)
{
vm.UpdateTempPasswordAction = () => DismissAndLaunch(() => LaunchUpdateTempPasswordFlow());
vm.SetPasswordSuccessAction = () => DismissLockAndContinue();
vm.CloseAction = () => DismissAndLaunch(() => LaunchHomePage());
}
NavigateToPage(setPasswordPage);
}
private void LaunchUpdateTempPasswordFlow()
{
var updateTempPasswordPage = new UpdateTempPasswordPage();
SetupAppAndApplyResources(updateTempPasswordPage);
if (updateTempPasswordPage.BindingContext is UpdateTempPasswordPageViewModel vm)
{
vm.UpdateTempPasswordSuccessAction = () => DismissAndLaunch(() => LaunchHomePage());
vm.LogOutAction = () => DismissAndLaunch(() => LaunchHomePage());
}
NavigateToPage(updateTempPasswordPage);
}
public void Navigate(NavigationTarget navTarget, INavigationParams navParams = null)
{
if (ExtNavigationController?.ViewControllers?.Any() ?? false)
{
ExtNavigationController.PopViewController(false);
}
else if (ExtNavigationController?.ModalViewController != null)
{
ExtNavigationController.DismissModalViewController(false);
}
switch (navTarget)
{
case NavigationTarget.HomeLogin:
ExecuteLaunch(LaunchHomePage);
break;
case NavigationTarget.Login:
if (navParams is LoginNavigationParams loginParams)
{
ExecuteLaunch(() => LaunchLoginFlow(loginParams.Email));
}
else
{
ExecuteLaunch(() => LaunchLoginFlow());
}
break;
case NavigationTarget.Lock:
NavigateToLockViewController();
break;
case NavigationTarget.Home:
DismissLockAndContinue();
break;
}
}
private void ExecuteLaunch(Action launchAction)
{
if (_presentingOnNavigationPage)
{
DismissAndLaunch(launchAction);
}
else
{
launchAction();
}
}
public Task SetPreviousPageInfoAsync() => Task.CompletedTask;
public Task UpdateThemeAsync() => Task.CompletedTask;
}
}