466 lines
16 KiB
C#
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;
|
|
}
|
|
}
|