bitwarden-mobile/src/iOS.ShareExtension/LoadingViewController.cs

466 lines
16 KiB
C#

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()
{
return _stateService.Value.IsAuthenticatedAsync();
}
private void LogoutIfAuthed()
{
NSRunLoop.Main.BeginInvokeOnMainThread(async () =>
{
if (await IsAuthed())
{
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;
}
}