using System; using System.Diagnostics; using Foundation; using UIKit; using Bit.iOS.Core; using Newtonsoft.Json; using Bit.iOS.Extension.Models; using MobileCoreServices; using Bit.iOS.Core.Utilities; using Bit.App.Resources; using Bit.iOS.Core.Controllers; using System.Collections.Generic; using Bit.iOS.Core.Models; using Bit.Core.Utilities; using Bit.Core.Abstractions; namespace Bit.iOS.Extension { public partial class LoadingViewController : ExtendedUIViewController { private Context _context = new Context(); public LoadingViewController(IntPtr handle) : base(handle) { } public override void ViewDidLoad() { InitApp(); base.ViewDidLoad(); Logo.Image = new UIImage(ThemeHelpers.LightTheme ? "logo.png" : "logo_white.png"); View.BackgroundColor = ThemeHelpers.SplashBackgroundColor; _context.ExtContext = ExtensionContext; foreach(var item in ExtensionContext.InputItems) { var processed = false; foreach(var itemProvider in item.Attachments) { if(ProcessWebUrlProvider(itemProvider) || ProcessFindLoginProvider(itemProvider) || ProcessFindLoginBrowserProvider(itemProvider, Constants.UTTypeAppExtensionFillBrowserAction) || ProcessFindLoginBrowserProvider(itemProvider, Constants.UTTypeAppExtensionFillWebViewAction) || ProcessSaveLoginProvider(itemProvider) || ProcessChangePasswordProvider(itemProvider) || ProcessExtensionSetupProvider(itemProvider)) { processed = true; break; } } if(processed) { break; } } } public override void ViewDidAppear(bool animated) { base.ViewDidAppear(animated); if(!IsAuthed()) { var alert = Dialogs.CreateAlert(null, AppResources.MustLogInMainApp, AppResources.Ok, (a) => { CompleteRequest(null); }); PresentViewController(alert, true, null); return; } if(_context.ProviderType == Constants.UTTypeAppExtensionSetup) { PerformSegue("setupSegue", this); return; } if(IsLocked()) { PerformSegue("lockPasswordSegue", this); } else { ContinueOn(); } } public override void PrepareForSegue(UIStoryboardSegue segue, NSObject sender) { if(segue.DestinationViewController is UINavigationController navController) { if(navController.TopViewController is LoginListViewController listLoginController) { listLoginController.Context = _context; listLoginController.LoadingController = this; } else if(navController.TopViewController is LoginAddViewController addLoginController) { addLoginController.Context = _context; addLoginController.LoadingController = this; } else if(navController.TopViewController is LockPasswordViewController passwordViewController) { passwordViewController.LoadingController = this; } else if(navController.TopViewController is SetupViewController setupViewController) { setupViewController.Context = _context; setupViewController.LoadingController = this; } } } public void DismissLockAndContinue() { Debug.WriteLine("BW Log, Dismissing lock controller."); DismissViewController(false, () => ContinueOn()); } private void ContinueOn() { Debug.WriteLine("BW Log, Segue to setup, login add or list."); if(_context.ProviderType == Constants.UTTypeAppExtensionSaveLoginAction) { PerformSegue("newLoginSegue", this); } else if(_context.ProviderType == Constants.UTTypeAppExtensionSetup) { PerformSegue("setupSegue", this); } else { PerformSegue("loginListSegue", this); } } public void CompleteUsernamePasswordRequest(string username, string password, List> fields, string totp) { NSDictionary itemData = null; if(_context.ProviderType == UTType.PropertyList) { var fillScript = new FillScript(_context.Details, username, password, fields); var scriptJson = CoreHelpers.SerializeJson(fillScript, true); var scriptDict = new NSDictionary(Constants.AppExtensionWebViewPageFillScript, scriptJson); itemData = new NSDictionary(NSJavaScriptExtension.FinalizeArgumentKey, scriptDict); } else if(_context.ProviderType == Constants.UTTypeAppExtensionFindLoginAction) { itemData = new NSDictionary( Constants.AppExtensionUsernameKey, username, Constants.AppExtensionPasswordKey, password); } else if(_context.ProviderType == Constants.UTTypeAppExtensionFillBrowserAction || _context.ProviderType == Constants.UTTypeAppExtensionFillWebViewAction) { var fillScript = new FillScript(_context.Details, username, password, fields); var scriptJson = CoreHelpers.SerializeJson(fillScript, true); itemData = new NSDictionary(Constants.AppExtensionWebViewPageFillScript, scriptJson); } else if(_context.ProviderType == Constants.UTTypeAppExtensionSaveLoginAction) { itemData = new NSDictionary( Constants.AppExtensionUsernameKey, username, Constants.AppExtensionPasswordKey, password); } else if(_context.ProviderType == Constants.UTTypeAppExtensionChangePasswordAction) { itemData = new NSDictionary( Constants.AppExtensionPasswordKey, string.Empty, Constants.AppExtensionOldPasswordKey, password); } if(!string.IsNullOrWhiteSpace(totp)) { UIPasteboard.General.String = totp; } CompleteRequest(itemData); } public void CompleteRequest(NSDictionary itemData) { Debug.WriteLine("BW LOG, itemData: " + itemData); var resultsProvider = new NSItemProvider(itemData, UTType.PropertyList); var resultsItem = new NSExtensionItem { Attachments = new NSItemProvider[] { resultsProvider } }; var returningItems = new NSExtensionItem[] { resultsItem }; NSRunLoop.Main.BeginInvokeOnMainThread(() => { ServiceContainer.Reset(); ExtensionContext?.CompleteRequest(returningItems, null); }); } private bool ProcessItemProvider(NSItemProvider itemProvider, string type, Action dictAction, Action urlAction = null) { if(!itemProvider.HasItemConformingTo(type)) { return false; } itemProvider.LoadItem(type, null, (NSObject list, NSError error) => { if(list == null) { return; } _context.ProviderType = type; if(list is NSDictionary dict && dictAction != null) { dictAction(dict); } else if(list is NSUrl && urlAction != null) { var url = list as NSUrl; urlAction(url); } else { throw new Exception("Cannot parse list for action. List is " + (list?.GetType().ToString() ?? "null")); } Debug.WriteLine("BW LOG, ProviderType: " + _context.ProviderType); Debug.WriteLine("BW LOG, Url: " + _context.UrlString); Debug.WriteLine("BW LOG, Title: " + _context.LoginTitle); Debug.WriteLine("BW LOG, Username: " + _context.Username); Debug.WriteLine("BW LOG, Password: " + _context.Password); Debug.WriteLine("BW LOG, Old Password: " + _context.OldPassword); Debug.WriteLine("BW LOG, Notes: " + _context.Notes); Debug.WriteLine("BW LOG, Details: " + _context.Details); if(_context.PasswordOptions != null) { Debug.WriteLine("BW LOG, PasswordOptions Min Length: " + _context.PasswordOptions.MinLength); Debug.WriteLine("BW LOG, PasswordOptions Max Length: " + _context.PasswordOptions.MaxLength); Debug.WriteLine("BW LOG, PasswordOptions Require Digits: " + _context.PasswordOptions.RequireDigits); Debug.WriteLine("BW LOG, PasswordOptions Require Symbols: " + _context.PasswordOptions.RequireSymbols); Debug.WriteLine("BW LOG, PasswordOptions Forbidden Chars: " + _context.PasswordOptions.ForbiddenCharacters); } }); return true; } private bool ProcessWebUrlProvider(NSItemProvider itemProvider) { return ProcessItemProvider(itemProvider, UTType.PropertyList, dict => { var result = dict[NSJavaScriptExtension.PreprocessingResultsKey]; if(result == null) { return; } _context.UrlString = result.ValueForKey(new NSString(Constants.AppExtensionUrlStringKey)) as NSString; var jsonStr = result.ValueForKey(new NSString(Constants.AppExtensionWebViewPageDetails)) as NSString; _context.Details = DeserializeString(jsonStr); }); } private bool ProcessFindLoginProvider(NSItemProvider itemProvider) { return ProcessItemProvider(itemProvider, Constants.UTTypeAppExtensionFindLoginAction, dict => { var version = dict[Constants.AppExtensionVersionNumberKey] as NSNumber; var url = dict[Constants.AppExtensionUrlStringKey] as NSString; if(url != null) { _context.UrlString = url; } }); } private bool ProcessFindLoginBrowserProvider(NSItemProvider itemProvider, string action) { return ProcessItemProvider(itemProvider, action, dict => { var version = dict[Constants.AppExtensionVersionNumberKey] as NSNumber; var url = dict[Constants.AppExtensionUrlStringKey] as NSString; if(url != null) { _context.UrlString = url; } _context.Details = DeserializeDictionary(dict[Constants.AppExtensionWebViewPageDetails] as NSDictionary); }, url => { if(url != null) { _context.UrlString = url.AbsoluteString; } }); } private bool ProcessSaveLoginProvider(NSItemProvider itemProvider) { return ProcessItemProvider(itemProvider, Constants.UTTypeAppExtensionSaveLoginAction, dict => { var version = dict[Constants.AppExtensionVersionNumberKey] as NSNumber; var url = dict[Constants.AppExtensionUrlStringKey] as NSString; var title = dict[Constants.AppExtensionTitleKey] as NSString; var sectionTitle = dict[Constants.AppExtensionSectionTitleKey] as NSString; var username = dict[Constants.AppExtensionUsernameKey] as NSString; var password = dict[Constants.AppExtensionPasswordKey] as NSString; var notes = dict[Constants.AppExtensionNotesKey] as NSString; var fields = dict[Constants.AppExtensionFieldsKey] as NSDictionary; if(url != null) { _context.UrlString = url; } _context.LoginTitle = title; _context.Username = username; _context.Password = password; _context.Notes = notes; _context.PasswordOptions = DeserializeDictionary(dict[Constants.AppExtensionPasswordGeneratorOptionsKey] as NSDictionary); }); } private bool ProcessChangePasswordProvider(NSItemProvider itemProvider) { return ProcessItemProvider(itemProvider, Constants.UTTypeAppExtensionChangePasswordAction, dict => { var version = dict[Constants.AppExtensionVersionNumberKey] as NSNumber; var url = dict[Constants.AppExtensionUrlStringKey] as NSString; var title = dict[Constants.AppExtensionTitleKey] as NSString; var sectionTitle = dict[Constants.AppExtensionSectionTitleKey] as NSString; var username = dict[Constants.AppExtensionUsernameKey] as NSString; var password = dict[Constants.AppExtensionPasswordKey] as NSString; var oldPassword = dict[Constants.AppExtensionOldPasswordKey] as NSString; var notes = dict[Constants.AppExtensionNotesKey] as NSString; var fields = dict[Constants.AppExtensionFieldsKey] as NSDictionary; if(url != null) { _context.UrlString = url; } _context.LoginTitle = title; _context.Username = username; _context.Password = password; _context.OldPassword = oldPassword; _context.Notes = notes; _context.PasswordOptions = DeserializeDictionary(dict[Constants.AppExtensionPasswordGeneratorOptionsKey] as NSDictionary); }); } private bool ProcessExtensionSetupProvider(NSItemProvider itemProvider) { if(itemProvider.HasItemConformingTo(Constants.UTTypeAppExtensionSetup)) { _context.ProviderType = Constants.UTTypeAppExtensionSetup; return true; } return false; } private T DeserializeDictionary(NSDictionary dict) { if(dict != null) { var jsonData = NSJsonSerialization.Serialize( dict, NSJsonWritingOptions.PrettyPrinted, out NSError jsonError); if(jsonData != null) { var jsonString = new NSString(jsonData, NSStringEncoding.UTF8); return DeserializeString(jsonString); } } return default(T); } private T DeserializeString(NSString jsonString) { if(jsonString != null) { var convertedObject = JsonConvert.DeserializeObject(jsonString.ToString()); return convertedObject; } return default(T); } private void InitApp() { iOSCoreHelpers.AppearanceAdjustments(); if(ServiceContainer.RegisteredServices.Count > 0) { return; } iOSCoreHelpers.RegisterLocalServices(); ServiceContainer.Init(); iOSCoreHelpers.RegisterHockeyApp(); iOSCoreHelpers.Bootstrap(); } private bool IsLocked() { var lockService = ServiceContainer.Resolve("lockService"); return lockService.IsLockedAsync().GetAwaiter().GetResult(); } private bool IsAuthed() { var userService = ServiceContainer.Resolve("userService"); return userService.IsAuthenticatedAsync().GetAwaiter().GetResult(); } } }