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 _stateService = new LazyResolve("stateService"); readonly LazyResolve _vaultTimeoutService = new LazyResolve("vaultTimeoutService"); Lazy _storyboard = new Lazy(() => UIStoryboard.FromName(STORYBOARD_NAME, null)); private readonly Lazy _appOptions = new Lazy(() => 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(); } /// /// Gets the provider given the input items /// 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.FromName(STORYBOARD_NAME, null)); } } private async Task ContinueOnAsync() { Tuple createSend = null; if (_context.ProviderType == UTType.Data) { var (filename, fileBytes) = await LoadDataBytesAsync(); createSend = new Tuple(SendType.File, filename, fileBytes, null); } else if (_context.ProviderType == UTType.PlainText) { createSend = new Tuple(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 IsLocked() { return _vaultTimeoutService.Value.IsLockedAsync(); } private Task 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(_appOptions.Value); SetupAppAndApplyResources(homePage); if (homePage.BindingContext is HomeViewModel vm) { vm.StartLoginAction = () => DismissAndLaunch(() => LaunchLoginFlow(vm.Email)); 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.StartSsoLoginAction = () => DismissAndLaunch(() => LaunchLoginSsoFlow()); vm.LogInWithDeviceAction = () => DismissAndLaunch(() => LaunchLoginWithDevice(email)); vm.LogInSuccessAction = () => { DismissLockAndContinue(); }; vm.CloseAction = () => DismissAndLaunch(() => LaunchHomePage()); } NavigateToPage(loginPage); LogoutIfAuthed(); } private void LaunchLoginWithDevice(string email = null) { var loginWithDevicePage = new LoginPasswordlessRequestPage(email, _appOptions.Value); SetupAppAndApplyResources(loginWithDevicePage); if (loginWithDevicePage.BindingContext is LoginPasswordlessRequestViewModel vm) { vm.StartTwoFactorAction = () => DismissAndLaunch(() => LaunchTwoFactorFlow(false)); vm.UpdateTempPasswordAction = () => DismissAndLaunch(() => LaunchUpdateTempPasswordFlow()); vm.LogInSuccessAction = () => { DismissLockAndContinue(); }; vm.CloseAction = () => DismissAndLaunch(() => LaunchHomePage()); } NavigateToPage(loginWithDevicePage); 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; } }