diff --git a/src/iOS/AppDelegate.cs b/src/iOS/AppDelegate.cs index 295ad42e6..5f98cc4a4 100644 --- a/src/iOS/AppDelegate.cs +++ b/src/iOS/AppDelegate.cs @@ -1,35 +1,150 @@ using System; -using System.Collections.Generic; -using System.Linq; - +using System.IO; +using System.Threading.Tasks; +using Bit.App.Abstractions; +using Bit.App.Services; +using Bit.Core; +using Bit.Core.Abstractions; +using Bit.Core.Services; +using Bit.Core.Utilities; +using Bit.iOS.Core.Services; +using Bit.iOS.Services; using Foundation; using UIKit; namespace Bit.iOS { - // The UIApplicationDelegate for the application. This class is responsible for launching the - // User Interface of the application, as well as listening (and optionally responding) to - // application events from iOS. [Register("AppDelegate")] public partial class AppDelegate : Xamarin.Forms.Platform.iOS.FormsApplicationDelegate { - // - // This method is invoked when the application has loaded and is ready to run. In this - // method you should instantiate the window, load the UI into it and then make the window - // visible. - // - // You have 17 seconds to return from this method, or iOS will terminate your application. - // + private const string AppId = "com.8bit.bitwarden"; + private const string AppGroupId = "group.com.8bit.bitwarden"; + private const string AccessGroup = "LTZ2PFU5D6.com.8bit.bitwarden"; + + private iOSPushNotificationHandler _pushHandler; + public override bool FinishedLaunching(UIApplication app, NSDictionary options) { Xamarin.Forms.Forms.Init(); - - FFImageLoading.Forms.Platform.CachedImageRenderer.Init(); + InitApp(); LoadApplication(new App.App(null)); ZXing.Net.Mobile.Forms.iOS.Platform.Init(); return base.FinishedLaunching(app, options); } + + public override void FailedToRegisterForRemoteNotifications(UIApplication application, NSError error) + { + _pushHandler?.OnErrorReceived(error); + } + + public override void RegisteredForRemoteNotifications(UIApplication application, NSData deviceToken) + { + _pushHandler?.OnRegisteredSuccess(deviceToken); + } + + public override void DidRegisterUserNotificationSettings(UIApplication application, + UIUserNotificationSettings notificationSettings) + { + application.RegisterForRemoteNotifications(); + } + + public override void DidReceiveRemoteNotification(UIApplication application, NSDictionary userInfo, + Action completionHandler) + { + _pushHandler?.OnMessageReceived(userInfo); + } + + public override void ReceivedRemoteNotification(UIApplication application, NSDictionary userInfo) + { + _pushHandler?.OnMessageReceived(userInfo); + } + + private void InitApp() + { + if(ServiceContainer.RegisteredServices.Count > 0) + { + return; + } + RegisterLocalServices(); + ServiceContainer.Init(); + _pushHandler = new iOSPushNotificationHandler( + ServiceContainer.Resolve("pushNotificationListenerService")); + // TODO: HockeyApp Init + } + + private void RegisterLocalServices() + { + ServiceContainer.Register("logService", new ConsoleLogService()); + ServiceContainer.Register("settingsShim", new App.Migration.SettingsShim()); + if(false && App.Migration.MigrationHelpers.NeedsMigration()) + { + ServiceContainer.Register( + "oldSecureStorageService", new Migration.KeyChainStorageService()); + } + + // Note: This might cause a race condition. Investigate more. + Task.Run(() => + { + FFImageLoading.Forms.Platform.CachedImageRenderer.Init(); + FFImageLoading.ImageService.Instance.Initialize(new FFImageLoading.Config.Configuration + { + FadeAnimationEnabled = false, + FadeAnimationForCachedImages = false + }); + }); + + var preferencesStorage = new PreferencesStorageService(AppGroupId); + var appGroupContainer = new NSFileManager().GetContainerUrl(AppGroupId); + var liteDbStorage = new LiteDbStorageService( + Path.Combine(appGroupContainer.Path, "Library", "bitwarden.db")); + liteDbStorage.InitAsync(); + var localizeService = new LocalizeService(); + var broadcasterService = new BroadcasterService(); + var messagingService = new MobileBroadcasterMessagingService(broadcasterService); + var i18nService = new MobileI18nService(localizeService.GetCurrentCultureInfo()); + var secureStorageService = new KeyChainStorageService(AppId, AccessGroup); + var cryptoPrimitiveService = new CryptoPrimitiveService(); + var mobileStorageService = new MobileStorageService(preferencesStorage, liteDbStorage); + var deviceActionService = new DeviceActionService(mobileStorageService, messagingService); + var platformUtilsService = new MobilePlatformUtilsService(deviceActionService, messagingService, + broadcasterService); + + ServiceContainer.Register("broadcasterService", broadcasterService); + ServiceContainer.Register("messagingService", messagingService); + ServiceContainer.Register("localizeService", localizeService); + ServiceContainer.Register("i18nService", i18nService); + ServiceContainer.Register("cryptoPrimitiveService", cryptoPrimitiveService); + ServiceContainer.Register("storageService", mobileStorageService); + ServiceContainer.Register("secureStorageService", secureStorageService); + ServiceContainer.Register("deviceActionService", deviceActionService); + ServiceContainer.Register("platformUtilsService", platformUtilsService); + + // Push + var notificationListenerService = new PushNotificationListenerService(); + ServiceContainer.Register( + "pushNotificationListenerService", notificationListenerService); + var iosPushNotificationService = new iOSPushNotificationService(); + ServiceContainer.Register( + "pushNotificationService", iosPushNotificationService); + } + + private void Bootstrap() + { + (ServiceContainer.Resolve("i18nService") as MobileI18nService).Init(); + ServiceContainer.Resolve("authService").Init(); + // Note: This is not awaited + var bootstrapTask = BootstrapAsync(); + } + + private async Task BootstrapAsync() + { + var disableFavicon = await ServiceContainer.Resolve("storageService").GetAsync( + Constants.DisableFaviconKey); + await ServiceContainer.Resolve("stateService").SaveAsync(Constants.DisableFaviconKey, + disableFavicon); + await ServiceContainer.Resolve("environmentService").SetUrlsFromStorageAsync(); + } } } diff --git a/src/iOS/Main.cs b/src/iOS/Main.cs index 3d3672401..13f65ed2e 100644 --- a/src/iOS/Main.cs +++ b/src/iOS/Main.cs @@ -4,13 +4,9 @@ namespace Bit.iOS { public class Application { - // This is the main entry point of the application. static void Main(string[] args) { ObjCRuntime.Dlfcn.dlopen(ObjCRuntime.Constants.libSystemLibrary, 0); - - // if you want to use a different Application Delegate class from "AppDelegate" - // you can specify it here. UIApplication.Main(args, null, "AppDelegate"); } } diff --git a/src/iOS/Migration/KeyChainStorageService.cs b/src/iOS/Migration/KeyChainStorageService.cs new file mode 100644 index 000000000..bc230bc8f --- /dev/null +++ b/src/iOS/Migration/KeyChainStorageService.cs @@ -0,0 +1,92 @@ +using System; +using System.Runtime.CompilerServices; +using Bit.App.Migration.Abstractions; +using Foundation; +using Security; + +namespace Bit.iOS.Migration +{ + public class KeyChainStorageService : IOldSecureStorageService + { + public void Store(string key, byte[] dataBytes) + { + using(var data = NSData.FromArray(dataBytes)) + using(var newRecord = GetKeyRecord(key, data)) + { + Delete(key); + CheckError(SecKeyChain.Add(newRecord)); + } + } + + public byte[] Retrieve(string key) + { + SecStatusCode resultCode; + + using(var existingRecord = GetKeyRecord(key)) + using(var record = SecKeyChain.QueryAsRecord(existingRecord, out resultCode)) + { + if(resultCode == SecStatusCode.ItemNotFound) + { + return null; + } + + CheckError(resultCode); + return record.Generic.ToArray(); + } + } + + public void Delete(string key) + { + using(var record = GetExistingRecord(key)) + { + if(record != null) + { + CheckError(SecKeyChain.Remove(record)); + } + } + } + + public bool Contains(string key) + { + using(var existingRecord = GetExistingRecord(key)) + { + return existingRecord != null; + } + } + + private static void CheckError(SecStatusCode resultCode, [CallerMemberName] string caller = null) + { + if(resultCode != SecStatusCode.Success) + { + throw new Exception(string.Format("Failed to execute {0}. Result code: {1}", caller, resultCode)); + } + } + + private static SecRecord GetKeyRecord(string key, NSData data = null) + { + var record = new SecRecord(SecKind.GenericPassword) + { + Service = "com.8bit.bitwarden", + Account = key, + AccessGroup = "LTZ2PFU5D6.com.8bit.bitwarden" + }; + + if(data != null) + { + record.Generic = data; + } + + return record; + } + + private static SecRecord GetExistingRecord(string key) + { + var existingRecord = GetKeyRecord(key); + + SecStatusCode resultCode; + SecKeyChain.QueryAsRecord(existingRecord, out resultCode); + + return resultCode == SecStatusCode.Success ? existingRecord : null; + } + } +} diff --git a/src/iOS/iOS.csproj b/src/iOS/iOS.csproj index 99beba7bc..17d169bd3 100644 --- a/src/iOS/iOS.csproj +++ b/src/iOS/iOS.csproj @@ -110,6 +110,7 @@ +