From 9755d4c79b2c3ddab1b6abf4308b34c429380cef Mon Sep 17 00:00:00 2001 From: Kyle Spearrin Date: Sat, 4 Jun 2016 17:04:49 -0400 Subject: [PATCH] Split extension up into smaller parts. Process in Loading controller. Response in action controller. --- src/iOS.Core/Constants.cs | 31 ++ src/iOS.Core/iOS.Core.csproj | 1 + src/iOS.Extension/ActionViewController.cs | 396 ++---------------- src/iOS.Extension/LoadingViewController.cs | 282 +++++++++++++ ...r.cs => LoadingViewController.designer.cs} | 4 +- src/iOS.Extension/MainInterface.storyboard | 18 +- src/iOS.Extension/Models/Context.cs | 19 + src/iOS.Extension/Models/FillScript.cs | 71 ++++ src/iOS.Extension/Models/PageDetails.cs | 46 ++ .../Models/PasswordGenerationOptions.cs | 13 + src/iOS.Extension/SplashViewController.cs | 111 ----- src/iOS.Extension/iOS.Extension.csproj | 10 +- 12 files changed, 512 insertions(+), 490 deletions(-) create mode 100644 src/iOS.Core/Constants.cs create mode 100644 src/iOS.Extension/LoadingViewController.cs rename src/iOS.Extension/{SplashViewController.designer.cs => LoadingViewController.designer.cs} (82%) create mode 100644 src/iOS.Extension/Models/Context.cs create mode 100644 src/iOS.Extension/Models/FillScript.cs create mode 100644 src/iOS.Extension/Models/PageDetails.cs create mode 100644 src/iOS.Extension/Models/PasswordGenerationOptions.cs delete mode 100644 src/iOS.Extension/SplashViewController.cs diff --git a/src/iOS.Core/Constants.cs b/src/iOS.Core/Constants.cs new file mode 100644 index 000000000..d44f85062 --- /dev/null +++ b/src/iOS.Core/Constants.cs @@ -0,0 +1,31 @@ +namespace Bit.iOS.Core +{ + public static class Constants + { + public const string AppExtensionVersionNumberKey = "version_number"; + public const string AppExtensionUrlStringKey = "url_string"; + public const string AppExtensionUsernameKey = "username"; + public const string AppExtensionPasswordKey = "password"; + public const string AppExtensionTotpKey = "totp"; + public const string AppExtensionTitleKey = "login_title"; + public const string AppExtensionNotesKey = "notes"; + public const string AppExtensionSectionTitleKey = "section_title"; + public const string AppExtensionFieldsKey = "fields"; + public const string AppExtensionReturnedFieldsKey = "returned_fields"; + public const string AppExtensionOldPasswordKey = "old_password"; + public const string AppExtensionPasswordGeneratorOptionsKey = "password_generator_options"; + public const string AppExtensionGeneratedPasswordMinLengthKey = "password_min_length"; + public const string AppExtensionGeneratedPasswordMaxLengthKey = "password_max_length"; + public const string AppExtensionGeneratedPasswordRequireDigitsKey = "password_require_digits"; + public const string AppExtensionGeneratedPasswordRequireSymbolsKey = "password_require_symbols"; + public const string AppExtensionGeneratedPasswordForbiddenCharactersKey = "password_forbidden_characters"; + public const string AppExtensionWebViewPageFillScript = "fillScript"; + public const string AppExtensionWebViewPageDetails = "pageDetails"; + + public const string UTTypeAppExtensionFindLoginAction = "org.appextension.find-login-action"; + public const string UTTypeAppExtensionSaveLoginAction = "org.appextension.save-login-action"; + public const string UTTypeAppExtensionChangePasswordAction = "org.appextension.change-password-action"; + public const string UTTypeAppExtensionFillWebViewAction = "org.appextension.fill-webview-action"; + public const string UTTypeAppExtensionFillBrowserAction = "org.appextension.fill-browser-action"; + } +} diff --git a/src/iOS.Core/iOS.Core.csproj b/src/iOS.Core/iOS.Core.csproj index df18dbea6..cd1a82d61 100644 --- a/src/iOS.Core/iOS.Core.csproj +++ b/src/iOS.Core/iOS.Core.csproj @@ -46,6 +46,7 @@ + diff --git a/src/iOS.Extension/ActionViewController.cs b/src/iOS.Extension/ActionViewController.cs index 8ca145636..4f0a46727 100644 --- a/src/iOS.Extension/ActionViewController.cs +++ b/src/iOS.Extension/ActionViewController.cs @@ -1,7 +1,6 @@ using System; -using System.Collections.Generic; -using System.Diagnostics; -using System.Linq; +using Bit.iOS.Core; +using Bit.iOS.Extension.Models; using Foundation; using MobileCoreServices; using Newtonsoft.Json; @@ -11,404 +10,69 @@ namespace Bit.iOS.Extension { public partial class ActionViewController : UIViewController { - private const string AppExtensionVersionNumberKey = "version_number"; - - private const string AppExtensionUrlStringKey = "url_string"; - private const string AppExtensionUsernameKey = "username"; - private const string AppExtensionPasswordKey = "password"; - private const string AppExtensionTotpKey = "totp"; - private const string AppExtensionTitleKey = "login_title"; - private const string AppExtensionNotesKey = "notes"; - private const string AppExtensionSectionTitleKey = "section_title"; - private const string AppExtensionFieldsKey = "fields"; - private const string AppExtensionReturnedFieldsKey = "returned_fields"; - private const string AppExtensionOldPasswordKey = "old_password"; - private const string AppExtensionPasswordGeneratorOptionsKey = "password_generator_options"; - - private const string AppExtensionGeneratedPasswordMinLengthKey = "password_min_length"; - private const string AppExtensionGeneratedPasswordMaxLengthKey = "password_max_length"; - private const string AppExtensionGeneratedPasswordRequireDigitsKey = "password_require_digits"; - private const string AppExtensionGeneratedPasswordRequireSymbolsKey = "password_require_symbols"; - private const string AppExtensionGeneratedPasswordForbiddenCharactersKey = "password_forbidden_characters"; - - private const string AppExtensionWebViewPageFillScript = "fillScript"; - private const string AppExtensionWebViewPageDetails = "pageDetails"; - - private const string UTTypeAppExtensionFindLoginAction = "org.appextension.find-login-action"; - private const string UTTypeAppExtensionSaveLoginAction = "org.appextension.save-login-action"; - private const string UTTypeAppExtensionChangePasswordAction = "org.appextension.change-password-action"; - private const string UTTypeAppExtensionFillWebViewAction = "org.appextension.fill-webview-action"; - private const string UTTypeAppExtensionFillBrowserAction = "org.appextension.fill-browser-action"; - public ActionViewController(IntPtr handle) : base(handle) { } - public NSExtensionContext Context { get; set; } - public string ProviderType { get; set; } - public Uri Url { get; set; } - public string SiteTitle { get; set; } - public string Username { get; set; } - public string Password { get; set; } - public string OldPassword { get; set; } - public string Notes { get; set; } - public PasswordGenerationOptions PasswordOptions { get; set; } - public PageDetails Details { get; set; } + public Context Context { get; set; } public override void ViewDidLoad() { base.ViewDidLoad(); View.BackgroundColor = UIColor.FromPatternImage(new UIImage("boxed-bg.png")); - - foreach(var item in Context.InputItems) - { - var processed = false; - foreach(var itemProvider in item.Attachments) - { - if(ProcessWebUrlProvider(itemProvider) - || ProcessFindLoginProvider(itemProvider) - || ProcessFindLoginBrowserProvider(itemProvider, UTTypeAppExtensionFillBrowserAction) - || ProcessFindLoginBrowserProvider(itemProvider, UTTypeAppExtensionFillWebViewAction) - || ProcessSaveLoginProvider(itemProvider) - || ProcessChangePasswordProvider(itemProvider)) - { - processed = true; - break; - } - } - - if(processed) - { - break; - } - } } - partial void CancelClicked (UIBarButtonItem sender) - { - Context.CompleteRequest(null, null); - } + partial void CancelClicked(UIBarButtonItem sender) + { + CompleteRequest(null); + } partial void DoneClicked(NSObject sender) { NSDictionary itemData = null; - if(ProviderType == UTType.PropertyList) + if(Context.ProviderType == UTType.PropertyList) { - var fillScript = new FillScript(Details); + var fillScript = new FillScript(Context.Details); var scriptJson = JsonConvert.SerializeObject(fillScript, new JsonSerializerSettings { NullValueHandling = NullValueHandling.Ignore }); - var scriptDict = new NSDictionary(AppExtensionWebViewPageFillScript, scriptJson); + var scriptDict = new NSDictionary(Constants.AppExtensionWebViewPageFillScript, scriptJson); itemData = new NSDictionary(NSJavaScriptExtension.FinalizeArgumentKey, scriptDict); } - if(ProviderType == UTTypeAppExtensionFindLoginAction) + if(Context.ProviderType == Constants.UTTypeAppExtensionFindLoginAction) { itemData = new NSDictionary( - AppExtensionUsernameKey, "me@example.com", - AppExtensionPasswordKey, "mypassword"); + Constants.AppExtensionUsernameKey, "me@example.com", + Constants.AppExtensionPasswordKey, "mypassword"); } - else if(ProviderType == UTTypeAppExtensionFillBrowserAction - || ProviderType == UTTypeAppExtensionFillWebViewAction) + else if(Context.ProviderType == Constants.UTTypeAppExtensionFillBrowserAction + || Context.ProviderType == Constants.UTTypeAppExtensionFillWebViewAction) { - var fillScript = new FillScript(Details); + var fillScript = new FillScript(Context.Details); var scriptJson = JsonConvert.SerializeObject(fillScript, new JsonSerializerSettings { NullValueHandling = NullValueHandling.Ignore }); - itemData = new NSDictionary(AppExtensionWebViewPageFillScript, scriptJson); + itemData = new NSDictionary(Constants.AppExtensionWebViewPageFillScript, scriptJson); } - else if(ProviderType == UTTypeAppExtensionSaveLoginAction) + else if(Context.ProviderType == Constants.UTTypeAppExtensionSaveLoginAction) { itemData = new NSDictionary( - AppExtensionUsernameKey, "me@example.com", - AppExtensionPasswordKey, "mypassword"); + Constants.AppExtensionUsernameKey, "me@example.com", + Constants.AppExtensionPasswordKey, "mypassword"); } - else if(ProviderType == UTTypeAppExtensionChangePasswordAction) + else if(Context.ProviderType == Constants.UTTypeAppExtensionChangePasswordAction) { itemData = new NSDictionary( - AppExtensionPasswordKey, "mynewpassword", - AppExtensionOldPasswordKey, "myoldpassword"); + Constants.AppExtensionPasswordKey, "mynewpassword", + Constants.AppExtensionOldPasswordKey, "myoldpassword"); } + CompleteRequest(itemData); + } + + private void CompleteRequest(NSDictionary itemData) + { var resultsProvider = new NSItemProvider(itemData, UTType.PropertyList); var resultsItem = new NSExtensionItem { Attachments = new NSItemProvider[] { resultsProvider } }; var returningItems = new NSExtensionItem[] { resultsItem }; - Context.CompleteRequest(returningItems, null); - } - - private bool ProcessItemProvider(NSItemProvider itemProvider, string type, Action action) - { - if(!itemProvider.HasItemConformingTo(type)) - { - return false; - } - - itemProvider.LoadItem(type, null, (NSObject list, NSError error) => - { - if(list == null) - { - return; - } - - ProviderType = type; - var dict = list as NSDictionary; - action(dict); - - Debug.WriteLine("BW LOG, ProviderType: " + ProviderType); - Debug.WriteLine("BW LOG, Url: " + Url); - Debug.WriteLine("BW LOG, Title: " + SiteTitle); - Debug.WriteLine("BW LOG, Username: " + Username); - Debug.WriteLine("BW LOG, Password: " + Password); - Debug.WriteLine("BW LOG, Old Password: " + OldPassword); - Debug.WriteLine("BW LOG, Notes: " + Notes); - Debug.WriteLine("BW LOG, Details: " + Details); - - if(PasswordOptions != null) - { - Debug.WriteLine("BW LOG, PasswordOptions Min Length: " + PasswordOptions.MinLength); - Debug.WriteLine("BW LOG, PasswordOptions Max Length: " + PasswordOptions.MaxLength); - Debug.WriteLine("BW LOG, PasswordOptions Require Digits: " + PasswordOptions.RequireDigits); - Debug.WriteLine("BW LOG, PasswordOptions Require Symbols: " + PasswordOptions.RequireSymbols); - Debug.WriteLine("BW LOG, PasswordOptions Forbidden Chars: " + PasswordOptions.ForbiddenCharacters); - } - }); - - return true; - } - - private bool ProcessWebUrlProvider(NSItemProvider itemProvider) - { - return ProcessItemProvider(itemProvider, UTType.PropertyList, (dict) => - { - var result = dict[NSJavaScriptExtension.PreprocessingResultsKey]; - if(result == null) - { - return; - } - - Url = new Uri(result.ValueForKey(new NSString(AppExtensionUrlStringKey)) as NSString); - var jsonStr = result.ValueForKey(new NSString(AppExtensionWebViewPageDetails)) as NSString; - Details = DeserializeString(jsonStr); - }); - } - - private bool ProcessFindLoginProvider(NSItemProvider itemProvider) - { - return ProcessItemProvider(itemProvider, UTTypeAppExtensionFindLoginAction, (dict) => - { - var version = dict[AppExtensionVersionNumberKey] as NSNumber; - var url = dict[AppExtensionUrlStringKey] as NSString; - - if(url != null) - { - Url = new Uri(url); - } - }); - } - - private bool ProcessFindLoginBrowserProvider(NSItemProvider itemProvider, string action) - { - return ProcessItemProvider(itemProvider, action, (dict) => - { - var version = dict[AppExtensionVersionNumberKey] as NSNumber; - var url = dict[AppExtensionUrlStringKey] as NSString; - if(url != null) - { - Url = new Uri(url); - } - - Details = DeserializeDictionary(dict[AppExtensionWebViewPageDetails] as NSDictionary); - }); - } - - private bool ProcessSaveLoginProvider(NSItemProvider itemProvider) - { - return ProcessItemProvider(itemProvider, UTTypeAppExtensionSaveLoginAction, (dict) => - { - var version = dict[AppExtensionVersionNumberKey] as NSNumber; - var url = dict[AppExtensionUrlStringKey] as NSString; - var title = dict[AppExtensionTitleKey] as NSString; - var sectionTitle = dict[AppExtensionSectionTitleKey] as NSString; - var username = dict[AppExtensionUsernameKey] as NSString; - var password = dict[AppExtensionPasswordKey] as NSString; - var notes = dict[AppExtensionNotesKey] as NSString; - var fields = dict[AppExtensionFieldsKey] as NSDictionary; - - if(url != null) - { - Url = new Uri(url); - } - - Url = new Uri(url); - SiteTitle = title; - Username = username; - Password = password; - Notes = notes; - PasswordOptions = DeserializeDictionary(dict[AppExtensionPasswordGeneratorOptionsKey] as NSDictionary); - }); - } - - private bool ProcessChangePasswordProvider(NSItemProvider itemProvider) - { - return ProcessItemProvider(itemProvider, UTTypeAppExtensionChangePasswordAction, (dict) => - { - var version = dict[AppExtensionVersionNumberKey] as NSNumber; - var url = dict[AppExtensionUrlStringKey] as NSString; - var title = dict[AppExtensionTitleKey] as NSString; - var sectionTitle = dict[AppExtensionSectionTitleKey] as NSString; - var username = dict[AppExtensionUsernameKey] as NSString; - var password = dict[AppExtensionPasswordKey] as NSString; - var oldPassword = dict[AppExtensionOldPasswordKey] as NSString; - var notes = dict[AppExtensionNotesKey] as NSString; - var fields = dict[AppExtensionFieldsKey] as NSDictionary; - - if(url != null) - { - Url = new Uri(url); - } - - SiteTitle = title; - Username = username; - Password = password; - OldPassword = oldPassword; - Notes = notes; - PasswordOptions = DeserializeDictionary(dict[AppExtensionPasswordGeneratorOptionsKey] as NSDictionary); - }); - } - - private T DeserializeDictionary(NSDictionary dict) - { - if(dict != null) - { - NSError jsonError; - var jsonData = NSJsonSerialization.Serialize(dict, NSJsonWritingOptions.PrettyPrinted, out 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); - } - - public class PasswordGenerationOptions - { - public int MinLength { get; set; } - public int MaxLength { get; set; } - public bool RequireDigits { get; set; } - public bool RequireSymbols { get; set; } - public string ForbiddenCharacters { get; set; } - } - - public class PageDetails - { - public string DocumentUUID { get; set; } - public string Title { get; set; } - public string Url { get; set; } - public string DocumentUrl { get; set; } - public string TabUrl { get; set; } - public Dictionary Forms { get; set; } - public List Fields { get; set; } - public long CollectedTimestamp { get; set; } - - public class Form - { - public string OpId { get; set; } - public string HtmlName { get; set; } - public string HtmlId { get; set; } - public string HtmlAction { get; set; } - public string HtmlMethod { get; set; } - } - - public class Field - { - public string OpId { get; set; } - public int ElementNumber { get; set; } - public bool Visible { get; set; } - public bool Viewable { get; set; } - public string HtmlId { get; set; } - public string HtmlName { get; set; } - public string HtmlClass { get; set; } - public string LabelRight { get; set; } - public string LabelLeft { get; set; } - public string Type { get; set; } - public string Value { get; set; } - public bool Disabled { get; set; } - public bool Readonly { get; set; } - public string OnePasswordFieldType { get; set; } - public string Form { get; set; } - } - } - - public class FillScript - { - public FillScript(PageDetails pageDetails) - { - if(pageDetails == null) - { - return; - } - - DocumentUUID = pageDetails.DocumentUUID; - - var loginForm = pageDetails.Forms.FirstOrDefault(form => pageDetails.Fields.Any(f => f.Form == form.Key && f.Type == "password")).Value; - if(loginForm == null) - { - return; - } - - Script = new List>(); - - var password = pageDetails.Fields.FirstOrDefault(f => - f.Form == loginForm.OpId - && f.Type == "password"); - - var username = pageDetails.Fields.LastOrDefault(f => - f.Form == loginForm.OpId - && (f.Type == "text" || f.Type == "email") - && f.ElementNumber < password.ElementNumber); - - if(username != null) - { - Script.Add(new List { "click_on_opid", username.OpId }); - Script.Add(new List { "fill_by_opid", username.OpId, "me@example.com" }); - } - - Script.Add(new List { "click_on_opid", password.OpId }); - Script.Add(new List { "fill_by_opid", password.OpId, "mypassword" }); - - if(loginForm.HtmlAction != null) - { - AutoSubmit = new Submit { FocusOpId = password.OpId }; - } - } - - [JsonProperty(PropertyName = "script")] - public List> Script { get; set; } - [JsonProperty(PropertyName = "autosubmit")] - public Submit AutoSubmit { get; set; } - [JsonProperty(PropertyName = "documentUUID")] - public object DocumentUUID { get; set; } - [JsonProperty(PropertyName = "properties")] - public object Properties { get; set; } = new object(); - [JsonProperty(PropertyName = "options")] - public object Options { get; set; } = new object(); - [JsonProperty(PropertyName = "metadata")] - public object MetaData { get; set; } = new object(); - - public class Submit - { - [JsonProperty(PropertyName = "focusOpid")] - public string FocusOpId { get; set; } - } + Context.ExtContext.CompleteRequest(returningItems, null); } } -} \ No newline at end of file +} diff --git a/src/iOS.Extension/LoadingViewController.cs b/src/iOS.Extension/LoadingViewController.cs new file mode 100644 index 000000000..3950b0042 --- /dev/null +++ b/src/iOS.Extension/LoadingViewController.cs @@ -0,0 +1,282 @@ +using System; +using System.Drawing; +using System.Diagnostics; +using Bit.App.Abstractions; +using Bit.App.Repositories; +using Bit.App.Services; +using Bit.iOS.Core.Services; +using Foundation; +using Microsoft.Practices.Unity; +using UIKit; +using XLabs.Ioc; +using XLabs.Ioc.Unity; +using Bit.iOS.Core; +using Newtonsoft.Json; +using Bit.iOS.Extension.Models; +using MobileCoreServices; + +namespace Bit.iOS.Extension +{ + public partial class LoadingViewController : UIViewController + { + private Context _context = new Context(); + + public LoadingViewController(IntPtr handle) : base(handle) + { + } + + public override void ViewDidLoad() + { + base.ViewDidLoad(); + View.BackgroundColor = UIColor.FromPatternImage(new UIImage("boxed-bg.png")); + NavigationController.SetNavigationBarHidden(true, false); + _context.ExtContext = ExtensionContext; + } + + public override void ViewDidAppear(bool animated) + { + base.ViewDidAppear(animated); + + if(!Resolver.IsSet) + { + SetIoc(); + } + + 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)) + { + processed = true; + break; + } + } + + if(processed) + { + break; + } + } + + PerformSegue("seque", this); + } + + public override void PrepareForSegue(UIStoryboardSegue segue, NSObject sender) + { + var navController = segue.DestinationViewController as UINavigationController; + if(navController != null) + { + var actionController = navController.TopViewController as ActionViewController; + if(actionController != null) + { + actionController.Context = _context; + } + } + } + + private void SetIoc() + { + var container = new UnityContainer(); + + container + // Services + .RegisterType(new ContainerControlledLifetimeManager()) + .RegisterType(new ContainerControlledLifetimeManager()) + //.RegisterType(new ContainerControlledLifetimeManager()) + .RegisterType(new ContainerControlledLifetimeManager()) + .RegisterType(new ContainerControlledLifetimeManager()) + .RegisterType(new ContainerControlledLifetimeManager()) + .RegisterType(new ContainerControlledLifetimeManager()) + .RegisterType(new ContainerControlledLifetimeManager()) + //.RegisterType(new ContainerControlledLifetimeManager()) + // Repositories + .RegisterType(new ContainerControlledLifetimeManager()) + .RegisterType(new ContainerControlledLifetimeManager()) + .RegisterType(new ContainerControlledLifetimeManager()) + .RegisterType(new ContainerControlledLifetimeManager()) + .RegisterType(new ContainerControlledLifetimeManager()); + // Other + //.RegisterInstance(CrossSettings.Current, new ContainerControlledLifetimeManager()) + //.RegisterInstance(CrossConnectivity.Current, new ContainerControlledLifetimeManager()) + //.RegisterInstance(UserDialogs.Instance, new ContainerControlledLifetimeManager()) + //.RegisterInstance(CrossFingerprint.Current, new ContainerControlledLifetimeManager()); + + Resolver.SetResolver(new UnityResolver(container)); + } + + private bool ProcessItemProvider(NSItemProvider itemProvider, string type, Action action) + { + if(!itemProvider.HasItemConformingTo(type)) + { + return false; + } + + itemProvider.LoadItem(type, null, (NSObject list, NSError error) => + { + if(list == null) + { + return; + } + + _context.ProviderType = type; + var dict = list as NSDictionary; + action(dict); + + Debug.WriteLine("BW LOG, ProviderType: " + _context.ProviderType); + Debug.WriteLine("BW LOG, Url: " + _context.Url); + Debug.WriteLine("BW LOG, Title: " + _context.SiteTitle); + 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.Url = new Uri(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.Url = new Uri(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.Url = new Uri(url); + } + + _context.Details = DeserializeDictionary(dict[Constants.AppExtensionWebViewPageDetails] as NSDictionary); + }); + } + + 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.Url = new Uri(url); + } + + _context.Url = new Uri(url); + _context.SiteTitle = 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.Url = new Uri(url); + } + + _context.SiteTitle = title; + _context.Username = username; + _context.Password = password; + _context.OldPassword = oldPassword; + _context.Notes = notes; + _context.PasswordOptions = DeserializeDictionary(dict[Constants.AppExtensionPasswordGeneratorOptionsKey] as NSDictionary); + }); + } + + private T DeserializeDictionary(NSDictionary dict) + { + if(dict != null) + { + NSError jsonError; + var jsonData = NSJsonSerialization.Serialize(dict, NSJsonWritingOptions.PrettyPrinted, out 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); + } + + } +} \ No newline at end of file diff --git a/src/iOS.Extension/SplashViewController.designer.cs b/src/iOS.Extension/LoadingViewController.designer.cs similarity index 82% rename from src/iOS.Extension/SplashViewController.designer.cs rename to src/iOS.Extension/LoadingViewController.designer.cs index eb595279f..a528ba3b7 100644 --- a/src/iOS.Extension/SplashViewController.designer.cs +++ b/src/iOS.Extension/LoadingViewController.designer.cs @@ -11,8 +11,8 @@ using UIKit; namespace Bit.iOS.Extension { - [Register ("SplashViewController")] - partial class SplashViewController + [Register ("LoadingViewController")] + partial class LoadingViewController { void ReleaseDesignerOutlets () { diff --git a/src/iOS.Extension/MainInterface.storyboard b/src/iOS.Extension/MainInterface.storyboard index bb288336f..a67b63df7 100644 --- a/src/iOS.Extension/MainInterface.storyboard +++ b/src/iOS.Extension/MainInterface.storyboard @@ -18,26 +18,28 @@ - + - + - + + + + + - - - - + + @@ -60,7 +62,7 @@ - + diff --git a/src/iOS.Extension/Models/Context.cs b/src/iOS.Extension/Models/Context.cs new file mode 100644 index 000000000..721258a9d --- /dev/null +++ b/src/iOS.Extension/Models/Context.cs @@ -0,0 +1,19 @@ +using System; +using Foundation; + +namespace Bit.iOS.Extension.Models +{ + public class Context + { + public NSExtensionContext ExtContext { get; set; } + public string ProviderType { get; set; } + public Uri Url { get; set; } + public string SiteTitle { get; set; } + public string Username { get; set; } + public string Password { get; set; } + public string OldPassword { get; set; } + public string Notes { get; set; } + public PasswordGenerationOptions PasswordOptions { get; set; } + public PageDetails Details { get; set; } + } +} diff --git a/src/iOS.Extension/Models/FillScript.cs b/src/iOS.Extension/Models/FillScript.cs new file mode 100644 index 000000000..32d02faab --- /dev/null +++ b/src/iOS.Extension/Models/FillScript.cs @@ -0,0 +1,71 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Newtonsoft.Json; + +namespace Bit.iOS.Extension.Models +{ + public class FillScript + { + public FillScript(PageDetails pageDetails) + { + if(pageDetails == null) + { + return; + } + + DocumentUUID = pageDetails.DocumentUUID; + + var loginForm = pageDetails.Forms.FirstOrDefault(form => pageDetails.Fields.Any(f => f.Form == form.Key && f.Type == "password")).Value; + if(loginForm == null) + { + return; + } + + Script = new List>(); + + var password = pageDetails.Fields.FirstOrDefault(f => + f.Form == loginForm.OpId + && f.Type == "password"); + + var username = pageDetails.Fields.LastOrDefault(f => + f.Form == loginForm.OpId + && (f.Type == "text" || f.Type == "email") + && f.ElementNumber < password.ElementNumber); + + if(username != null) + { + Script.Add(new List { "click_on_opid", username.OpId }); + Script.Add(new List { "fill_by_opid", username.OpId, "me@example.com" }); + } + + Script.Add(new List { "click_on_opid", password.OpId }); + Script.Add(new List { "fill_by_opid", password.OpId, "mypassword" }); + + if(loginForm.HtmlAction != null) + { + AutoSubmit = new Submit { FocusOpId = password.OpId }; + } + } + + [JsonProperty(PropertyName = "script")] + public List> Script { get; set; } + [JsonProperty(PropertyName = "autosubmit")] + public Submit AutoSubmit { get; set; } + [JsonProperty(PropertyName = "documentUUID")] + public object DocumentUUID { get; set; } + [JsonProperty(PropertyName = "properties")] + public object Properties { get; set; } = new object(); + [JsonProperty(PropertyName = "options")] + public object Options { get; set; } = new object(); + [JsonProperty(PropertyName = "metadata")] + public object MetaData { get; set; } = new object(); + + public class Submit + { + [JsonProperty(PropertyName = "focusOpid")] + public string FocusOpId { get; set; } + } + } + +} diff --git a/src/iOS.Extension/Models/PageDetails.cs b/src/iOS.Extension/Models/PageDetails.cs new file mode 100644 index 000000000..f2fc44e4f --- /dev/null +++ b/src/iOS.Extension/Models/PageDetails.cs @@ -0,0 +1,46 @@ +using System; +using System.Collections.Generic; + +namespace Bit.iOS.Extension.Models +{ + public class PageDetails + { + public string DocumentUUID { get; set; } + public string Title { get; set; } + public string Url { get; set; } + public string DocumentUrl { get; set; } + public string TabUrl { get; set; } + public Dictionary Forms { get; set; } + public List Fields { get; set; } + public long CollectedTimestamp { get; set; } + + public class Form + { + public string OpId { get; set; } + public string HtmlName { get; set; } + public string HtmlId { get; set; } + public string HtmlAction { get; set; } + public string HtmlMethod { get; set; } + } + + public class Field + { + public string OpId { get; set; } + public int ElementNumber { get; set; } + public bool Visible { get; set; } + public bool Viewable { get; set; } + public string HtmlId { get; set; } + public string HtmlName { get; set; } + public string HtmlClass { get; set; } + public string LabelRight { get; set; } + public string LabelLeft { get; set; } + public string Type { get; set; } + public string Value { get; set; } + public bool Disabled { get; set; } + public bool Readonly { get; set; } + public string OnePasswordFieldType { get; set; } + public string Form { get; set; } + } + } + +} diff --git a/src/iOS.Extension/Models/PasswordGenerationOptions.cs b/src/iOS.Extension/Models/PasswordGenerationOptions.cs new file mode 100644 index 000000000..0f897a78b --- /dev/null +++ b/src/iOS.Extension/Models/PasswordGenerationOptions.cs @@ -0,0 +1,13 @@ +using System; + +namespace Bit.iOS.Extension.Models +{ + public class PasswordGenerationOptions + { + public int MinLength { get; set; } + public int MaxLength { get; set; } + public bool RequireDigits { get; set; } + public bool RequireSymbols { get; set; } + public string ForbiddenCharacters { get; set; } + } +} diff --git a/src/iOS.Extension/SplashViewController.cs b/src/iOS.Extension/SplashViewController.cs deleted file mode 100644 index d7072f41e..000000000 --- a/src/iOS.Extension/SplashViewController.cs +++ /dev/null @@ -1,111 +0,0 @@ -using System; -using System.Drawing; -using System.Diagnostics; -using Bit.App.Abstractions; -using Bit.App.Repositories; -using Bit.App.Services; -using Bit.iOS.Core.Services; -using Foundation; -using Microsoft.Practices.Unity; -using UIKit; -using XLabs.Ioc; -using XLabs.Ioc.Unity; - -namespace Bit.iOS.Extension -{ - public partial class SplashViewController : UIViewController - { - public SplashViewController(IntPtr handle) : base(handle) - { - } - - public override void DidReceiveMemoryWarning() - { - // Releases the view if it doesn't have a superview. - base.DidReceiveMemoryWarning(); - - // Release any cached data, images, etc that aren't in use. - } - - #region View lifecycle - - public override void ViewDidLoad() - { - base.ViewDidLoad(); - View.BackgroundColor = UIColor.FromPatternImage(new UIImage("boxed-bg.png")); - NavigationController.SetNavigationBarHidden(true, false); - } - - public override void ViewWillAppear(bool animated) - { - base.ViewWillAppear(animated); - } - - public override void ViewDidAppear(bool animated) - { - base.ViewDidAppear(animated); - - if(!Resolver.IsSet) - { - SetIoc(); - } - - PerformSegue("seque", this); - } - - public override void PrepareForSegue (UIStoryboardSegue segue, NSObject sender) - { - var navController = segue.DestinationViewController as UINavigationController; - if(navController != null) - { - var actionController = navController.TopViewController as ActionViewController; - if(actionController != null) - { - actionController.Context = ExtensionContext; - } - } - } - - private void SetIoc() - { - var container = new UnityContainer(); - - container - // Services - .RegisterType(new ContainerControlledLifetimeManager()) - .RegisterType(new ContainerControlledLifetimeManager()) - //.RegisterType(new ContainerControlledLifetimeManager()) - .RegisterType(new ContainerControlledLifetimeManager()) - .RegisterType(new ContainerControlledLifetimeManager()) - .RegisterType(new ContainerControlledLifetimeManager()) - .RegisterType(new ContainerControlledLifetimeManager()) - .RegisterType(new ContainerControlledLifetimeManager()) - //.RegisterType(new ContainerControlledLifetimeManager()) - // Repositories - .RegisterType(new ContainerControlledLifetimeManager()) - .RegisterType(new ContainerControlledLifetimeManager()) - .RegisterType(new ContainerControlledLifetimeManager()) - .RegisterType(new ContainerControlledLifetimeManager()) - .RegisterType(new ContainerControlledLifetimeManager()); - // Other - //.RegisterInstance(CrossSettings.Current, new ContainerControlledLifetimeManager()) - //.RegisterInstance(CrossConnectivity.Current, new ContainerControlledLifetimeManager()) - //.RegisterInstance(UserDialogs.Instance, new ContainerControlledLifetimeManager()) - //.RegisterInstance(CrossFingerprint.Current, new ContainerControlledLifetimeManager()); - - Resolver.SetResolver(new UnityResolver(container)); - } - - public override void ViewWillDisappear(bool animated) - { - base.ViewWillDisappear(animated); - } - - public override void ViewDidDisappear(bool animated) - { - base.ViewDidDisappear(animated); - } - - #endregion - } -} \ No newline at end of file diff --git a/src/iOS.Extension/iOS.Extension.csproj b/src/iOS.Extension/iOS.Extension.csproj index a6d36de2b..eb16d6d16 100644 --- a/src/iOS.Extension/iOS.Extension.csproj +++ b/src/iOS.Extension/iOS.Extension.csproj @@ -95,9 +95,13 @@ - - - SplashViewController.cs + + + + + + + LoadingViewController.cs